如何正确在结构体切片中使用Go泛型?单元测试问题求解
问题:Go泛型函数单元测试中多类型测试表的实现问题
我正在开发一个Go项目,这是首次在项目中使用泛型。目前LinearSearch泛型函数本身运行正常,但在创建使用结构体作为测试表的单元测试时遇到问题。
类型定义及待测试函数
type AllowedTypes interface { int | int8 | int16 | int32 | int64 | float32 | float64 | string } func LinearSearch[T AllowedTypes](value T, array []T) bool { for _, e := range array { if e == value { return true } } return false }
尝试的测试代码及错误
编写的测试代码如下:
func TestLinearSearch(t *testing.T) { type tableProps[T AllowedTypes] struct { Name string Value T Array []T ExpectedResult bool } tables := []tableProps{ { Name: "Success: Value Exists (int)", Value: 5, Array: []interface{}{1, 3, 5, 6, 8}, ExpectedResult: true, }, { Name: "Success: Value Exists (string)", Value: "y", Array: []interface{}{"a", "c", "f", "g", "y"}, ExpectedResult: true, }, } for _, tc := range tables { t.Run(tc.Name, func(t *testing.T) { actualResult := LinearSearch(tc.Value, tc.Array) require.Equal(t, tc.ExpectedResult, actualResult) }) } }
运行时出现错误:
cannot use generic type tableProps[T AllowedTypes] without instantiation
尝试将测试表实例化为[]tableProps[AllowedTypes]后,又出现新错误:
cannot use type AllowedTypes outside a type constraint: interface contains type constraints
已知单独实例化特定类型(如[]tableProps[int])可运行,但无法同时支持AllowedTypes下的多种类型测试用例。请问如何在结构体切片中正确使用自定义泛型,以同时测试多种类型?
解决方案
Go中泛型结构体无法直接将不同类型的实例存入同一个切片(切片要求元素类型统一),你可以通过以下两种方式实现多类型测试:
方法一:泛型测试子函数(推荐)
定义一个泛型子函数复用测试逻辑,针对每种AllowedTypes类型单独创建测试表并调用该函数,既能复用代码,又能覆盖所有目标类型。
示例代码:
import ( "testing" "github.com/stretchr/testify/require" ) func TestLinearSearch(t *testing.T) { // 泛型测试子函数,处理单一类型的测试用例 runTypeTests := func[T AllowedTypes](t *testing.T, cases []struct { Name string Value T Array []T ExpectedResult bool }) { for _, tc := range cases { t.Run(tc.Name, func(t *testing.T) { actual := LinearSearch(tc.Value, tc.Array) require.Equal(t, tc.ExpectedResult, actual) }) } } // 测试int类型 t.Run("int类型测试", func(t *testing.T) { intCases := []struct { Name string Value int Array []int ExpectedResult bool }{ {"存在目标值", 5, []int{1, 3, 5, 6, 8}, true}, {"不存在目标值", 2, []int{1, 3, 5, 6, 8}, false}, } runTypeTests[int](t, intCases) }) // 测试string类型 t.Run("string类型测试", func(t *testing.T) { strCases := []struct { Name string Value string Array []string ExpectedResult bool }{ {"存在目标值", "y", []string{"a", "c", "f", "g", "y"}, true}, {"不存在目标值", "z", []string{"a", "c", "f", "g", "y"}, false}, } runTypeTests[string](t, strCases) }) // 按需添加其他类型测试,比如float64、int8等 t.Run("float64类型测试", func(t *testing.T) { floatCases := []struct { Name string Value float64 Array []float64 ExpectedResult bool }{ {"存在目标值", 3.14, []float64{1.0, 2.5, 3.14, 4.2}, true}, {"不存在目标值", 5.0, []float64{1.0, 2.5, 3.14, 4.2}, false}, } runTypeTests[float64](t, floatCases) }) }
方法二:接口封装测试逻辑
如果不想为每种类型单独写测试表,可以定义统一的测试用例接口,让不同类型的测试用例实现该接口,再将所有用例存入接口切片执行。不过这种方式需要手动处理类型断言,代码相对繁琐:
示例代码:
import ( "testing" "github.com/stretchr/testify/require" ) func TestLinearSearch(t *testing.T) { type testCase interface { run(t *testing.T) } // int类型测试用例 type intTestCase struct { Name string Value int Array []int ExpectedResult bool } func (tc intTestCase) run(t *testing.T) { actual := LinearSearch(tc.Value, tc.Array) require.Equal(t, tc.ExpectedResult, actual) } // string类型测试用例 type stringTestCase struct { Name string Value string Array []string ExpectedResult bool } func (tc stringTestCase) run(t *testing.T) { actual := LinearSearch(tc.Value, tc.Array) require.Equal(t, tc.ExpectedResult, actual) } // 收集所有测试用例 allCases := []testCase{ intTestCase{"存在目标值(int)", 5, []int{1, 3, 5, 6, 8}, true}, intTestCase{"不存在目标值(int)", 2, []int{1, 3, 5, 6, 8}, false}, stringTestCase{"存在目标值(string)", "y", []string{"a", "c", "f", "g", "y"}, true}, stringTestCase{"不存在目标值(string)", "z", []string{"a", "c", "f", "g", "y"}, false}, } // 执行所有测试 for _, tc := range allCases { switch v := tc.(type) { case intTestCase: t.Run(v.Name, func(t *testing.T) { v.run(t) }) case stringTestCase: t.Run(v.Name, func(t *testing.T) { v.run(t) }) } } }
方案对比
方法一更贴合Go泛型的设计思路,代码结构清晰,无需额外类型断言,复用性强,适合绝大多数场景。只需按照AllowedTypes中定义的类型逐一添加测试块即可。
内容的提问来源于stack exchange,提问作者new line
相关产品推荐
相关产品推荐

