英文:
Compare structs that have slice fields ignoring item order with stretchr/testify
问题
我有一个问题,我需要比较两个非常大的结构体(protobuf生成的),作为测试用例的一部分。这些结构体中包含多个嵌套的数组。下面是一个简化的示例,重现/演示了这个问题。
package pkg
import (
"github.com/stretchr/testify/assert"
"reflect"
"testing"
)
type structOne struct {
Foo string
Offs []*structTwo
}
type structTwo struct {
Identifier string
}
func Test_Compare(t *testing.T) {
exp := &structOne{
Foo: "bar",
Offs: []*structTwo{
{
Identifier: "one",
},
{
Identifier: "two",
},
{
Identifier: "three",
},
{
Identifier: "four",
},
},
}
act := &structOne{
Foo: "bar",
Offs: []*structTwo{
{
Identifier: "four",
},
{
Identifier: "three",
},
{
Identifier: "two",
},
{
Identifier: "one",
},
},
}
assert.Equal(t, exp, act) // 失败
assert.True(t, reflect.DeepEqual(exp, act)) // 失败
}
我尝试使用assert.Equal(t, exp, act)
和assert.True(t, reflect.DeepEqual(exp, act))
。我正在寻找一种比较这样的结构体的方法,最好不需要为所有对象创建自定义比较函数。
谢谢。
英文:
I have a problem where I need to compare two very large structs (protobuf generated) with each other, as part of a test-case. These structs have multiple nested arrays in them. Below is a simplified example that reproduces / demonstrates the problem.
package pkg
import (
"github.com/stretchr/testify/assert"
"reflect"
"testing"
)
type structOne struct {
Foo string
Offs []*structTwo
}
type structTwo struct {
Identifier string
}
func Test_Compare(t *testing.T) {
exp := &structOne{
Foo: "bar",
Offs: []*structTwo{
{
Identifier: "one",
},
{
Identifier: "two",
},
{
Identifier: "three",
},
{
Identifier: "four",
},
},
}
act := &structOne{
Foo: "bar",
Offs: []*structTwo{
{
Identifier: "four",
},
{
Identifier: "three",
},
{
Identifier: "two",
},
{
Identifier: "one",
},
},
}
assert.Equal(t, exp, act) // fails
assert.True(t, reflect.DeepEqual(exp, act)) // fails
}
I have tried using assert.Equal(t, exp, act)
and assert.True(t, reflect.DeepEqual(exp, act))
. I am looking for a way to compare such structs, preferably without having to create custom comparison functions for all of objects.
Thank you
答案1
得分: 3
你可以使用assert.ElementsMatch
来比较两个切片,无论元素的顺序如何。
ElementsMatch
断言指定的listA
(数组、切片等)与指定的listB
(数组、切片等)相等,忽略元素的顺序。如果有重复的元素,在两个列表中每个元素的出现次数应该相同。
然而,这仅适用于切片字段本身。如果你的结构体模型有几个字段,你可以逐个比较它们,并在切片上使用ElementsMatch
:
assert.Equal(t, exp.Foo, act.Foo)
assert.ElementsMatch(t, exp.Offs, act.Offs)
如果你的结构体有很多字段,你可以将切片的值重新赋给临时变量,将字段设置为nil
,然后进行比较:
expOffs := exp.Offs
actOffs := act.Offs
exp.Offs = nil
act.Offs = nil
assert.Equal(t, exp, act) // 比较没有 Offs 字段的完整结构体
assert.ElementsMatch(t, expOffs, actOffs) // 单独比较 Offs
另外,如果stretchr/testify
允许为用户定义的类型注册自定义比较器,或者检查对象是否实现了某个特定接口并调用该接口来测试相等性,那将更好:
if cmp, ok := listA.(Comparator); ok {
cmp.Compare(listB)
}
但我不知道是否有这样的功能。
作为替代,建议使用https://github.com/r3labs/diff
,你可以按如下方式使用它。默认情况下,忽略切片项的顺序。
// import "github.com/r3labs/diff/v2"
changelog, err := diff.Diff(exp, act)
assert.NoError(t, err)
assert.Len(t, changelog, 0)
英文:
You can use assert.ElementsMatch
to compare two slices irrespective of element ordering.
> ElementsMatch asserts that the specified listA(array, slice...) is equal to specified listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, the number of appearances of each of them in both lists should match.
However this applies only to the slice field itself. If your struct model has few fields, you can compare them one by one and use ElementsMatch
on the slice:
assert.Equal(t, exp.Foo, act.Foo)
assert.ElementsMatch(t, exp.Offs, act.Offs)
If your structs have a lot of fields, you can reassign the slice values to temp variables, nil
the fields out, and then compare:
expOffs := exp.Offs
actOffs := act.Offs
exp.Offs = nil
act.Offs = nil
assert.Equal(t, exp, act) // comparing full structs without Offs
assert.ElementsMatch(t, expOffs, actOffs) // comparing Offs separately
<hr>
It would be even better if stretchr/testify
allowed registering custom comparators for user-defined types, or checking if the objects implement a certain interface and call that to test equality
if cmp, ok := listA.(Comparator); ok {
cmp.Compare(listB)
}
but I'm not aware of such a feature.
<hr>
In alternative, https://github.com/r3labs/diff
was suggested, which you can use as such. The order or slice items is ignored by default.
// import "github.com/r3labs/diff/v2"
changelog, err := diff.Diff(exp, act)
assert.NoError(t, err)
assert.Len(t, changelog, 0)
答案2
得分: 0
你可以使用 cmpopts 包 的 IgnoreFields
函数来实现这个功能。你需要指定哪些结构体的哪些字段需要被忽略。
字段名可以是一个用点分隔的字符串(例如 "Foo.Bar"
),用于忽略父结构体中嵌套的特定子字段。
下面是一个示例:
got, want := SomeFunc()
if !cmp.Equal(want, got, cmpopts.IgnoreFields(Struct1{}, "Field1", "Field2", "Struct2.FieldA")) {
t.Errorf("SomeFunc() 不匹配")
}
英文:
You can do this by using IgnoreFields
function of the cmpopts package. You'd need to specify which fields from which structs would need to be ignored.
The name may be a dot-delimited string (e.g., "Foo.Bar"
) to ignore a specific sub-field that is embedded or nested within the parent struct.
Here's an example:
got, want := SomeFunc()
if !cmp.Equal(want, got, cmpopts.IgnoreFields(Struct1{}, "Field1", "Field2", "Struct2.FieldA")) {
t.Errorf("SomeFunc() mismatch")
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论