文章目录

  • 1.什么是单元测试
  • 2.单元测试的作用
  • 3.Go 如何写单元测试
  • 4.go test 命令
    • 4.1 简介
    • 4.2 示例
  • 5.快速生成单测代码
  • 6.看看单元测试覆盖率
  • 7.使用单测框架写单测
    • 7.1 testify + gomonkey
    • 7.2 goconvey + gomock
      • 7.2.1 goconvey
      • 7.2.2 gomock
  • 8.再谈 mock
    • 8.1 test doubles
    • 8.2 mock 与 stub
    • 8.3 不要滥用 mock
  • 9.小结
  • 参考文献

1.什么是单元测试

单元测试(Unit Testing),是指对软件中的最小可测试单元进行检查和验证。

对于单元测试中单元的含义,一般要根据实际情况去判定其具体含义,如 C 语言中单元指一个函数,Java 里单元指一个类,图形化的软件中可以指一个窗口或一个菜单等。总的来说,单元就是人为规定的最小被测功能模块。

在 Go 中,一般指对函数的单元测试。

2.单元测试的作用

单元测试可以检查我们的代码能否按照预期执行,来提升代码质量。

通过单元测试,我们可以设置多个测试用例,执行要测试的函数,判断是否符合预期。尽可能达保证函数功能没有问题,或者出现我们预知的错误。一次书写测试用例,随着代码一起永久保留,来验证函数功能,这就是单元测试的好处。

3.Go 如何写单元测试

Go 本身对自动化测试非常友好,并且有许多优秀的第三方测试框架,非常好上手。

首先看一下 Go 官方的 testing 包。

要编写一个测试文件,需要创建一个名称以 _test.go 结尾的文件,该文件包含 TestXxx 函数。将该文件放在与被测试文件相同的包中,该文件将被排除在正常的程序包之外,但在运行 go test 命令时将被包含。

测试函数的签名必须接收一个指向 testing.T 类型的指针,并且不能返回任何值,函数名必须以 Test 开头,建议后跟要测试的函数名。

记得一定要先看一下 Package testing 的官方文档!

下面利用 Go 官方 testing 包给出一个示例。

代码目录结构:

gotest- go.mod- go.sum- main.go- hello- hello.go- hello_test.go

main.go:

package mainimport ("main/hello"
)func main() {fmt.Println(hello.Hello())
}

hello.go:

package hellofunc Hello() string {return "Hello world"
}

我们新建一个单测文件 hello_test.go,为函数 Hello() 添加单元测试。

hello_test.go:

package helloimport "testing"func TestHello(t *testing.T) {got := Hello()want := "Hello world"if got != want {t.Errorf("got %q want %q", got, want)}
}

进入 hello 目录执行命令go test,或指定目录 go test ./hello,所有以_test.go结尾的源码文件内以 Test 开头的函数会自动被执行。

输出:

PASS
ok      main/hello      0.173s

我们也可以加上-v选项,输出单测执行的详细过程:

go test -v
=== RUN   TestHello
--- PASS: TestHello (0.00s)
PASS
ok      main/hello      0.176s

该结果,表示单测通过了,返回的值与我们预期的值是相同的。

现在尝试把预期结果修改一下:

want := "Hello fuck"

测试结果:

D:\code\gotest\hello>go test -v
=== RUN   TestHellohello_test.go:9: got "Hello world" want "Hello fuck"
--- FAIL: TestHello (0.00s)
FAIL
exit status 1
FAIL    main/hello      0.127s

此时提示测试不通过,得到的值与预期的值不相同。

至此,利用 Go 官方包 testing 便完成了一个简单的单元测试,我们可以进行正确或错误的测试。

4.go test 命令

4.1 简介

go test 是 Go 用来执行测试函数(test function)、基准函数(benchmark function)和示例函数(example function)的命令。

