【问题标题】:What is the preferred way to implement testing mocks in Go?在 Go 中实现测试模拟的首选方法是什么?
【发布时间】:2017-08-08 18:49:54
【问题描述】:

我正在用 Go 语言构建一个简单的 CLI 工具,它充当各种密码存储(Chef Vault、Ansible Vault、Hashicorp Vault 等)的包装器。这部分是作为熟悉 Go 的练习。

在处理这个问题时,我遇到了一种情况,我正在编写测试并发现我需要为很多事情创建interfaces,只是为了能够模拟依赖项。因此,为了测试,一个相当简单的实现似乎有一堆抽象。

但是,我最近在阅读 The Go Programming Language 并找到了一个示例,他们通过以下方式模拟了它们的依赖项。

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(尽管目前只有一个实现)并将其注入Parse 方法。这样,我们可以模拟它进行测试。

所以我的问题是:每种方法的优缺点是什么?什么时候更适合为模拟目的创建接口,而不是将具体函数存储在变量中以便在测试中重新声明?

【问题讨论】:

    标签: go


    【解决方案1】:

    出于测试目的,我倾向于使用您描述的模拟方法,而不是创建新接口。原因之一是,AFAIK,有 no direct ways to identify which structs implement an interface,如果我想知道模拟是否在做正确的事情,这对我来说很重要。

    这种方法的主要缺点是该变量本质上是一个包级别的全局变量(即使它未导出)。所以声明全局变量的所有缺点都适用。

    在您的测试中,一旦测试完成,您肯定希望使用deferstoreFunc 重新分配回其原始具体实现。

    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】:

      没有“正确的方法”来回答这个问题。

      话虽如此,我发现interface 方法比定义函数变量并为测试设置它更通用、更清晰。

      以下是一些关于原因的 cmets:

      • 如果您需要模拟多个函数(在您的示例中它只是一个函数),则 function variable 方法无法很好地扩展。

      • interface 更清楚地说明了注入函数/模块的行为,而不是最终隐藏在实现中的函数变量。

      • interface 允许您注入具有状态(结构)的类型,这可能有助于配置模拟的行为。

      当然,对于简单的情况,您可以依赖“函数变量”方法,而对于更复杂的功能,您可以使用“接口”,但如果您想保持一致并只使用一种方法,我会使用“接口” .

      【讨论】:

        【解决方案3】:

        我以不同的方式解决问题。给定

        function Parse(s Store) map[string] string{
          // Do stuff on the interface Store
        }
        

        你有几个优点:

        1. 您可以根据需要使用 mock 或 stub Store。
        2. 恕我直言,代码变得更加透明。仅签名就清楚地表明需要 Store 实现。并且代码不需要被打开商店的错误处理污染。
        3. 代码文档可以保持更简洁。

        但是,这很明显:Parse 是一个可以附加到 store 的函数,这很可能比解析 store 更有意义。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-11-10
          • 1970-01-01
          • 2013-10-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-05-17
          • 1970-01-01
          相关资源
          最近更新 更多