在golang中为泛型函数编写单元测试

huangapple go评论70阅读模式
英文:

Writing unit tests for generics function in golang

问题

我有这个简单的通用函数,用于从map中检索键:

// getMapKeys 返回一个 map 的键
func getMapKeys[T comparable, U any](m map[T]U) []T {
	keys := make([]T, len(m))
	i := 0
	for k := range m {
		keys[i] = k
		i++
	}
	return keys
}

我试图编写表驱动的单元测试,如下所示:

var testUnitGetMapKeys = []struct {
	name     string
	inputMap interface{}
	expected interface{}
}{
	{
		name:     "string keys",
		inputMap: map[string]int{"foo": 1, "bar": 2, "baz": 3},
		expected: []string{"foo", "bar", "baz"},
	},
	{
		name:     "int keys",
		inputMap: map[int]string{1: "foo", 2: "bar", 3: "baz"},
		expected: []int{1, 2, 3},
	},
	{
		name:     "float64 keys",
		inputMap: map[float64]bool{1.0: true, 2.5: false, 3.1415: true},
		expected: []float64{1.0, 2.5, 3.1415},
	},
}

然而,以下代码失败了:

func (us *UnitUtilSuite) TestUnitGetMapKeys() {
	for i := range testUnitGetMapKeys {
		us.T().Run(testUnitGetMapKeys[i].name, func(t *testing.T) {
			gotKeys := getMapKeys(testUnitGetMapKeys[i].inputMap)
		})
	}
}

报错信息为:

testUnitGetMapKeys[i].inputMap 的类型 interface{} 与 map[T]U 不匹配(无法推断出 T 和 U)

这可以通过显式转换来修复:

gotKeys := getMapKeys(testUnitGetMapKeys[i].inputMap.(map[string]string))

是否有一种方法可以自动化这些测试,而不必为每个输入测试变量执行显式转换?

英文:

I have this trivial generic function that retrieves the keys from a map

// getMapKeys returns the keys of a map
func getMapKeys[T comparable, U any](m map[T]U) []T {
	keys := make([]T, len(m))
	i := 0
	for k := range m {
		keys[i] = k
		i++
	}
	return keys
}

I am attempting to write table driven unit tests for it as in:

var testUnitGetMapKeys = []struct {
	name     string
	inputMap interface{}
	expected interface{}
}{
	{
		name:     "string keys",
		inputMap: map[string]int{"foo": 1, "bar": 2, "baz": 3},
		expected: []string{"foo", "bar", "baz"},
	},
	{
		name:     "int keys",
		inputMap: map[int]string{1: "foo", 2: "bar", 3: "baz"},
		expected: []int{1, 2, 3},
	},
	{
		name:     "float64 keys",
		inputMap: map[float64]bool{1.0: true, 2.5: false, 3.1415: true},
		expected: []float64{1.0, 2.5, 3.1415},
	},
}

However, the following code fails

func (us *UnitUtilSuite) TestUnitGetMapKeys() {
	for i := range testUnitGetMapKeys {
		us.T().Run(testUnitGetMapKeys[i].name, func(t *testing.T) {
			gotKeys := getMapKeys(testUnitGetMapKeys[i].inputMap)
		})
	}
}

with

> type interface{} of testUnitGetMapKeys[i].inputMap does not match map[T]U (cannot infer T and U)

This is fixed by explicit casting

gotKeys := getMapKeys(testUnitGetMapKeys[i].inputMap.(map[string]string))

Is there a way to automate these tests rather than having to perform explicit casting for each input test variable?

答案1

得分: 1

请注意,除非你的通用函数除了通用逻辑之外还执行了一些特定类型的逻辑,否则通过针对不同类型测试该函数不会带来任何好处。该函数的通用逻辑对类型参数的类型集中的所有类型都是相同的,因此可以通过单一类型完全测试该逻辑。

但是,如果你仍然想针对不同类型运行测试,你可以简单地按照以下方式进行操作:

var testUnitGetMapKeys = []struct {
    name string
    got  any
    want any
}{
    {
        name: "string keys",
        got:  getMapKeys(map[string]int{"foo": 1, "bar": 2, "baz": 3}),
        want: []string{"foo", "bar", "baz"},
    },
    {
        name: "int keys",
        got:  getMapKeys(map[int]string{1: "foo", 2: "bar", 3: "baz"}),
        want: []int{1, 2, 3},
    },
    {
        name: "float64 keys",
        got:  getMapKeys(map[float64]bool{1.0: true, 2.5: false, 3.1415: true}),
        want: []float64{1.0, 2.5, 3.1415},
    },
}

// ...

func (us *UnitUtilSuite) TestUnitGetMapKeys() {
    for _, tt := range testUnitGetMapKeys {
        us.T().Run(tt.name, func(t *testing.T) {
            if !reflect.DeepEqual(tt.got, tt.want) {
                t.Errorf("got=%v; want=%v", tt.got, tt.want)
            }
        })
    }
}
英文:

Note that, unless your generic function is performing some type-specific logic in addition to the generic logic, you gain nothing by testing that function against different types. The function's generic logic is identical for all types in the type parameter's type set and can therefore be fully exercised with a single type.

But if you want to run the tests against different types anyway, you can simply do the following:

var testUnitGetMapKeys = []struct {
    name string
    got  any
    want any
}{
    {
        name: "string keys",
        got:  getMapKeys(map[string]int{"foo": 1, "bar": 2, "baz": 3}),
        want: []string{"foo", "bar", "baz"},
    },
    {
        name: "int keys",
        got:  getMapKeys(map[int]string{1: "foo", 2: "bar", 3: "baz"}),
        want: []int{1, 2, 3},
    },
    {
        name: "float64 keys",
        got:  getMapKeys(map[float64]bool{1.0: true, 2.5: false, 3.1415: true}),
        want: []float64{1.0, 2.5, 3.1415},
    },
}

// ...

func (us *UnitUtilSuite) TestUnitGetMapKeys() {
    for _, tt := range testUnitGetMapKeys {
        us.T().Run(tt.name, func(t *testing.T) {
            if !reflect.DeepEqual(tt.got, tt.want) {
                t.Errorf("got=%v; want=%v", tt.got, tt.want)
            }
        })
    }
}

huangapple
  • 本文由 发表于 2023年3月21日 15:22:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/75798309.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定