如何在Golang中实现Python的mock ANY?

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

How do I implement Python's mock ANY in Golang

问题

我想要能够比较两个字典,同时忽略某些字段的值。在Python中,使用mock.ANY非常容易实现。

以下是Python代码示例:

from unittest.mock import ANY
actual = {'userName':'bob', 'lastModified':'2012-01-01'}
expected = {'userName':'bob', 'lastModified': ANY}
assert actual == expected

这里是我在Go语言中的尝试,利用了cmp包,该包允许你实现一个Equal方法(https://pkg.go.dev/github.com/google/go-cmp/cmp#Equal),但是它不起作用。我认为这是因为我的MatchAll结构体无法“赋值”给字符串。有没有办法解决这个问题?

以下是Go语言的代码示例:

package main

import (
	"fmt"

	"github.com/google/go-cmp/cmp"
)

type MatchAll string

func (m MatchAll) Equal(v string) bool {
	return true
}

func main() { 
	matchAll := MatchAll("bruh")
	actual := map[string]interface{}{
		"key": matchAll,
	}

	expected := map[string]interface{}{
		"key": "foobar",
	}

	fmt.Println(cmp.Equal(expected, actual))
}

希望对你有帮助!

英文:

I want to be able to compare two dictionaries while ignoring the value of some fields. This is extremely easy in Python with mock.ANY.

from unittest.mock import ANY
actual = {'userName':'bob', 'lastModified':'2012-01-01'}
expected = {'userName':'bob', 'lastModified': ANY}
assert actual == expected

Here's my attempt in Go that leverages cmp which allows you to implement a Equal method (https://pkg.go.dev/github.com/google/go-cmp/cmp#Equal) but it doesn't work. I believe it's because my MatchAll struct is not "assignable" to string. Is there anyway to get around this?

package main

import (
	"fmt"

	"github.com/google/go-cmp/cmp"
)

type MatchAll string

func (m MatchAll) Equal(v string) bool {
	return true
}

func main() { 
	matchAll := MatchAll("bruh")
	actual := map[string]any{
		"key": matchAll,
	}

	expected := map[string]any{
		"key": "foobar",
	}

	fmt.Println(cmp.Equal(expected, actual))
}

答案1

得分: 1

观察cmp代码,我认为问题在于在比较映射元素时,用于检查Equal方法存在性的类型是映射的元素类型(这里是any),而不是两个值的具体类型(这里是stringMatchAll)。由于any类型没有声明Equal方法,因此会回退到内置的比较方式。

作为替代方案,你可以尝试使用cmp.FilterValuescmp.Comparer函数来构建一个支持特殊的“等于任何值”的选项。例如,下面的代码似乎可以按预期工作:

package main

import (
	"fmt"

	"github.com/google/go-cmp/cmp"
)

type matchAllType struct{}

var matchAll = matchAllType{}

var matchAllOption = cmp.FilterValues(
	// 过滤函数:仅对使用此比较的值返回true。
	func(x, y any) bool {
		return x == matchAll || y == matchAll
	},
	// 比较函数:对过滤器接受的值对调用。
	cmp.Comparer(func(x, y any) bool { return true }),
)

func main() {
	actual := map[string]any{
		"key": matchAll,
	}

	expected := map[string]any{
		"key": "foobar",
	}

	fmt.Println(cmp.Equal(expected, actual, matchAllOption))
}

始终返回true的比较函数也可以替换为cmp.Ignore(),不过我不确定语义是否完全相同。至少在上面的示例中它是有效的。

还有一个选项是cmpopts.IgnoreMapEntries,可以按照以下方式使用,不过语义略有不同:

var matchAllOption = cmpopts.IgnoreMapEntries(func(_, v any) bool { return v == matchAll })

不同之处在于,使用此选项时,即使actual字典不包含与匹配键对应的条目,比较结果也会为true,因为内置的映射比较会测试“递归调用所有非被忽略的映射条目的Equal方法是否相等”,强调部分已添加。相比之下,之前的解决方案仍要求另一个映射包含具有相同键的某个值。

英文:

Looking at the cmp code, I believe the problem here is that when comparing map elements, the type that's used to check for the existence of the Equal method is the element type of the map (here, any), not the concrete type of one of the two values (here, string or MatchAll). Since the any type does not declare an Equal method, it falls back to the built-in comparisons.

As an alternative, you could potentially use the cmp.FilterValues and cmp.Comparer functions to construct an option that supports your special "equal-to-anything" value. For example, this would appear to work as intended:

package main

import (
	"fmt"

	"github.com/google/go-cmp/cmp"
)

type matchAllType struct{}

var matchAll = matchAllType{}

var matchAllOption = cmp.FilterValues(
	// Filter function: returns `true` only for values using this comparison.
	func(x, y any) bool {
		return x == matchAll || y == matchAll
	},
	// Comparison function: called for pairs of values the filter accepts.
	cmp.Comparer(func(x, y any) bool { return true }),
)

func main() {
	actual := map[string]any{
		"key": matchAll,
	}

	expected := map[string]any{
		"key": "foobar",
	}

	fmt.Println(cmp.Equal(expected, actual, matchAllOption))
}

The always-true comparison function could possibly be replaced with cmp.Ignore() as well, though I'm not entirely sure whether the semantics are exactly the same. It does work in the above example, at least.

There is also the cmpopts.IgnoreMapEntries option, which could be used as follows, but with slightly different semantics:

var matchAllOption = cmpopts.IgnoreMapEntries(func(_, v any) bool { return v == matchAll })

The difference is that with this option, the comparison would be true even if the actual dictionary did not contain an entry with a matching key at all, because the built-in map comparison tests whether "recursively calling Equal on all non-ignored map entries report equal", emphasis added. By contrast, the earlier solutions still require the other map to contain some value with the same key.

huangapple
  • 本文由 发表于 2023年7月29日 04:39:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/76790898.html
匿名

发表评论

匿名网友

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

确定