使用Testify断言字符串指针切片包含预期字符串的正确方法
问题背景
当从API调用等逻辑拿到[]*string类型返回值时,常需要校验切片中存在指向预期字符串值的指针。先构造测试样例复现场景:
// 测试公共样例 var one = "one" var two = "two" var three = "three" var slice = []*string{&one, &two, &three}
最容易写出的错误断言如下:
func TestSliceContainsString(t *testing.T) { assert.Contains(t, slice, "one") }
运行后直接报错:[]*string{(*string)(0x22994f0), (*string)(0x2299510), (*string)(0x2299500)} does not contain "one"。原因很明确:切片存储的是字符串指针,传入的字符串值"one"和切片元素类型不匹配,自然无法命中。
现有方案的不足
目前常见的两种可行写法都存在明显的冗余问题:
- 遍历解引用整个切片后再断言:
逻辑完全正确,但每次断言都要重复写遍历解引用的样板代码,冗余度高。func TestDereferencedSliceContainsString(t *testing.T) { deref := make([]string, len(slice)) for i, v := range slice { deref[i] = *v } assert.Contains(t, deref, "one") } - 定义临时变量存期望值,传入变量地址做断言:
这个写法能正常生效,但Go不支持直接对字符串字面量取地址,无法直接写func TestSliceContainsPointerToExpectation(t *testing.T) { expect := "one" // assert.Same 校验指针地址完全一致,这里会失败 assert.Same(t, &one, &one) assert.NotSame(t, &one, &expect) // assert.Contains 底层用reflect.DeepEqual判断,值一致就会通过 assert.Contains(t, slice, &expect) }&"one"传参,每次断言都要额外定义临时变量,写法不够紧凑。
推荐的惯用实现
方案1:通用指针工具函数+原生断言(首选)
这是Go社区处理「无法直接取字面量地址」问题的标准方案,只需要在测试公共文件里写1行泛型工具函数,就可以在所有测试场景复用:
// Ptr 返回任意类型值的指针,兼容所有基础类型、自定义类型 func Ptr[T any](v T) *T { return &v } func TestSliceContainsStringPtr(t *testing.T) { // 直接传入字面量,不需要额外定义临时变量 assert.Contains(t, slice, Ptr("one")) }
这个写法完全兼容Testify原生Contains断言,代码非常紧凑。之所以能生效,是因为assert.Contains底层用reflect.DeepEqual做元素匹配:两个*string类型的值哪怕内存地址不同,只要指向的实际值相等,就会被判定为匹配,完全符合「校验切片中存在指向预期值的指针」的需求。
如果项目还在使用Go 1.18以前的版本(不支持泛型),可以单独写一个字符串专用的指针函数,逻辑完全一致:
func StrPtr(s string) *string { return &s }
方案2:自定义条件断言
如果不想额外定义工具函数,也可以直接用Testify自带的Condition断言,把匹配逻辑内联:
func TestSliceContainsStringValue(t *testing.T) { assert.Condition(t, func() bool { for _, v := range slice { if v != nil && *v == "one" { return true } } return false }, "切片中应存在指向值为'one'的字符串指针") }
这个写法不需要额外依赖工具函数,匹配逻辑完全可控,还支持自定义报错信息,适合匹配规则比较复杂的场景。
选型建议
优先选择Ptr工具函数+原生Contains的写法:这是目前Go生态中处理这类指针断言的最惯用方式,代码简洁、工具函数可跨场景复用,完全契合Testify的断言设计逻辑,没有额外心智负担。
不推荐每次手动遍历解引用切片,重复的样板代码会降低测试代码的可读性和维护效率。
内容的提问来源于stack exchange,提问作者Geoffrey Wiseman

