Go语言 可以模拟从包中导入的函数吗?

g0czyy6m  于 2023-01-28  发布在  Go
关注(0)|答案(5)|浏览(151)

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

import x.y.z

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

可以在Go语言中模拟SomeFunc()吗?

zsohkypk

zsohkypk1#

是的,通过一个简单的重构。创建一个函数类型的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
}

参见相关/可能重复:在Go语言中使用覆盖信息测试os. exit场景(coveralls.io/Goveralls)

k5hmc34c

k5hmc34c2#

你可以做的一件事是:

import "x/y/z"

var someFunc = z.SomeFunc

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

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

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

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

iswrvxsc

iswrvxsc3#

有函数指针和猴子修补它是这样做的一个。但是当你有多个函数要模仿时,你会有很多函数指针,不必要的,你将不得不只使用函数指针调用函数。
更好和推荐的想法是有一个接口,并使您的函数成为实现该接口的结构体的一部分。一旦这样做了,您就可以使用一些不错的工具生成mock。
我一直在用这个:

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

您可以通过以下方式进行安装:

go get github.com/golang/mock
mnemlml8

mnemlml84#

虽然创建包级变量是一个可行的选项,但它有一些限制。仅举几例:
1.它不鼓励使用t.Parallel()运行并行测试,因为模拟函数的不同行为可能存在争用条件。
1.这是危险的,因为同一包中的未来开发者可能意外地更新该全局变量的实现。
另一种方法是把你想模拟的方法作为参数传递给函数来实现可测试性。2在我的例子中,我已经有很多客户调用这个方法,因此,我想避免违反现有的契约。3所以,我最终创建了一个 Package 函数。
例如:

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合同。

ghg1uchk

ghg1uchk5#

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...)
}

您的目标是使用被模拟的包fmt中的Sprintf测试functionThatUsesGlobalFunction
为此,您可以使用mockcompose配置go generate,如下所示:
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以了解详细信息。

相关问题