测试单个文件,必定要带上被测试的原文件git
go test -v wechat_test.go wechat.gogithub
测试单个方法golang
go test -v -test.run TestRefreshAccessToken数据库
go 单元测试写法
参考 https://studygolang.com/articles/22982安全
testing
go语言package提供的自动化测试框架,
testing支持普通用例测试、压力测试、并行测试,基本能知足单测需求;并发
缺失:框架
testing不支持mock数据,想要mock数据必须本身实现mock逻辑,这会增长许多没必要要的工做量。ide
mock的重要性
理想状况下,一个好的函数结构必须有入参、出参,完成的是一项单1、独立的工做,好比:函数
func add(a int, b int) int {
return a + b
}
这样的函数写单测很简单,且轻易就能达到100%覆盖率。单元测试
但实际工做中,这样纯粹的函数占比可能不到10%,单个函数每每不是独立的,它须要依赖于其余模块、三方库、数据库等等。
以上传图片为例,上传一张图片样本函数:
func uploadImage(content []byte, UID string, fdfs *tsFDFS) error {
// 1. 上传图片到fdfs
url := fdfs.Upload(content)
// 2. 生成缩略图
resize.Resize(…)
jpeg.Encode(…)
// 3. 图片入库
sample := &database.yangbenModel{ … }
database.CreateSample(…)
// 4. 图片绑定任务
binding := &database.ExamplennotationTask{ … }
database.CreateBinding(…)
return nil
}
若是在单测中想要正常走完这个函数且不出错,咱们须要有一个真实的fdfs环境能够上传数据成功,一个真实的数据库,且根据业务逻辑,咱们想要插入一个sample前,必须有对应的enterprise、pipeline、annotation-task等等;
因而,在此前,项目中使用testing的单测实现思路是这样的:
func TestUploadImage(t *testing.T) {
// 1. 建立真实的fdfs环境
fdfs := &FDFS{ … }
// 2. 链接真实的数据库
db := database.NewGormConn(…)
// 3. 插入一条enters数据
enters := &database.EnterpriseModel{…}
database.CreateEnters(…)
// 4. 插入一条业务数据
…
// 5. 插入另一条业务数据
…
// 6. 开始执行单测
uploadImage(…)
…
}
能够看到,单测以前进行了大量耗时的、冗余的、无心义的工做
单测执行耗时长,正常一条单测时间通常在100ms内,在模拟了真实场景后,单测时间大幅增长,甚至项目中有部分单测超过1分钟;
单测编码效率低,须要了解了业务场景后,才能编写完一条单测case;
过于依赖真实环境,稳定性差,维护成本高,对于一些没法模拟真实环境的流程,只能放弃覆盖测试。
mock框架
gomonkey 单元测试
安装
go get -u -v https://github.com/agiledragon/gomonkey
github地址为:https://github.com/agiledragon/gomonkey
参考博客: https://studygolang.com/articles/15034
目前本身尝试过mock方法 把代码使用的粘贴供gopher参考下
这个地方是rpc调用其余服务,
测试中用打桩的方式,将这个
函数的结果先执行了,当执行
这个函数时,采用这个函数
打桩时mock的数据就能够了
测试文件中如何写mock第三方数据呢?
ApplyMethod 第三个参数
func() 中的第一个参数为这个
方法属于那个类型,剩余的参数
书写方式为 func(_ 绑定的那个类型,
,_ type)
例如
func(_ 绑定的那个类型,
,_ string, _ int)(返回结果)
gostub
对全局变量、函数或过程打桩,对代码中的外部依赖进行劫持并替换成mock的内容
为一个全局变量打桩
stubs := gostub.Stub(&num, 10)
defer stubs.Reset()
为一个函数打桩
stubs := gostub.StubFunc(&Func, func(args string) error {
return nil
})
defer stubs.Reset()
为一个过程打桩
stubs := gostub.StubFunc(&Destroy)
defer stubs.Reset()
场景组合
以上文测试上传一张图片样本为例
func TestUploadImage(t *testing.T) {
// 1. stub fdfs
stubs := gostub.StubFunc(&Upload, func() string {
return “”
})
defer stubs.Reset()
// 2. stub 生成缩略图 stubs = gostub.StubFunc(&resize.Resize, func() error { return nil }) // 3. stub sample create stubs = gostub.StubFunc(&database.CreateSample, func() error { return nil }) ... // 4. stub binding create ... // 5. 开始执行单测 uploadImage(...) ...
}
缺点
对代码有必定的侵入性 (须要将函数改成全局变量语法形式、对三方库须要作一层包装)
没法对方法(成员函数)进行打桩
monkey
monkey是go的一个猴子补丁(monkeypatching)框架,在运行时经过汇编语句重写可执行文件,将待打桩函数或方法的实现跳转到桩实现,原理和热补丁相似。
经过monkey能够解决stub没法为方法打桩的问题,但monkey不是线程安全的,不能用于并发测试。
为一个函数打桩
guard := monkey.Patch(Func, func(args string) error {
return nil
})
defer guard.Unpatch()
为一个方法打桩
guard := monkey.PatchInstanceMethod(reflect.TypeOf(client), “Test”, func(args string) error {
return nil
})
defer guard.Unpatch()缺点线程不安全在macOS 10.15及以上版本没法运行,因为其原理是修改可执行文件,从Catalina版本起,系统对文件读写权限作了更严格的管理,monkey修改的文件无写权限,运行panic,参考https://github.com/agiledragon/gomonkey/issues/10