从一个包中导入的函数是否可以进行模拟?

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

Is it possible to mock a function imported from a package?

问题

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

import x.y.z

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

在Go语言中,是否可以模拟SomeFunc()函数?

英文:

I have the following method to test, which uses a function imported from a package.

import x.y.z

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

Is it possible to mock SomeFunc() in Go?

答案1

得分: 82

是的,可以通过简单的重构实现。创建一个函数类型的zSomeFunc变量,并用z.SomeFunc进行初始化,然后在你的包中调用该变量,而不是z.SomeFunc()

var zSomeFunc = z.SomeFunc

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

在测试中,你可以将另一个函数赋值给zSomeFunc,这个函数在测试中定义,并根据测试的需要执行相应的操作。

例如:

func TestAbc(t *testing.T) {
    // 保存当前函数并在最后恢复:
    old := zSomeFunc
    defer func() { zSomeFunc = old }()

    zSomeFunc = func() int {
        // 这个函数将被调用,你可以在这里执行任何操作,
        // 并返回任何你想要的结果
        return 1
    }

    // 调用被测试的函数
    abc()

    // 检查预期的行为
}

参考/可能的重复问题:
https://stackoverflow.com/questions/40615641/testing-os-exit-scenarios-in-go-with-coverage-information-coveralls-io-goverall/40801733#40801733

英文:

Yes, with a simple refactoring. Create a zSomeFunc variable of function type, initialized with z.SomeFunc, and have your package call that instead of z.SomeFunc():

var zSomeFunc = z.SomeFunc

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

In tests you may assign another function to zSomeFunc, one that is defined in tests, and does whatever the test wants it to.

For example:

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
}

See related / possible duplicate:
https://stackoverflow.com/questions/40615641/testing-os-exit-scenarios-in-go-with-coverage-information-coveralls-io-goverall/40801733#40801733

答案2

得分: 12

你可以这样做:

import "x/y/z"

var someFunc = z.SomeFunc

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

在你的测试文件中,你可以这样做:

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

但请确保以并发的方式进行操作,如果你有多个TestXxx函数调用abc或设置someFunc,最好使用一个带有someFunc字段的struct

英文:

One thing you can do is this:

import "x/y/z"

var someFunc = z.SomeFunc

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

And in your test file you would do this.

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

But make sure that you do this in a concurrent manner, if you have multiple TestXxx functions calling abc or setting someFunc you may be better of using a struct with a someFunc field.

答案3

得分: 5

使用函数指针和猴子补丁是其中一种方法。但是当你需要模拟多个函数时,你将会有多个函数指针,并且你必须通过函数指针来调用函数,这是不必要的。

更好且推荐的方法是创建一个接口,并将你的函数作为实现该接口的结构体的一部分。完成后,你可以使用一些适用于Go的好工具来生成模拟。

我一直在使用这个命令:

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

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

go get github.com/golang/mock
英文:

Having function pointer and monkey patching it is the one of doing it. But then when you multiple functions to mock, you will have a number function pointers and unnecessarily you will have to call function using the function pointer only.

The better and the recommended idea to have an interface and make your function part of the struct implementing the interface. Once done that, you can generate mocks using some nice tools available for go.

I have been using this:

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

You can install it by:

go get github.com/golang/mock

答案4

得分: 2

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

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

另一种方法是将你想要模拟的方法作为参数传递给函数,以实现可测试性。在我的情况下,我已经有很多客户端调用这个方法,因此我希望避免违反现有的契约,所以我最终创建了一个包装函数。

例如:

import (
 z "x.y.z"
)

// 这应该复制 x.y.z 中函数 z 的类型
type operation func() resp

func wrappedAbc(op operation) {
  ....
  resp := op()
  ....
}

func Abc() {
  wrappedAbc(z)
}

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

英文:

While creating a package level variable is a viable option, it comes with some restrictions. to name a few:

  1. It discourages running parallel tests using t.Parallel() as there can be race conditions on different behaviors of mocked function.
  2. it is dangerous as a future developer in the same package can accidentally update the implementation of this global variable.

another way is to pass along the methods you want to mock as arguments to the function to enable testability. In my case, I already had numerous clients calling this method and thus, I wanted to avoid violating the existing contracts. so, I ended up creating a wrapped function.

eg:

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

now for testing the actual logic, you will test calls to wrappedAbc instead of abc and you would pass it a mocked operation. this will allow you to test all the business logic while not violating API contract with current clients of method Abc.

答案5

得分: 1

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

假设你有一个函数functionThatUsesGlobalFunction,它导入了fmt包中的Sprintf

func functionThatUsesGlobalFunction(
	format string,
	args ...interface{},
) string {
	//
	// 跳过复杂的逻辑...
	//

	// 调用fmt包中的全局函数
	return fmt.Sprintf(format, args...)
}

你的目标是使用模拟的fmt包中的Sprintf来测试functionThatUsesGlobalFunction

为此,你可以使用以下配置将mockcomposego 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)

	// 设置函数的模拟
	fmtMock.On("Sprintf", mock.Anything, mock.Anything).Return("mocked Sprintf")

	// 在functionThatUsesMultileGlobalFunctions内部:fmt.Sprintf被模拟
	assert.True(functionThatUsesGlobalFunction_clone("format", "value") == "mocked Sprintf")
}

请查看这里获取详细信息。

英文:

mockcompose uses an approach that allows you to generate a mocking class, you can direct mockcompose to include your selected dependency closure (any imported functions from other packages). In the mean time, it generates a cloned copy of your subject function with local overrides so that you can test against. You can embed code generation process with go generate, therefore ensure your cloned copy is always in-sync with your code changes.

Say you have a function functionThatUsesGlobalFunction that imports
Sprintf in package 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...)
}

Your goal is to test functionThatUsesGlobalFunction with Sprintf in package fmt being mocked.

To do that, you can configure go generate with mockcompose as following:

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 will then generate plumbing classes for you, enable you to write test code as following:

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

Please checkout this for details.

huangapple
  • 本文由 发表于 2017年3月22日 20:52:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/42952191.html
匿名

发表评论

匿名网友

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

确定