Go 语言工程实践之测试 | 青训营笔记

102 阅读3分钟

这是我参与「第五届青训营 」笔记创作活动的第3天

本堂课重点内容

Go语言单元测试方法 如何利用mock在不依赖外部数据的情况下进行测试

详细知识点介绍

测试分回归测试、集成测试和单元测试三种。覆盖率依次变大,成本依次降低。

单元测试的主要方式是,生成不同的输出给被测试单元,对比其输出与期望值。

使用Go语言单元测试需要引用testing包

测试代码文件名为:被测试文件名_test.go

测试函数名为:被测试函数名前面加Test,其唯一参数为*testing.T类型

为了使测试不依赖外部数据(数据库、文件)等,保证幂等性(多次测试结果相同),可以使用mock,为测试代码提供依赖项。例如把一个文件中所有的abc替换成abcabc则不满足幂等性,多次运行测试会导致文件越来越大。

可以使用Go的mockgen包来生成mock代码

实践练习例子

//main.go
type Database interface {
   GetDB() string
}

type Mysql struct {
}

func (m *Mysql) GetDB() string {
   return "123abc456"
}

func Replace(m Database) string {
   s := m.GetDB()
   return strings.ReplaceAll(s, "abc", "abcabc")
}

假如我们要测试Replace()函数,其通过传入的Database接口获取数据库中的某字符串,然后将字符串中的abc替换成abcabc。假设实际项目中,该函数通过实现了该接口的Mysql类获取用户数据,返回值被用于更新数据库。那么如果直接对其进行测试,每次测试后数据库中的该字符串都会不断变长。这一方面污染了数据库,改变了用户原有数据;另一方面其结果也每次改变,不利于反复进行测试。

这种情况下我们可以进行mock测试,先安装并用mockgen生成mock代码。相当于生成了一个你可以操控的类,而这个类实现了Database接口,可以用作被测试函数的参数。

> go get -u github.com/golang/mock/gomock
> go get -u github.com/golang/mock/mockgen
> mockgen -source="main.go" -destination="main_mock.go" -package="main" 

其中mockgen命令的source为要测试的函数所在文件,destination为生成的代码位置,package为mock代码包名。执行完后,目录下便生成了main_mock.go文件。

然后编写测试代码。

//main_test.go
func TestGetFromDB(t *testing.T) {
   ctrl := gomock.NewController(t)
   defer ctrl.Finish()
   mockObj := NewMockDatabase(ctrl)
   mockObj.EXPECT().GetDB().Return("123abc456")
   if res := Replace(mockObj); res != "123abcabc456" {
      t.Fatal("Wrong answer: ", res)
   }
}

ctrl为mock的控制器,前两行可看做是固定写法。

核心是mockObj.EXPECT().GetDB().Return("123abc456")一句。mockObj便是main_mock.go中实现Database接口的那个类。如前文所述,你可以操作这个类,其GetDB()方法返回值为123abcabc456。其中的EXPECT()表示被测试函数必须调用GetDB()方法,否则测试会失败。

之后将mockObj作为参数传入要测试的函数即可。

图片.png

若不调用GetDB()方法,则测试失败:

图片.png