不会写单元测试的程序员不是一个合格的滴滴司机
go内置了一套单元测试机制: 利用 go test测试命令
和一套按照约定方式编写的测试函数。
- 测试文件以 _test.go 结尾
- 测试函数以Test、 Benchmark 、 Example开头
- go test有两种模式:
- go test 本地目录模式,不带包名,寻找当前目录下的tests
- go test 指定包名模式: ('go test math', 'go test ./...', and even 'go test .') 寻找指定包名下面的tests。
在包目录内,所有以_test.go为后缀名编写的go文件不会参与go build的编译过程.
go test 一共三种测试函数:
- 标准测试函数, 函数以Test为前缀,用于测试逻辑行为正确性, go test 会报告测试结果 PASS、FAIL
- 基准测试函数是以Benchmark为前缀的函数,用于衡量函数性能, 拿到平均执行时间
- 样例函数, 提供一个编译器保证正确性的示例文档
golang的约定式测试函数中,会要求传入testing.T testing.B等参数,
有些童鞋会问,我的测试函数不用这个参数,也能跑,
这些参数的作用是充当测试框架和测试函数的桥梁: 测试函数可以通过这个参数,报告日志,控制测试流程,报告测试结果。
标准测试函数
- 导入testing包
- 以Test开头,除Test开头的自定义函数需要首字母大写
- 函数参数t *testing.T用于报告测试失败和附加的日志信息
func TestWriteLog(t *testing.T) {
l := logrus.New()
l.SetFormatter(&logrus.TextFormatter{
DisableTimestamp: true,
})
l.SetOutput(io.Discard) // Send all logs to nowhere by default
bh := &BufferedWriterHook{Writer: os.Stdout}
defer bh.Stop()
err := bh.Fire(&logrus.Entry{Logger: l, Level: logrus.InfoLevel, Message: "test" + time.Now().Format(time.RFC3339)})
if err != nil {
t.Error(t.Name() + " FAIL")
}
}
基准测试函数
- 以Benchmark开头
- b.N表示迭代次数,不固定,确保至少执行1s
func BenchmarkFire(b *testing.B) {
l := logrus.New()
l.SetFormatter(&logrus.TextFormatter{
DisableTimestamp: true,
})
logf, err := os.OpenFile("./log.txt", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600)
if err != nil {
panic(err)
}
defer logf.Close()
bh := &BufferedWriterHook{Writer: logf}
defer bh.Stop()
b.ResetTimer() // 重置计时器,忽略前面的准备时间
for n := 0; n < b.N; n++ {
err := bh.Fire(&logrus.Entry{Logger: l, Level: logrus.InfoLevel, Message: "test" + time.Now().Format(time.RFC3339)})
if err != nil {
b.Error(b.Name() + " FAIL")
}
}
}
go test -bench=. 执行基准测试
以上如果有单元测试,也会执行,若要忽略单元测试,请执行go test -bench=. -count 5 -run=^#
//对https://github.com/zwbdzb/logrus-bufferedWriter-hook执行基准测试
BenchmarkFire-8 940003 1130 ns/op
BenchmarkFire1-8 53912 19678 ns/op
前者是循环次数,后者是每次循环的平均耗时。
结果显示 带异步缓冲区的logrus写磁盘能力,是logrus默认同步写磁盘能力的10+倍。
样例函数
- 以Example开头
- 需要在代码内体现预期输出,下方output注释部分。
go test -run=ExampleHook_default
func ExampleHook_default() {
l := logrus.New()
l.SetLevel(logrus.InfoLevel)
l.SetFormatter(&logrus.TextFormatter{
DisableTimestamp: true,
})
l.SetOutput(io.Discard) // Send all logs to nowhere by default
ws := &BufferedWriterHook{Writer: os.Stdout}
defer ws.Stop()
l.AddHook(ws)
l.Info("test2")
l.Warn("test3")
l.Error("test4")
// Output:
// level=info msg=test2
// level=warning msg=test3
// level=error msg=test4
}
用于单元测试的:mock打桩工具
字节开源了一个用于单元测试的打桩工具 : https://github.com/bytedance/mockey
有几个概念先要重申:
mock: 在单元测试中,mock(模拟)指的是一种技术,用于创建一个虚拟的对象,代替真正的对象。通过这种方式,开发人员可以在测试中模拟出某些行为和结果,以此来验证代码的正确性。
Mock对象通常会模拟出真实对象的某些特性,例如方法、属性、状态等等。当测试用例调用这些模拟对象时,它们的行为和结果可以被预设,以此来测试真实对象在不同情况下的行为。
例如,假设我们正在测试一个函数,该函数依赖于一个外部API来执行某些操作。在测试中,我们可以使用mock对象来代替这个API,并预设它在不同情况下返回不同的结果。这样,我们就能够在不依赖于外部API的情况下测试函数的功能,并验证它的正确性。
官方的demo:
import (
"fmt"
"testing"
. "github.com/bytedance/mockey"
. "github.com/smartystreets/goconvey/convey" // . 用在import语法中,表示将引入的包对外暴露的方法和变量导入到当前命名空间
)
func Foo(in string) string {
return in
}
type A struct{}
func (a A) Foo(in string) string { return in }
var Bar = 0
func TestMockXXX(t *testing.T) {
PatchConvey("TestMockXXX", t, func() {
Mock(Foo).Return("c").Build() // mock function, 模拟该Foo函数返回c
Mock(A.Foo).Return("c").Build() // mock method
MockValue(&Bar).To(1) // mock variable ,模拟该变量值是1
So(Foo("a"), ShouldEqual, "c") // assert `Foo` is mocked,
So(new(A).Foo("b"), ShouldEqual, "c") // assert `A.Foo` is mocked
So(Bar, ShouldEqual, 1) // assert `Bar` is mocked
})
// mock is released automatically outside `PatchConvey`
fmt.Println(Foo("a")) // a
fmt.Println(new(A).Foo("b")) // b
fmt.Println(Bar) // 0
}
这个mock 库总体上是个打桩工具,帮助你在单元测试中模拟 某些依赖项的行为和状态。
go test -gcflags="all=-l -N" -v
本文快速记录了golang单元测试、基准测试、样例测试的写法,介绍了在单元测试中可能用到的打桩工具, 耗时3h, 有用指数4颗星。
btw 本文测试源码位于https://github.com/zwbdzb/logrus-bufferedWriter-hook, 这是一个带异步缓冲区的logrus日志Hook,能有效解决logrus默认不支持异步日志带来的写性能问题,欢迎试用,期待你的star。
下面是相比同步写日志的基准测试结果:大致是原同步写日志的 10s+倍性能。
-8表示8个CPU线程执行;64819表示总共执行了64819次;19755ns/op,表示每次执行耗时19755纳秒;496/op表示每次执行分配了496字节内存;15 allocs/op表示每次执行分配了15次对象。
go test cached
go在执行单元测试,会缓存单元测试的输出结果。
有时候会影响对测试真实性的判断, 可go clean -testcache 关闭。
https://stackoverflow.com/questions/48882691/force-retesting-or-disable-test-caching
本文来自博客园,作者:{有态度的马甲},转载请注明原文链接:https://www.cnblogs.com/JulianHuang/p/17523100.html
欢迎关注我的原创技术、职场公众号, 加好友谈天说地,一起进化