使用stretchr/testify库比较具有切片字段的结构体时,可以忽略项目的顺序。

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

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 &quot;github.com/r3labs/diff/v2&quot;
changelog, err := diff.Diff(exp, act)
assert.NoError(t, err)
assert.Len(t, changelog, 0)

答案2

得分: 0

你可以使用 cmpopts 包IgnoreFields 函数来实现这个功能。你需要指定哪些结构体的哪些字段需要被忽略。

字段名可以是一个用点分隔的字符串(例如 &quot;Foo.Bar&quot;),用于忽略父结构体中嵌套的特定子字段。

下面是一个示例:

got, want := SomeFunc()
if !cmp.Equal(want, got, cmpopts.IgnoreFields(Struct1{}, &quot;Field1&quot;, &quot;Field2&quot;, &quot;Struct2.FieldA&quot;)) {
t.Errorf(&quot;SomeFunc() 不匹配&quot;)
}
英文:

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., &quot;Foo.Bar&quot;) 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{}, &quot;Field1&quot;, &quot;Field2&quot;, &quot;Struct2.FieldA&quot;)) {
t.Errorf(&quot;SomeFunc() mismatch&quot;)
}

huangapple
  • 本文由 发表于 2021年11月15日 18:07:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/69972598.html
匿名

发表评论

匿名网友

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

确定