我正在Go中构建一个简单的CLI工具,它充当各种密码存储(Chef Vault,Ansible Vault,Hashicorp Vault等)的包装器。这部分是为了熟悉Go。
在处理这个问题时,我遇到了一个我正在编写测试的情况,发现我需要为许多事情创建Dim Value1
Dim Value2
Dim Value3
'Etc.
GetSomeValues MyConnectionString, "TABLE_NAME", "ROW_ID = 1", _
"FIELD_NAME_1", Value1, _
"FIELD_NAME_2", Value2, _
"FIELD_NAME_3", Value3, ... Etc.
,只是为了能够模拟依赖项。因此,为了测试,一个相当简单的实现似乎有一堆抽象。
但是,我最近在阅读The Go Programming Language并找到了一个例子,他们以下列方式模拟了它们的依赖关系。
interfaces
因此,为了测试,我们将这个具体实现存储在一个变量中,然后我们可以在测试中重新声明变量并让它返回我们需要的。
否则,我会为此创建func Parse() map[string]string {
s := openStore()
// Do something with s to parse into a map…
return s.contents
}
var storeFunc = func openStore() *Store {
// concrete implementation for opening store
}
// and in the testing file…
func TestParse(t *testing.T) {
openStore := func() {
// set contents of mock…
}
parse()
// etc...
}
(尽管目前只有一个实现)并将其注入interface
方法。这样,我们可以模拟它进行测试。
所以我的问题是:每种方法的优点和缺点是什么?何时更适合为模拟目的创建接口,而不是将具体函数存储在变量中以便在测试中重新声明?
答案 0 :(得分:1)
没有"正确的方式"回答这个问题。
说了这些之后,我发现interface
方法比定义函数变量并将其设置为测试更通用,更清晰。
以下是对原因的一些评论:
如果您需要模拟多个函数,则function variable
方法无法很好地扩展(在您的示例中,它只是一个函数)。
interface
更明确了注入函数/模块的行为,而不是最终隐藏在实现中的函数变量。
interface
允许您注入一个带有状态(结构)的类型,这可能对配置模拟行为很有用。
你当然可以依赖"函数变量"处理简单案例并使用"界面"对于更复杂的功能,但是如果你想保持一致并只使用一种方法我会选择"接口"。
答案 1 :(得分:1)
出于测试目的,我倾向于使用您描述的模拟方法而不是创建新接口。其中一个原因是,AFAIK有no direct ways to identify which structs implement an interface,如果我想知道模拟是否做得对,这对我来说很重要。
这种方法的主要缺点是变量本质上是一个包级别的全局变量(即使它未被导出)。因此,声明全局变量的所有缺点都适用。
在测试中,一旦测试完成,您肯定希望使用defer
将storeFunc
重新分配回其原始的具体实现。
var storeFunc = func *Store {
// concrete implementation for opening store
}
// and in the testing file…
func TestParse(t *testing.T) {
storeFuncOriginal := storeFunc
defer func() {
storeFunc = storeFuncOriginal
}()
storeFunc := func() {
// set contents of mock…
}
parse()
// etc...
}
顺便说一句,var storeFunc = func openStore() *Store
将无法编译。
答案 2 :(得分:0)
我以不同的方式解决问题。给定
function Parse(s Store) map[string] string{
// Do stuff on the interface Store
}
你有几个好处:
然而,这使得一些事情非常明显:Parse是一个可以附加到商店的功能,这很可能比解析商店更有意义。