行 go test 命令,它会在 *_test.go 文件中寻找 test、benchmark 和 example 函数来执行。测试函数名必须以 TestXXX 开头,基准函数名必须以 BenchmarkXXX 开头,示例函数必须以 ExampleXXX 开头。

// test 测试函数
func TestXXX(t *testing.T) { ... }// benchmark 基准函数
func BenchmarkXXX(b *testing.B) { ... }// examples 示例函数,其相关命名方式可以查看第一篇文章
func ExamplePrintln() {Println("The output of\nthis example.")// Output: The output of// this example.
}

关于更多测试函数的信息请查看go help testfunc

go test 有很多选项,主要分为三类:一类是控制构建,一类是控制测试行为,一类是用于状态分析。

这里只列出常用的用于控制测试行为的选项:

-bench regexp只执行匹配对应正则表达式的 benchmark 函数,如执行所有性能测试 "-bench ." 或 "-bench=."
-benchtime t对每个 benchmark 函数运行指定时间。如 -benchtime 1h30,默认值为 1s。特殊语法 Nx 表示运行基准测试 N 次(如 -benchtime 100x)
-run regexp只运行匹配对应正则表达式的 test 和 example 函数,例如 "-run Array" 那么就执行函数名包含 Array 的单测函数
-cover开启测试覆盖率
-v显示测试的详细命令

详见官方文档 Testing flags。

4.2 示例

假设在文件 add.go 有一个被测试函数

package hellofunc Add(a, b int) int {return a + b
}
  • 测试函数(test function)

在测试文件 add_test.go 添加一个单元测试函数 TestAdd:

package hellofunc TestAdd(t *testing.T) {sum := Add(5, 5)if sum == 10 {t.Log("the result is ok")} else {t.Fatal("the result is wrong")}
}

比如使用 -run 来运行指定单元测试函数,发现只运行了 TestAdd 测试函数。

go test -v -run TestAdd main/hello
=== RUN   TestAddadd_test.go:16: the result is ok
--- PASS: TestAdd (0.00s)
PASS
ok      main/hello      0.170s
  • 基准函数(benchmark function)

添加一个性能测试函数 BenchmarkAdd:

package hellofunc BenchmarkAdd(b *testing.B) {for n := 0; n < b.N; n++ {Add(1, 2)}
}

运行指定基准函数:

go test -bench BenchmarkAdd main/hello
goos: windows
goarch: amd64
pkg: main/contain
cpu: Intel(R) Core(TM) i7-9700 CPU @ 3.00GHz
BenchmarkAdd-8          1000000000               0.2333 ns/op
PASS
ok      main/contain    0.586s
  • 示例函数(example function)
package hellofunc ExampleAdd() {fmt.Println(Add(1, 2))// Output: 3
}

运行指定示例函数:

go test -v -run ExampleAdd main/contain
=== RUN   ExampleAdd
--- PASS: ExampleAdd (0.00s)
PASS
ok      main/contain    (cached)

注意: 示例函数类似于测试函数,但不是使用 *testing.T 来报告成功或失败,而是将输出打印到 os.Stdout。如果示例函数中的最后一条注释以“Output:”开头,则将输出与注释进行精确比较(参见上面的示例)。如果最后一条注释以“Unordered output:”开头,则将输出与注释进行比较,但忽略行的顺序。编译了一个没有此类注释的示例函数,会被编译但不会被执行。如果在“Output:”之后没有文本,示例函数仍会被编译并执行,并且预期不会产生任何输出。

5.快速生成单测代码

实际上,不同函数的单测代码虽然逻辑不同,但结构是一样的,长得非常相似,因此重复的代码可以使用工具来生成,不用手动繁琐地重复书写。

常用的 IDE,比如 GoLand 或 VS Code,都自带了生成单元测试代码的工具。以 GoLand 为例,可以快速为函数、文件或包生成测试代码。在源码文件中”右键函数名 > Generate… > Test for function“ 便可以快速生成对应函数的单测代码模板,然后我们在生成的模板代码中添加具体的测试用例即可。

