golang生成自包含模拟对象工具插件库counterfeiter的使用

Golang生成自包含模拟对象工具插件库counterfeiter的使用

介绍

在为一个对象编写单元测试时,通常需要该对象协作者的假实现。在Go中,这样的假实现无法在运行时自动生成,手动编写可能相当繁琐。counterfeiter允许你为给定接口简单地生成测试替身。

支持的Go版本

counterfeiter遵循Go本身的支持策略

每个主要的Go版本会一直支持到有两个更新的主要版本发布为止。例如,Go 1.5一直支持到Go 1.7发布,Go 1.6一直支持到Go 1.8发布。我们会根据需要发布小版本修订(例如Go 1.6.1、Go 1.6.2等)来修复受支持版本中的关键问题,包括关键安全问题。

使用counterfeiter

步骤1 - 添加counterfeiter作为工具依赖

通过运行以下命令建立对counterfeiter的工具依赖:

go get -tool github.com/maxbrunsfeld/counterfeiter/v6

步骤2a - 添加go:generate指令

你可以在接口定义旁边(或不在旁边)添加指令,放在模块中的任何.go文件中。

package foo

//go:generate go tool counterfeiter . MySpecialInterface

type MySpecialInterface interface {
	DoThings(string, uint64) (int, error)
}

然后运行:

$ go generate ./...
Writing `FakeMySpecialInterface` to `foofakes/fake_my_special_interface.go`... Done

步骤2b - 添加counterfeiter:generate指令

如果你计划在一个包中有多个指令,考虑使用这个选项,因为它会大大加快速度。

package foo

// You only need **one** of these per package!
//go:generate go tool counterfeiter -generate

// You will add lots of directives like these in the same package...
//counterfeiter:generate . MySpecialInterface
type MySpecialInterface interface {
	DoThings(string, uint64) (int, error)
}

// Like this...
//counterfeiter:generate . MyOtherInterface
type MyOtherInterface interface {
	DoOtherThings(string, uint64) (int, error)
}

然后运行:

$ go generate ./...
Writing `FakeMySpecialInterface` to `foofakes/fake_my_special_interface.go`... Done
Writing `FakeMyOtherInterface` to `foofakes/fake_my_other_interface.go`... Done

步骤3 - 运行go generate

你可以在包含指令的目录中运行go generate,或者在模块的根目录中运行(以确保为模块中的所有包生成)。

$ go generate ./...

从shell调用counterfeiter

你可以使用以下命令在go模块中调用counterfeiter

$ go tool counterfeiter

USAGE
	counterfeiter
		[-generate>] [-o <output-path>] [-p] [--fake-name <fake-name>]
		[-header <header-file>]
		[<source-path>] <interface> [-]

生成测试替身

给定包的路径和接口名称,你可以生成一个测试替身。

package foo

type MySpecialInterface interface {
		DoThings(string, uint64) (int, error)
}
$ go tool counterfeiter path/to/foo MySpecialInterface
Wrote `FakeMySpecialInterface` to `path/to/foo/foofakes/fake_my_special_interface.go`

在测试中使用测试替身

实例化假对象:

import "my-repo/path/to/foo/foofakes"

var fake = &foofakes.FakeMySpecialInterface{}

假对象记录它们被调用的参数:

fake.DoThings("stuff", 5)

Expect(fake.DoThingsCallCount()).To(Equal(1))

str, num := fake.DoThingsArgsForCall(0)
Expect(str).To(Equal("stuff"))
Expect(num).To(Equal(uint64(5)))

你可以存根它们的返回值:

fake.DoThingsReturns(3, errors.New("the-error"))

num, err := fake.DoThings("stuff", 5)
Expect(num).To(Equal(3))
Expect(err).To(Equal(errors.New("the-error")))

为第三方接口生成测试替身

对于第三方接口,你可以使用替代语法<package>.<interface>指定接口,例如:

$ go tool counterfeiter github.com/go-redis/redis.Pipeliner

运行counterfeiter的测试

如果你想运行counterfeiter的测试(也许是因为你想贡献一个PR),你只需要运行scripts/ci.sh

贡献

