【问题标题】:Is it possible to mock a function imported from a package in golang?是否可以模拟从 golang 中的包导入的函数?
【发布时间】:2022-04-28 12:26:53
【问题描述】:

我有以下方法要测试,它使用从包中导入的函数。

import x.y.z

func abc() {
    ...
    v := z.SomeFunc()
    ... 
}

是否可以在 golang 中模拟 SomeFunc()

【问题讨论】:

  • 是的。只需将v 分配给您的模拟并使用v 而不是z.SomeFunc。可能您希望将v 设为全局(并重命名为zSomeFunc
  • 感谢沃尔克!!!

标签: unit-testing testing go mocking


【解决方案1】:

是的,通过简单的重构。创建一个函数类型的zSomeFunc 变量,用z.SomeFunc 初始化,并让你的包调用它而不是z.SomeFunc()

var zSomeFunc = z.SomeFunc

func abc() {
    // ...
    v := zSomeFunc()
    // ...
}

在测试中,您可以为 zSomeFunc 分配另一个函数,该函数在测试中定义,并执行测试想要它做的任何事情。

例如:

func TestAbc(t *testing.T) {
    // Save current function and restore at the end:
    old := zSomeFunc
    defer func() { zSomeFunc = old }()

    zSomeFunc = func() int {
        // This will be called, do whatever you want to,
        // return whatever you want to
        return 1
    }

    // Call the tested function
    abc()

    // Check expected behavior
}

查看相关/可能的重复项: Testing os.Exit scenarios in Go with coverage information (coveralls.io/Goveralls)

【讨论】:

  • 函数返回多个值时如何mock?
  • @user4002112 同样的方式。显然,用于模拟的函数也必须返回多个值。
  • 并发测试时不会出现竞态问题吗?
  • 这本质上是全局状态。可能会为一些微不足道的事情工作,但也可能会给继承此类代码的人带来严重的痛苦(即几个小时的调试损失)。在专业环境中,这不是一个好的模式。
  • 上周我不得不调试一个这样的案例。该测试在与所有其他测试一起运行时失败,但在单独运行时通过。全局状态是指全局可用且可以更改(已被其他测试更改)的任何变量或符号,包括 func-vars。这实际上是全局状态的定义。
【解决方案2】:

您可以这样做:

import "x/y/z"

var someFunc = z.SomeFunc

func abc() {
    ...
    v := someFunc()
    ... 
}

在你的测试文件中你会这样做。

func Test_abc() {
    someFunc = mockFunc
    abc()
}

但请确保以并发方式执行此操作,如果您有多个调用 abc 或设置 someFuncTestXxx 函数,则最好使用带有 someFunc 字段的 struct

【讨论】:

  • 值得注意的是,您可以使用-p 1go test 的参数来强制所有测试串行而不是并行运行。
  • 这本质上是全局状态。可能会为一些微不足道的事情工作,但也可能会给继承此类代码的人带来严重的痛苦(即几个小时的调试损失)。在专业环境中,这不是一个好的模式。
  • @Kaedys 对于这个答案,默认标志 ​​go test -parallel 1 确实有意义。然而,-p 1 不是 的同义词!前者是 goroutine 级别,后者是 pid 级别。
【解决方案3】:

拥有函数指针和猴子补丁就是其中之一。但是当你要模拟多个函数时,你将有一个数字函数指针,并且不必要地你必须只使用函数指针来调用函数。

最好和推荐的想法是拥有一个接口并将您的函数作为实现接口的结构的一部分。完成此操作后,您可以使用一些不错的 go 工具生成模拟。

我一直在用这个:

mockgen -source myModule.go -package myPackage -destination myModuleMock.go

您可以通过以下方式安装它:

go get github.com/golang/mock

【讨论】:

【解决方案4】:

mockcompose 使用一种允许您生成模拟类的方法,您可以指示mockcompose 包含您选择的依赖闭包(从其他包中导入的任何函数)。同时,它会生成带有本地覆盖的主题函数的克隆副本,以便您可以进行测试。您可以使用go generate 嵌入代码生成过程,因此请确保您的克隆副本始终与您的代码更改同步。

假设你有一个函数 functionThatUsesGlobalFunction 可以导入 Sprintf 在包中fmt

func functionThatUsesGlobalFunction(
    format string,
    args ...interface{},
) string {
    //
    // skip fansy logic...
    //

    // call out to a global function in fmt package
    return fmt.Sprintf(format, args...)
}

您的目标是测试 functionThatUsesGlobalFunctionSprintf 包中的 fmt 被嘲笑。

为此,您可以将go generate 配置为mockcompose,如下所示:

mocks.go

//go:generate mockcompose -n mockFmt -p fmt -mock Sprintf
//go:generate mockcompose -n mockJson -p encoding/json -mock Marshal
//go:generate mockcompose -n clonedFuncs -real "functionThatUsesGlobalFunction,fmt=fmtMock"
package clonefn

go generate mockcompose 将为您生成管道类,使您可以编写如下测试代码:

package clonefn

import (
    "testing"

    "github.com/stretchr/testify/mock"
    "github.com/stretchr/testify/require"
)

var fmtMock *mockFmt = &mockFmt{}

func TestClonedFuncs(t *testing.T) {
    assert := require.New(t)

    // setup function mocks
    fmtMock.On("Sprintf", mock.Anything, mock.Anything).Return("mocked Sprintf")

    // inside functionThatUsesMultileGlobalFunctions: fmt.Sprintf is mocked
    assert.True(functionThatUsesGlobalFunction_clone("format", "value") == "mocked Sprintf")
}

详情请关注this

【讨论】:

    【解决方案5】:

    虽然创建包级变量是一个可行的选择,但它也有一些限制。仅举几例:

    1. 不鼓励使用 t.Parallel() 运行并行测试,因为模拟函数的不同行为可能存在竞争条件。
    2. 这很危险,因为同一包中的未来开发人员可能会意外更新此全局变量的实现。

    另一种方法是将要模拟的方法作为参数传递给函数以启用可测试性。在我的例子中,我已经有很多客户调用这个方法,因此,我想避免违反现有的合同。所以,我最终创建了一个包装函数。

    例如:

    import (
     z "x.y.z"
    )
    
    //this should replicate the type of function z from x.y.z
    type operation func() resp
    
    func wrappedAbc(op operation) {
      ....
      resp := op()
      ....
    }
    
    func Abc() {
      wrappedAbc(z)
    }
    
    

    现在为了测试实际逻辑,您将测试对wrappedAbc 的调用而不是abc,并且您将传递一个模拟的operation。这将允许您测试所有业务逻辑,同时不违反与方法 Abc 的当前客户端的 API 合同。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-15
      • 2018-01-31
      • 2015-11-07
      • 1970-01-01
      • 2022-01-19
      • 1970-01-01
      相关资源
      最近更新 更多