使用该方法,为上面的 Hello() 函数生成的测试代码如下:

func TestHello(t *testing.T) {tests := []struct {name stringwant string}{// TODO: Add test cases.}for _, tt := range tests {t.Run(tt.name, func(t *testing.T) {if got := Hello(); got != tt.want {t.Errorf("Hello() = %v, want %v", got, tt.want)}})}
}

我们在注释处添加测试用例即可,以表驱动的方式完成测试用例的书写与执行,非常快捷方便。

6.看看单元测试覆盖率

写好测试后,可以利用 Go 自带的工具 test coverage 查看一下单元测试覆盖率。

测试覆盖率是一个术语,用于统计通过运行程序包的测试多少代码得到执行。 如果执行测试函数导致 80%的语句得到了运行,则测试覆盖率为 80%。

我们来试一下。

D:\code\gotest>go test -v -cover ./hello
=== RUN   TestHello
--- PASS: TestHello (0.00s)
=== RUN   TestAddhello_test.go:16: the result is ok
--- PASS: TestAdd (0.00s)
PASS
coverage: 100.0% of statements
ok      main/hello      0.154s  coverage: 100.0% of statements

可以看到,目录 hello 下的所有单测都通过了,且报告覆盖率为 100%.

7.使用单测框架写单测

学会使用 Go 官方 testing 包写单元测试是远远不够的,因为在实际项目开发中,面对复杂的逻辑判断,繁多的测试用例,网络IO调用等,都加大了单测编写与管理的难度,此时我们需要用到更好的测试框架来完成单测的书写。

7.1 testify + gomonkey

这里推荐使用 testify + gomonkey 开源库来完成 Go 单元测试的书写。

testify 提供了三个能力:

  • 断言能力:assert 和 require
  • 单测组织:suite
  • mock

其中 mock 的功能,但我们不使用,因为有更好用的 mock 库。

suite 提供了几个接口,可以用来做初始化和释放的动作

方法 说明
SetupSuite 在整个suite开始时执行一次
SetupTest 在每个测试函数开始之前执行
BeforeTest 在每个测试函数开始之前执行,类似SetupTest,带suite和测试函数名
AfterTest 在每个测试函数结束之后执行,类似TearDownTest,带suite和测试函数名
TearDownTest 在每个测试函数结束之后执行
TearDownSuite 在整个suite结束时执行一次

gomonkey 主要用于提供 mock 能力。

说到 mock,其本意是模拟,就是对一些不想执行的函数,比如有网络 IO 或对 DB 有写入的函数,因为测试环境网络不通或不想执行单测而向 DB 写入数据,都可以将其 mock 住,写一个替代函数。执行单测的时候会调用这个替代函数,相当于替代函数模拟了原函数。

下面使用 testify + gomonkey 给出使用示例。

先改造一下 Hello() 和 Add() 函数。

// Hello 返回 Hello world 和写入 DB 的值
func Hello(a, b int) string {return fmt.Sprintf("Hello world and %v", Add(a, b))
}// Add 将 a b 和写入 DB 并返回
func Add(a, b int) int {sum := a + b// 省略的 DB 操作return sum
}

我们为 Hello() 函数添加单元测试并 mock 住 Add() 函数。

package helloimport ("testing""github.com/agiledragon/gomonkey""github.com/stretchr/testify/assert""github.com/stretchr/testify/suite"
)// TestSuiteHello 测试套件
type TestSuiteHello struct {suite.Suitepatches []*gomonkey.Patches
}// SetupTest 测试套件初始化
func (suite *TestSuiteHello) SetupTest() {// mock Add 函数p := gomonkey.ApplyFunc(Add, func(a, b int) int {// 不执行DB写入,只返回加和结果return a + b})suite.patches = append(suite.patches, p)
}// TearDownSuite 测试套件析构
func (suite *TestSuiteHello) TearDownSuite() {for _, p := range suite.patches {p.Reset()}
}// TestHelloSuite 启动测试套件
func TestHelloSuite(t *testing.T) {suite.Run(t, new(TestSuiteHello))
}// TestHello hello 函数单测
func (suite *TestSuiteHello) TestHello() {type args struct {a intb int}tests := []struct {name stringargs argswant string}{{"testcase1", args{1, 2}, "Hello world and 3"},{"testcase2", args{3, 4}, "Hello world and 7"},{"testcase3", args{5, 6}, "Hello world and 11"},}for _, t := range tests {res := Hello(t.args.a, t.args.b)assert.Equal(suite.T(), t.want, res, t.name)}
}

自定义的测试套件 TestSuiteHello,可以添加多个以 Test 开头的单测,将被一一执行。

执行单元测试:

D:\code\gotest>go test -v ./hello
=== RUN   TestHelloSuite
=== RUN   TestHelloSuite/TestHello
--- PASS: TestHelloSuite (0.00s)--- PASS: TestHelloSuite/TestHello (0.00s)
PASS
ok      main/hello      0.149s

测试通过。

7.2 goconvey + gomock

7.2.1 goconvey

如果觉得上面使用表驱动的方式来管理测试用例太过简单,不够丰富,那么可以使用单测框架 goconvey 和 Go 官方提供的 mock 框架 gomock 来完成单测的开发。

goconvey 的单测组织形式更富逻辑性和结构化,更有可读性和可维护性。此外还具有丰富的断言函数,Web 界面,是极其常用的测试框架。

goconvey 的断言以 So 函数包裹,常用的断言有 ShouldEqual,ShouldBeNil,ShouldResemble(就是递归比较,类似 reflect.DeepEqual)。自定义断言也比较方便,实现 assertion 函数就可以。

goconvey 还可以无限嵌套,将相关的用例集合在一起,类似于:

import("testing". "github.com/smartystreets/goconvey/convey"
)func TestFoo(t *testing.T) {Convey("测试用例 1", t, func() {Convey("测试用例 1.1", func() {        //第 2 层及其以上不需要传递 testing.T 变量// ...})Convey("测试用例 1.2", func() {// ...})}
}

使用 goconvey 改写上面 TestHello()。

package helloimport ("testing""github.com/smartystreets/goconvey/convey"
)func TestHello(t *testing.T) {convey.Convey("test case 1", t, func() {s := Hello(1, 2)convey.So(s, convey.ShouldEqual, "Hello world and 3")})convey.Convey("test case 2", t, func() {s := Hello(3, 4)convey.So(s, convey.ShouldEqual, "Hello world and 7")})convey.Convey("test case 3", t, func() {s := Hello(5, 6)convey.So(s, convey.ShouldEqual, "Hello world and 11")})
}

提一嘴,goconvey 的作者又搞了一个新的框架 gunit,相关文章有 A History of Testing in Go at SmartyStreets 及其译文。gunit 风格更像 testify,不过 star 数不到 1K,感兴趣的同学可以了解一下。

7.2.2 gomock

gomock 是 Go 官方提供的 mock 框架,同时还提供了 mockgen 工具用来辅助生成测试代码。

其特点为:

  • 基于接口
  • 能够与 Golang 内置的 testing 包良好集成

何为基于接口,一开始不太好理解它的用法,先来看看一段业务代码。

代码结构:

- main.go
- db- db.go
//
// db.go
//package dbimport ("log"
)// OrderDBI 定义了一个订单接口,有一个获取名称的方法
type OrderDBI interface {GetName(orderid int) string
}// OrderInfo 定义结构体
type OrderInfo struct {orderid int
}// GetName 实现接口的方法 GetName
func (order OrderInfo) GetName(orderid int) string {log.Println("原本应该连接数据库去取名称")return "foo"
}//
// main.go
//package mainimport ("fmt""main/db"
)func main() {// 创建接口实例var orderDBI db.OrderDBIorderDBI = new(db.OrderInfo)// 调用方法,返回名称ret := orderDBI.GetName(1)fmt.Println("取到的用户名:", ret)
}

运行这段代码可以得到“取到的用户名:foo”。

假设这个 GetName 是需要连接数据库去取用户名,那我们想针对 GetName 写单测的话,就要真实连接一个数据库才行,意味着在任何一台电脑上想运行单测代码都必须要依赖数据库。

此时我们可以使用 gomock 将其 mock 住,提前定义好返回内容。

首先,我们需要预先将 mockgen 安装好,可以通过以下命令安装:

go install github.com/golang/mock/mockgen

安装好后,我们使用 mockgen 来 mock 前面的 Add() 函数。

mockgen -source=./db/db.go -destination=./db/db_mock.go -package=db

-source 需要 mock 的源文件
-destination 生成的 mock 文件路径
-package 所属包

务必开启 Modules 管理,不然使用 mockgen 可能会出现这样的提示Loading input failed: Source directory is outside GOPATH

命令执行成功后会生成指定的 mock 文件,点进去查阅一下,有一个EXPECT()需要重点留意一下。

// Code generated by MockGen. DO NOT EDIT.
// Source: ./db/db.go// Package db is a generated GoMock package.
package dbimport (reflect "reflect"gomock "github.com/golang/mock/gomock"
)// MockOrderDBI is a mock of OrderDBI interface.
type MockOrderDBI struct {ctrl     *gomock.Controllerrecorder *MockOrderDBIMockRecorder
}// MockOrderDBIMockRecorder is the mock recorder for MockOrderDBI.
type MockOrderDBIMockRecorder struct {mock *MockOrderDBI
}// NewMockOrderDBI creates a new mock instance.
func NewMockOrderDBI(ctrl *gomock.Controller) *MockOrderDBI {mock := &MockOrderDBI{ctrl: ctrl}mock.recorder = &MockOrderDBIMockRecorder{mock}return mock
}// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockOrderDBI) EXPECT() *MockOrderDBIMockRecorder {return m.recorder
}// GetName mocks base method.
func (m *MockOrderDBI) GetName(orderid int) string {m.ctrl.T.Helper()ret := m.ctrl.Call(m, "GetName", orderid)ret0, _ := ret[0].(string)return ret0
}// GetName indicates an expected call of GetName.
func (mr *MockOrderDBIMockRecorder) GetName(orderid interface{}) *gomock.Call {mr.mock.ctrl.T.Helper()return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetName", reflect.TypeOf((*MockOrderDBI)(nil).GetName), orderid)
}

MockOrderDBIMockRecorder 和 MockOrderDBI 都实现了 interface 的方法。前者用来记录预期的行为,后者匹配并执行预期的行为。

下面是一个 mock 的执行流程。

gomock 的核心结构就是 Call,expectedCalls 是 call 的集合。

Call 对象有一系列的方法来表示预期的行为。

调用方法 说明
After(preReq Call) *Call 指定执行顺序
AnyTimes() *Call 允许调用次数为 0 次或更多次
Do(f interface{}) *Call 指定匹配时执行的操作
Return(rets …interface{}) *Call 模拟返回值
DoAndReturn(f interface{}) *Call 指定匹配是执行的操作,并且模拟返回值
MaxTimes(n int) *Call 匹配到的方法最多可以被调用次数n 次
MinTimes(n int) *Call 匹配到的方法最少要被调用次数n 次
SetArg(n int, value interface{}) *Call 设置第n个参数的值为value
String() String 返回其字符串形式
Times(n int) *Call 匹配到的方法必须被调用次数 n 次

另外一个就是 Call 的入参匹配规则。

匹配方法 说明
Any() Matcher 始终匹配
AssignableToTypeOf(x interface{}) Matcher 如果入参可赋值给目标,则匹配,使用的是reflect.Type的AssignableTo
Eq(x interface{}) Matcher 相等则匹配
Nil() Matcher 为 nil 则匹配
Not(x interface{}) Matcher 匹配条件取反

接下来新建一个 db_test.go,给出一个使用示例。

func TestGetName(t *testing.T) {// 新建一个 mockControllerctrl := gomock.NewController(t)// 断言 DB.GetName() 方法是否被调用defer ctrl.Finish()// mock 接口mock := NewMockOrderDBI(ctrl)// 模拟传入值与预期的返回值mock.EXPECT().GetName(gomock.Eq(1225)).Return("foo")//前面定义了传入值与返回值//在这里if v := mock.GetName(1225); v != "foo" {t.Fatal("expected foo, but got", v)}else{log.Println("通过 mock 取到的 name:", v)}
}

执行上面的单测输出:

go test -v ./db
=== RUN   TestGetName
2022/02/05 14:24:58 通过mock取到的name: foo
--- PASS: TestGetName (0.00s)
PASS
ok      main/db 0.386s

可以看到测试通过了,与我们预期的值相符。是不是突然 Get 到了 GoMock 的用法~

8.再谈 mock

我们对不需要或无法执行单测时运行的函数将其 mock 住,模拟返回一个假值。谈到 mock,我们有必要说一下与 mock 相关的概念。。

8.1 test doubles

在《xUnit Test Patterns》一书中,作者首次提出 test doubles(测试替身)的概念。我们常挂在嘴边的 mock 只是其中一种,而且是最容易与 stub(打桩)混淆的一种。在前文中对 gomonkey 的介绍,你可以注意到了,我没有使用 mock,全部是 stub。是的,gomonkey 不是mock工具,只是一个高级打桩的工具,适配了我们大部分的使用场景。

测试替身,共有五种:可以参考这篇翻译《xUnit Test Patterns》学习笔记6 - Test Double 。

  • Dummy Object
    用于传递给调用者但是永远不会被真实使用的对象,通常它们只是用来填满参数列表

  • Test Stub
    Stubs通常用于在测试中提供封装好的响应,譬如有时候编程设定的并不会对所有的调用都进行响应。Stubs也会记录下调用的记录,譬如一个email gateway就是一个很好的例子,它可以用来记录所有发送的信息或者它发送的信息的数目。简而言之,Stubs一般是对一个真实对象的封装

  • Test Spy
    Test Spy像一个间谍,安插在了SUT内部,专门负责将SUT内部的间接输出(indirect outputs)传到外部。它的特点是将内部的间接输出返回给测试案例,由测试案例进行验证,Test Spy只负责获取内部情报,并把情报发出去,不负责验证情报的正确性

  • Mock Object
    针对设定好的调用方法与需要响应的参数封装出合适的对象

  • Fake Object
    Fake 对象常常与类的实现一起起作用,但是只是为了让其他程序能够正常运行,譬如内存数据库就是一个很好的例子。

8.2 mock 与 stub

mock 和 stub 应该是最容易混淆的,而且习惯上我们统一用 mock 去形容模拟返回的能力,习惯成自然,也就把 mock 常挂在嘴边了。

就我的理解,stub 可以理解为 mock 的子集,mock 更强大一些:

  • mock 可以验证实现过程,验证某个函数是否被执行,被执行几次
  • mock可以依条件生效,比如传入特定参数,才会使mock效果生效
  • mock可以指定返回结果
  • 当mock指定任何参数都返回固定的结果时,它等于stub

只不过,Go 的 mock 工具 gomock 只基于接口生效,不适合大部分项目,而 gomonkey 的 stub 覆盖了大部分的使用场景。

关于 mock 和 stub 的资料还可参考 Mocks Aren’t Stubs及其译文。

关于 mock 框架,这里给出一个简单的对比说明。

框架 类型 特点 资料
gomock stub 1.由Golang官方开发维护的测试框架,实现了较为完整的基于 interface 的 mock 功能;
2.提供相应工具(mockgen)生成单测代码提高单测书写效率。
golang/mock
gomokey stub 1. 支持对于函数、方法、接口、局部变量、全局变量、调用序列等插桩;
2. 不支持异包私有函数 mock。
Monkey Patching in Go
gomonkey底层原理剖析.pdf

8.3 不要滥用 mock

mock 虽好,切勿贪杯。

两个门派。

约从 2004-2005 年间,江湖上形成两大门派:经典测试驱动开发派 和 mockist(mock极端派)。

先说 mockist。他主张将被测函数 所有 调用的外面函数,全部 mock。也即,只关注被测函数自己的一行行代码,只要调用其他函数,全都 mock 掉,用假数据来测试。

再说经典测试驱动开发派,他们主张不要滥用 mock,能不 mock 就不 mock,被测单元也不一定是具体的一个函数,可能是多个函数,串起来。必要的时候再 mock。

两个门派相争多年,理论各有利弊,至今仍然共存。存在即合理。比如 mockist,使用了过多的 mock,无法覆盖函数接口,这部分又是很容易出错的;经典派,串的太多,又被质疑是集成测试。

对于我们实际应用,不必强制遵从某一派,结合即可,需要的时候 mock,尽量少 mock,不用纠结。

什么时候适合 mock?

如果一个对象具有以下特征,比较适合使用 mock 对象:

  • 该对象提供非确定的结果(比如当前的时间或者当前的温度)
  • 对象的某些状态难以创建或者重现(比如网络错误或者文件读写错误)
  • 对象方法上的执行太慢(比如在测试开始之前初始化数据库)
  • 该对象还不存在或者其行为可能发生变化(比如测试驱动开发中驱动创建新的类)
  • 该对象必须包含一些专门为测试准备的数据或者方法(后者不适用于静态类型的语言,流行的 mock 框架不能为对象添加新的方法,stub 是可以的)。

因此,不要滥用 mock(stub),当被测方法中调用其他方法函数,第一反应应该走进去串起来,而不是从根部就 mock掉了。

关于 mock 的使用问题,推荐阅读肖鹏的《mock七宗罪》。

9.小结

关于单元测试,本文从 0 到 1 讲解了 Go 如何编写测试用例,熟练掌握 Golang 中单元测试的书写是一位合格 gopher 的必备技能。

使用单测框架编写单测,如 testify + gomonkey 或 goconvey + gomock 均可,可根据实际场景选择使用。关于其他的单测框架,感兴趣的你可自行了解,比如 mock 框架还有 monkey 和 gostub。


参考文献

GoLang快速上手单元测试(思想、框架、实践)
gomonkey 1.0 正式发布!
Package testing 中文文档
《xUnit Test Patterns》学习笔记6 - Test Double
Mocks Aren’t Stubs
Mock并非Stub(翻译)
Golang go 命令

Go 单元测试从 0 到 1相关推荐

  1. c语言 单元测试工具 免费下载,雨田单元测试系统(C语言单元测试) 1.0官方版

    雨田单元测试系统(C语言单元测试)是一款适用于C语言文件的单元测试和集成测试系统.可以大幅度提高对C语言测试效率,从而提供软件质量. 雨田单元测试系统介绍 雨田单元测试系统可以针对c语言程序文件进行单 ...

  2. ng-notadd 0.17.1 发布,基于 Angular 的企业级中后台

    中文说明: 支持后台生成截图 修改步进组件的表单字段和按钮样式 修改json-schema-form组件布局样式 修改步进器组件布局样式 技术栈 Typescript Angular Material ...

  3. ng-notadd 0.10.1,基于 Angular7 和 material2 的中后台解决方案

    更新内容 修复 scss 左侧导航栏美化 修复导航栏 2px 间隔问题 技术栈 Typescript Angular Material2 rxjs Graphql 相关链接 项目地址 DEMO ng- ...

  4. linux下使用gtest框架进行c/c++单元测试

    原文地址linux下使用gtest框架进行c/c++单元测试 前言 google test(以下简称gtest)是谷歌的开源C++单元测试框架,用来做c/c++的单元测试比较方便.下面对于它在linu ...

  5. 玩转Spring JUnit+mockito+powermock单元测试(使用详解)

    说明:请耐心看完... Spring中执行单元测试,最麻烦的就是解决Bean的定义以及注入的问题.最开始使用Spring的上下文初始化进行测试,开头是这样的: @RunWith(SpringJUnit ...

  6. 估算带卷积核二分类0,3的网络的收敛时间和迭代次数

    制作一个网络分类minst的0和3求出这网络的迭代次数曲线表达式n(δ),和准确率表达式p-max(δ),用预期准确率去估算n,并推算需要的时间. 将minst的28*28的图片缩小到9*9,网络用一 ...

  7. python答辩结束语_Beta答辩总结

    前言 队名:拖鞋旅游队 项目的链接与宣传 项目总结 原计划 实现功能 预期完成程度 上传照片 完美实现 照片信息标注在地图上 对于有地理信息的照片能够较为精确的定位 足迹地图可视化 能够用颜色区分出到 ...

  8. 福大软工1816 · 第七次作业 - 需求分析报告之拖鞋旅游队

    [组长博客链接] 031602428 苏路明 [计划安排] 阶段 主要任务 时间 任务内容 1 项目选题 09.22 - 10.10 确定选题内容,收集用户需求,明确定位,竞品分析,选题报告 2 需求 ...

  9. package.json和bower的参数解释

    package.json和bower的参数解释 一.package.json解释: package.json是用来声明项目中使用的模块, 这样新的环境部署时,只要在package.json文件所在的目 ...

最新文章

  1. 打破 Google 破坏性的搜索垄断
  2. UIMenuController的使用,对UILabel拷贝以及定制菜单
  3. oracle 控制文件作用是什么,Oracle控制文件(controlfile)作用
  4. ubuntu18.04的ifconfig输出没有ip地址
  5. [BZOJ 1046] [HAOI2007] 上升序列 【DP】
  6. .NET 也有 Husky 了
  7. 51nod1551-集合交易【hall定理,最大权闭合图,网络流】
  8. Shader Model 版本与DirectX的关系(OpenGL对应的呢?)
  9. linux 下查看硬件信息
  10. 编写可靠shell脚本的八个建议
  11. sed 去掉最后一行_shell sed命令匹配替换删除最后第一行字符正则表
  12. 一个程序员父亲的呼吁:不要教你的孩子从小学编程!
  13. python批量修改文件夹名称,简洁快捷
  14. Python运行效率低的原因
  15. 码农与程序员两种不同称呼,有什么本质上的区别?
  16. 想成为游戏原画师需要哪些系统的学习?
  17. Android 音乐资源管理与播放
  18. [Oracle] 一个通过添加本地分区索引提高SQL性能的案例
  19. 【VMCloud云平台】SCAP(四)租户(二)
  20. (转!)利用Keras实现图像分类与颜色分类

热门文章

  1. PHP 绕过禁用函数漏洞的原理与利用分析
  2. 知名网络安全专家访谈记
  3. 谷歌8月更新修复50多个漏洞
  4. BZOJ5329:[SDOI2018]战略游戏(圆方树,虚树)
  5. 微信小程序框架——微信小程序前端开发工具
  6. PHP和MySQL Web开发从新手到高手,第9天-总结
  7. *** cannot be resolved or is not a field
  8. 注意:不能将文件名叫做email.py,否则会报 ImportError: No module named mime.text
  9. LINUX警告:检测到时钟错误。您的创建可能是不完整的。-转
  10. 手把手教你玩转网络编程模型之完成例程(Completion Routine)篇(下)-转