如果你想贡献counterfeiter,请遵循以下步骤:

  1. 打开一个新的github issue,描述你的问题或用例
  2. 帮助我们理解你想如何修复或扩展counterfeiter
  3. 为你想要的行为编写一个或多个单元测试
  4. 为你正在处理的功能编写最简单的代码
  5. 尝试寻找重构的机会
  6. 避免编写没有单元测试覆盖的代码

许可证

counterfeiter是MIT许可的。


更多关于golang生成自包含模拟对象工具插件库counterfeiter的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang生成自包含模拟对象工具插件库counterfeiter的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Golang 模拟对象工具 Counterfeiter 使用指南

Counterfeiter 是一个用于 Go 语言的模拟对象生成工具,它可以自动为接口生成模拟实现,非常适合单元测试场景。

安装 Counterfeiter

go install github.com/maxbrunsfeld/counterfeiter/v6@latest

基本使用

1. 为接口生成模拟实现

假设我们有以下接口定义:

// 在文件 mypackage/myinterface.go 中
package mypackage

type MyInterface interface {
    DoSomething(int) string
    DoSomethingElse(string) (string, error)
}

运行以下命令生成模拟实现:

counterfeiter -generate mypackage/myinterface.go

这将生成一个 mypackagefakes/fake_my_interface.go 文件,其中包含 FakeMyInterface 结构体。

2. 在测试中使用模拟对象

package mypackage_test

import (
    "testing"

    . "github.com/onsi/ginkgo/v2"
    . "github.com/onsi/gomega"
    "github.com/yourproject/mypackage"
    "github.com/yourproject/mypackage/mypackagefakes"
)

var _ = Describe("MyService", func() {
    var (
        fakeMyInterface *mypackagefakes.FakeMyInterface
        myService       *mypackage.MyService
    )

    BeforeEach(func() {
        fakeMyInterface = new(mypackagefakes.FakeMyInterface)
        myService = mypackage.NewMyService(fakeMyInterface)
    })

    It("should call DoSomething with correct parameters", func() {
        fakeMyInterface.DoSomethingReturns("fake result")
        
        result := myService.Execute(42)
        
        Expect(result).To(Equal("fake result"))
        Expect(fakeMyInterface.DoSomethingCallCount()).To(Equal(1))
        
        arg := fakeMyInterface.DoSomethingArgsForCall(0)
        Expect(arg).To(Equal(42))
    })
})

高级功能

1. 自定义返回值

// 设置返回值
fakeMyInterface.DoSomethingReturns("custom result")

// 设置多个返回值(对有返回error的方法)
fakeMyInterface.DoSomethingElseReturns("result", errors.New("error"))

// 使用回调函数动态返回值
fakeMyInterface.DoSomethingStub = func(arg int) string {
    if arg > 10 {
        return "big"
    }
    return "small"
}

2. 验证调用参数

// 验证调用次数
Expect(fakeMyInterface.DoSomethingCallCount()).To(Equal(1))

// 获取特定调用的参数
arg := fakeMyInterface.DoSomethingArgsForCall(0)
Expect(arg).To(Equal(42))

3. 生成命令选项

# 为特定接口生成模拟
counterfeiter . MyInterface

# 指定输出目录
counterfeiter -o ./testfakes mypackage/myinterface.go

# 在Go generate中使用
//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . MyInterface

最佳实践

  1. 保持模拟简单:只模拟必要的接口,避免过度模拟
  2. 明确命名:使用清晰的命名区分真实实现和模拟实现
  3. 验证交互:不仅要验证返回值,还要验证是否正确调用了依赖
  4. 重置状态:在测试之间重置模拟对象的状态

与其他工具集成

Counterfeiter 可以与 Ginkgo/Gomega 等测试框架完美配合:

It("should handle errors", func() {
    fakeMyInterface.DoSomethingElseReturns("", errors.New("failed"))
    
    _, err := myService.ExecuteElse("input")
    
    Expect(err).To(HaveOccurred())
    Expect(err).To(MatchError("failed"))
})

Counterfeiter 是 Go 生态中功能强大且易于使用的模拟工具,能够显著提高单元测试的编写效率和质量。

回到顶部