代码测试
go语言通过自带的testing框架,可以用来实现单元测试与性能测试,通过go test命令来执行单元测试或性能测试。
go test执行单元测试是以包为单位的,如果没有指定包,则默认使用执行命令时所在的包。遍历包下以*_test.go
结尾的文件,执行以Test
,Benchmark
, Example
开头的测试函数。
单元测试
单元测试用例函数以Test开头,例如TestXxx或者Test_xxx。函数的参数必须是testing.T
,可以调用testing.T
的Error 、Errorf 、FailNow 、Fatal 、FatalIf 方法,来说明测试不通过;调用 Log 、Logf 方法来记录测试信息。
执行go test
命令就会执行所在包下的所有测试函数。在测试过程中还有如下常用的参数:
-
-v
:显示测试函数的运行细节。 -
-run <regexp>
:指定要执行的测试函数 -
-count N
:指定测试函数指定的次数
单元测试模板
测试一个函数,我们通过会测试多个输入与输出是否正确,测试函数的编写也有了较为通用的模板
func TestFunc(t *testing.T) {
// 开始写单元测试逻辑
type args struct {
// 被测试函数的参数
}
tests := []struct {
name string
args args
want TYPE //args作为参数的时候,被测试函数预期的输出结果
}{
// TODO: Add test cases.
}
for _, tt := range tests {
if got := Func(tt.args.x, tt.args.y); got != tt.want {
t.Errorf("Func(args.x, args.y) = %f, want %v", got, tt.want)
}
}
}
具体的细节可能有所不同,但是一般较为规范的单元测试都符合上面的格式。
在上面的got != tt.want
的比较,我们可以使用github.com/stretchr/testify/assert
包来使得比较更加方便和简单。
单元测试自动化生成
单元测试既然可以有通用的模板,那么当然就可以有工具来帮助我们生成这个模板。比如gotests
这个工具。
执行如下命令安装gotests
$ go get -u github.com/cweill/gotests/...
gotests 命令执行格式为:gotests [options] [PATH] [FILE] ...
。gotests 可以为PATH下的所有 Go 源码文件中的函数生成测试代码,也可以只为某个FILE中的函数生成测试代码。
例如$ gotests -all -w .
命令就会为当前目录下的所有函数生成测试代码。然后只需要在模板的TODO位置出添加具体的case即可。
性能测试
性能测试函数必须以Benchmark开头,例如BenchmarkXxx或者Benchmark_xxx。并且其函数参数必须为b testing.B
,函数内b.N
作为循环测试,其中N会在运行时动态调整,直到性能测试函数能够运行足够长的时间,来进行可靠的计时。
func BenchmarkRandInt(b *testing.B) {
for i := 0; i < b.N; i++ {
RandInt()
}
}
go test
命令默认不会执行性能测试函数,需要通过参数-bench <regexp>
来运行指定的测试函数,go test -bench=".*"
表示执行所有的性能测试函数。
在性能测试中,如果被测试函数执行前需要进行一些耗时的准备操作,那么可以在准备工作完成后重置计时,或者先暂停计时准备工作完成后再启动计时。
func BenchmarkXxx(b *testing.B) {
// 准备工作
b.ResetTimer()
for i := 0; i < b.N; i++ {
//...
}
}
func BenchmarkXxx(b *testing.B) {
b.StopTimer() // 调用该函数停止压力测试的时间计数
// 准备工作
b.StartTimer() // 重新开始时间
for i := 0; i < b.N; i++ {
//...
}
}
性能测试中我们还关注如下的参数
-
-benchmem
:性能测试中关注的内存指标。如 每次执行分配的内存大小(越小,占用内存越少)以及每次执行内存的分配次数(越小,性能越好)。 -
-benchtime
:指定测试时间和循环执行测试,格式为Nx,例如10s表示10秒,100x表示执行100次。 -
-cpu
:指定 GOMAXPROCS。 -
-timeout
:指定测试函数的超时时间
示例测试
示例测试函数以Example开头。例如ExampleXxx或者Example_xxx。示例测试函数没有输入参数和输出参数,但是在函数的结尾可能会有以Output:
或者Unordered output:
开头的注释,Unordered output:
开头的注释会忽略输出行的顺序。例如
func ExampleMax() {
fmt.Println(Max(1, 2))
// Output:
// 2
}
go test
命令默认也会执行示例测试函数,并且将示例测试函数输出到标准输出的内容与注释的内容进行比较(比较时忽略前后空格),相等则测试通过,不相等则测试失败。
对于大型示例测试,可以一个测试函数一个文件,这样godoc展示这类示例测试的时候会直接展示整个文件。
TestMain函数
func TestMain(m *testing.M) {
fmt.Println("do some setup")
m.Run()
fmt.Println("do some cleanup")
}
TestMain是一个特殊的函数,go test
命令执行测试函数的时候,会先执行TestMain函数,TestMain中调用m.Run()
来执行普通的测试函数。所以TestMain函数,我们通过在m.Run()
之前做一些测试的准备工作,例如创建数据库连接;在m.Run()
做一些测试的清理工作,例如关闭数据库连接,删除测试产生的临时文件。
mock测试
一半来说,数据库中是不允许有外部依赖的,例如数据库连接,这些外部依赖都需要被模拟,在go中,就是借用各种mock工具进行模拟的。
GoMock是golang官方开发的测试框架,实现较为完整的基于interface的Mock功能。接下来我们就了解一下使用GoMock进行mock测试。
GoMock测试框架分为两部分,一是GoMock包,用来管理对象生成周期,另一个就是mockgen工具,用来生成interface对应的mock源文件。
首先,安装gomock包和mockgen工具
$ go get github.com/golang/mock/gomock
$ go install github.com/golang/mock/mockgen
GoMock是基于interface的,所以我们现在有如下的接口Store
,其中只有一个方法GetCount
用于获取记录条数。我们可以为不同的数据库来实现这个接口。
type Store interface {
GetCount() int
}
然后有一个函数调用了这个接口实现的方法
func Count(store Store) int {
return store.GetCount()
}
我们现在要对Count
这个函数进行单元测试,但是现在Store接口既没有实现,单元测试环境中也不应该依赖外部数据库,所以我们要对Store
进行mock来获得一个实例。
对接口进行mock,这就要用到mockgen工具了。当前demo的module名为gomock-demo,目录结构如下:
├── count.go
├── go.mod
├── go.sum
└── store
└── store.go
执行下面的命令为Store接口生成mock实现,命令参数后续进行说明
$ mockgen -destination store/mock/store_mock.go -package store gomock-demo/store Store
执行结束后的目录结构如下:
├── count.go
├── go.mod
├── go.sum
└── store
├── mock
│ └── store_mock.go
└── store.go
我们接下来就可以为Count
编写如下的单元测试了
func TestCount(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockStore := store.NewMockStore(ctrl)
mockStore.EXPECT().GetCount().Return(10)
got := Count(mockStore)
if got != 10 {
t.Error("Count wrong res: ", got)
}
}
通过mock,很多无法测试的函数也可以进行测试了。
mockgen说明
mockgen工具用于为接口生成mock实现。有两种生成方式,一种是带-source
的源码模式,例如
mockgen -destination store/mock/store_mock.go -package store -source store/store.go
还有一种就是利用反射程序的反射模式,通过传递两个非标志参数,即导入路径和逗号分隔的接口列表来启用,其他参数和源码模式共用。前面的示例中使用的就是反射模式
$ mockgen -destination store/mock/store_mock.go -package store gomock-demo/store Store
这里介绍再一下命令行的参数即可
-
-destination
:生成的mock代码所在路径 -
-package
;mock文件的包名 -
-source
:需要mock的接口文件 -
-imports
:依赖的包 -
-aux_files
:接口不止一个文件时,附件文件 -
-build_flags
:传递给build工具的参数
通常前3个参数就已经够用了。
为了方便,可以在接口文件的代码添加注释
//go:generate mockgen -destination mock/store_mock.go -package store -source store.go
这样我们只需要执行下面的命令,就可以为这个接口文件生成mock代码了
go generate ./...
mock代码编写单元测试
通过mockgen工具生成mock代码之后,我们就可以利用生成的mock代码和gomock包编写单元测试了。
首先,创建Mock控制器来管理整个mock过程,并在完成之后进行回收
ctrl := gomock.NewController(t)
defer ctrl.Finish()
然后创建mock实例
mockStore := store.NewMockStore(ctrl)
获得mock实例之后,要想mock一个接口,就需要mock接口的入参和返回值。返回值通过Return来mock,就像前面的示例一样
mockStore.EXPECT().GetCount().Return(10)
这里的GetCount
没有入参,但是对于有入参的方法,这里需要使用如下的参数匹配对入参进行约束
- gomock.Any(),可以用来表示任意的入参。
- gomock.Eq(value),用来表示与 value 等价的值。
- gomock.Not(value),用来表示非 value 以外的值。
- gomock.Nil(),用来表示 None 值。
mock实例进行EXPECT断言,然后调用方法就可以获得第一个Call对象,并对其进行约束
func (c *Call) After(preReq *Call) *Call // After声明调用在preReq完成后执行
func (c *Call) Times(n int) *Call // 设置调用次数为 n 次
func (c *Call) AnyTimes() *Call // 允许调用次数为 0 次或更多次
func (c *Call) MaxTimes(n int) *Call // 设置最大的调用次数为 n 次
func (c *Call) MinTimes(n int) *Call // 设置最小的调用次数为 n 次
func (c *Call) Do(f interface{}) *Call // 声明在匹配时要运行的操作
func (c *Call) Return(rets ...interface{}) *Call // // 声明模拟函数调用返回的值
func (c *Call) SetArg(n int, value interface{}) *Call // 声明使用指针设置第 n 个参数的值
测试覆盖率
为了避免漏掉为某些函数编写测试用例,或者测试用例不够全面。Go提供了cover工具来统计测试覆盖率。
- 生成测试覆盖率数据
$ go test -coverprofile=coverage.out
会在当前目录下生成coverage.out覆盖率数据文件。
- 分析覆盖率文件
$ go tool cover -func=coverage.out
可以查看各个函数的测试覆盖率。可以据此来为函数编写或完善测试用例。
为了更清晰地展示,还可以生成html格式的分析文件
$ go tool cover -html=coverage.out -o coverage.html
原文地址:https://www.cnblogs.com/smarticen/p/17076767.html
- Java内存管理
- python基础知识——内置数据结构(字典)
- mysql、mongodb、python(dataframe).聚合函数的形式,以及报错解决方案
- JavaScript计算水仙花数【可自定义范围】
- JSP简单入门(1)
- mongodb取出json,利用python转成dataframe(dict-to-dataframe)
- JSP简单入门(2)
- JSP简单入门(3)
- 物化视图相关的性能改进 (r7笔记第58天)
- Maven 核心原理解析(1)
- LeetCode——Two Sum
- TensorFlow全新的数据读取方式:Dataset API入门教程
- 不经意发现的dba_objects和dba_tables中的细节(r7笔记第56天)
- LeetCode——Longest Substring Without Repeating Characters
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Arrow更好用的python时间序列处理库,你用过吗?
- 死信队列监听补充
- 手把手教你用Python查询你的物流信息
- Selenium自动登录淘宝,我无意间发现了登录漏洞!
- 【DB宝20】在Docker中分分钟即可拥有OGG Director环境
- mq监听死信队列后如何处理
- 【小白学PyTorch】7 最新版本torchvision.transforms常用API翻译与讲解
- 小白学PyTorch | 8 实战之MNIST小试牛刀
- 干货:用好VSCode这13款插件和8个快捷键,工作效率提升10倍
- 使用dplyr包对表格整理
- 安利 5 个拍案叫绝的 Matplotlib 骚操作!
- 多媒体程序开发
- 本地 IDE 已废!编辑器大结局!GitHub 的云 VSCode 实测
- 实战 | Python 编写端口扫描器
- 我这几年踩过的十个坑,每一条都是血泪教训