优良的代码习惯肯定是随同着单元测试的,这也是 go 语言设计的哲学;
国外的很多公司很多优良的程序员都比拟器重 TDD,然而在国内非常少见;(TDD:测试驱动开发(test driven devlopment))
无论如何,学习并应用 golang 的单元测试,不是浪费时间,而是让你的代码更加优雅健硕!
测试文件
文件名以 _test.go
为后缀的文件均为测试代码,都会被 go test
测试捕获,不会被 go build
编译;
测试函数
测试文件中的三种函数类型:
- 单元测试函数:函数名前缀
Test
;测试程序的逻辑 - 基准函数:函数名前缀
Benchmark
;测试函数的性能 - 示例函数:函数名前缀
Example
;会呈现在 godoc 中的,为文档提供示例文档
测试命令
Go 语言中的测试依赖 go test
命令;在此命令下增加各种不同的参数以实现不同目标的测试;前面会一一介绍;
go test
命令会遍历所有的 *_test.go
文件中合乎上述命名规定的 测试函数
;
而后生成一个长期的 main 包用于调用相应的测试函数,而后构建并运行、报告测试后果,最初清理测试中生成的临时文件;
接下来别离介绍单元测试函数、基准函数、示例函数:
单元测试函数
-
单元测试函数的格局:
func TestName(t *testing.T) {}
- 函数的名字必须以
Test
结尾,可选的后缀名必须以大写字母结尾 - 每个测试函数必须导入
testing
包;对于 testing 包中的办法能够去看一下源码; - 参数
t
用于报告测试失败和附加的日志信息
-
一个简略的测试函数示例:将
输入的后果
与预期的后果
进行比拟-
创立业务函数
// 文件 split/split.go:定义一个 split 的包,包中定义了一个 Split 函数 package split import "strings" func Split(s, sep string) (result []string) {i := strings.Index(s, sep) for i > -1 {result = append(result, s[:i]) s = s[i+1:] i = strings.Index(s, sep) } result = append(result, s) return }
-
创立测试文件
// 文件 split/split_test.go:创立一个 split_test.go 的测试文件 package split import ( "reflect" "testing" ) // 单元测试函数 // 测试函数名必须以 Test 结尾,必须接管一个 *testing.T 类型参数 // 1. 间接调用业务函数 // 2. 定义冀望后果 // 3. 比拟理论后果和冀望后果 func TestSplit(t *testing.T) {got := Split("a:b:c", ":") // 调用程序并返回程序后果 want := []string{"a", "b", "c"} // 冀望的后果 if !reflect.DeepEqual(want, got) { // 因为 slice 不能间接比拟,借助反射包中的办法比拟 t.Errorf("expected:%v, got:%v", want, got) // 如果测试失败输入谬误提醒 } } // 提供一个失败的单元测试 func TestSplitFail(t *testing.T) {got := Split("abcd", "bc") want := []string{"a", "d"} if !reflect.DeepEqual(want, got) {t.Errorf("expected:%v, got:%v", want, got) } } // 基准测试函数 func BenchmarkSplit(b *testing.B) { } // 示例函数 func ExampleSplit() {}
-
执行测试命令
进入 split 目录下,间接运行
go test
命令即可;如果运行
go test -v
的话,能够看到更具体的输入后果:晓得哪个测试函数没有通过,错在哪里=== RUN TestSplit --- PASS: TestSplit (0.00s) === RUN TestSplitFail split_test.go:28: expected:[a d], got:[a cd] --- FAIL: TestSplitFail (0.00s) FAIL exit status 1 FAIL gotest/split 0.001s
-
-
其余
go test
命令go test -run=?
:run
对应一个正则表达式,只有函数名匹配上的测试函数才会被go test
命令执行;// 比方以上代码执行命令:go test -v -run=Fail // 示意本次只运行 能正则匹配到 Fail 的 测试函数 === RUN TestSplitFail split_test.go:28: expected:[a d], got:[a cd] --- FAIL: TestSplitFail (0.00s) FAIL exit status 1 FAIL gotest/split 0.001s
go test -short
:跳过测试函数中蕴含testing.Short()
函数的测试函数;个别用于跳过执行起来太耗时的测试函数;比方:// 批改以上示例代码中的 TestSplitFail 函数如下 func TestSplitFail(t *testing.T) {if testing.Short() {t.Skip("short 模式下会跳过该测试用例") } got := Split("abcd", "bc") // 调用程序并返回程序后果 want := []string{"a", "d"} // 冀望的后果 if !reflect.DeepEqual(want, got) { // 因为 slice 不能间接比拟,借助反射包中的办法比拟 t.Errorf("expected:%v, got:%v", want, got) // 如果测试失败输入谬误提醒 } } // 而后执行命令 `go test -v -short` 打印如下后果:=== RUN TestSplit --- PASS: TestSplit (0.00s) === RUN TestSplitFail split_test.go:25: short 模式下会跳过该测试用例 --- SKIP: TestSplitFail (0.00s) PASS ok gotest/split 0.002s
go test -cover
测试覆盖率:覆盖率是指测试代码笼罩的业务代码的占比;go test -cover -coverprofile=c.out
将覆盖率相干的信息输入到以后文件夹上面的c.out
文件中;再而后执行
go tool cover -html=c.out
,应用cover
工具来解决生成的记录信息,该命令会关上本地的浏览器窗口生成一个 HTML 报告; -
子测试:对多组测试用例可能清晰精确的进行谬误定位
Go1.7+ 中新增了子测试,咱们能够依照如下形式应用
t.Run
执行子测试:func TestSplit(t *testing.T) { type test struct { input string sep string want []string} // 定义多组测试用例 tests := map[string]test{"simple": {input: "a:b:c", sep: ":", want: []string{"a", "b", "c"}}, "wrong sep": {input: "a:b:c", sep: ",", want: []string{"a:b:c"}}, "more sep": {input: "abcd", sep: "bc", want: []string{"a", "d"}}, "leading sep": {input: "沙河有沙又有河", sep: "沙", want: []string{"河有", "又有河"}}, } // t.Run 就是子测试 for name, tc := range tests {t.Run(name, func(t *testing.T) {// 应用 t.Run()执行子测试 got := Split(tc.input, tc.sep) if !reflect.DeepEqual(got, tc.want) {t.Errorf("expected:%#v, got:%#v", tc.want, got) } }) } } // 执行 go test -v === RUN TestSplit === RUN TestSplit/simple === RUN TestSplit/wrong_sep === RUN TestSplit/more_sep split_test.go:34: expected:[]string{"a", "d"}, got:[]string{"a", "cd"} === RUN TestSplit/leading_sep split_test.go:34: expected:[]string{"河有", "又有河"}, got:[]string{"","\xb2\x99 河有 ","\xb2\x99 又有河 "} --- FAIL: TestSplit (0.00s) --- PASS: TestSplit/simple (0.00s) --- PASS: TestSplit/wrong_sep (0.00s) --- FAIL: TestSplit/more_sep (0.00s) --- FAIL: TestSplit/leading_sep (0.00s) FAIL exit status 1 FAIL gotest/split 0.002s
基准函数
在肯定的工作负载之下检测程序性能;
根本格局如:func BenchmarkName(b *testing.B){}
- 以
Benchmark
为前缀,前面跟着首字母大写 - 必须要有
testing.B
类型的参数; - 基准测试必须要执行
b.N
次,这样的测试才有对照性,b.N
的值是零碎依据理论状况去调整的(见前面应用示例) - 对于
testing.B
的办法能够参考源码 -
一个简略的基准函数的示例:
-
创立测试函数(持续接着下面的示例代码)
func BenchmarkSplit(b *testing.B) { // 留神 b.N for i := 0; i < b.N; i++ {Split("沙河有沙又有河", "沙") } }
-
执行测试命令
-bench=$ 正则匹配
// go test -bench=Split goos: windows goarch: amd64 pkg: go-test/split cpu: Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz BenchmarkSplit-4 4017925 311.9 ns/op PASS ok go-test/split 1.976s
以上输入了电脑的一些信息;
其中 BenchmarkSplit- 4 中的 4 示意
GOMAXPROCS
的值;4017925
示意调用该函数的次数;311.9 ns/op
调用该函数的均匀耗时;// `go test -bench=Split -benchmem` 减少了内存调配的统计数据 goos: windows goarch: amd64 pkg: go-test/split cpu: Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz BenchmarkSplit-4 3995856 300.9 ns/op 112 B/op 3 allocs/op PASS ok go-test/split 1.890s
其中
112 B/op
示意每次操作内存调配了 112 字节;3 allocs/op
示意每次操作进行了 3 次内存调配;
-
-
性能比拟函数
- 基准函数只能给出相对耗时;理论中咱们可能想晓得不同操作的绝对耗时;
- 性能比拟函数通常是一个带有参数的函数,被多个不同的 Benchmark 函数传入不同的值来调用;
-
一个简略的基准测试中的性能比拟函数示例:
-
创立一个 fib 目录,并新建一个 fib.go 文件
package fib // Fib 是一个计算第 n 个斐波那契数的函数 func Fib(n int) int { if n < 2 {return n} return Fib(n-1) + Fib(n-2) }
-
同目录下创立一个测试函数文件 fib_test.go
package fib import ("testing") func benchmarkFib(b *testing.B, n int) { for i := 0; i < b.N; i++ {Fib(n) } } func BenchmarkFib1(b *testing.B) {benchmarkFib(b, 1) } func BenchmarkFib2(b *testing.B) {benchmarkFib(b, 2) } func BenchmarkFib3(b *testing.B) {benchmarkFib(b, 3) } func BenchmarkFib10(b *testing.B) {benchmarkFib(b, 10) } func BenchmarkFib20(b *testing.B) {benchmarkFib(b, 20) } func BenchmarkFib40(b *testing.B) {benchmarkFib(b, 40) }
-
执行测试命令
// go test -bench=Fib goos: windows goarch: amd64 pkg: go-test/fib cpu: Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz BenchmarkFib1-4 470478618 2.393 ns/op BenchmarkFib2-4 178773399 6.737 ns/op BenchmarkFib3-4 100000000 12.60 ns/op BenchmarkFib10-4 3025942 421.8 ns/op BenchmarkFib20-4 24792 55344 ns/op BenchmarkFib40-4 2 724560000 ns/op PASS ok go-interview/fib 11.675s
BenchmarkFib40-4 2 724560000 ns/op
:第一个是对应的比拟函数;第二个是执行的次数;第三个是执行的均匀工夫;由此可见 fib 函数入参值越大,函数执行的效率越低;
-
- 基准测试命令除了以上这些外还有一些其余的参数做其余用途:比方这些参数
benchtime
,ResetTimer
,RunParallel
,SetParallelism
,cpu
,setup
,teardown
,TestMain
,能够理解一下
示例函数
示例函数可能作为文档间接应用,例如基于 web 的 godoc 中能把示例函数与对应的函数或包相关联
保持每日输入 go 开发 + 面试题 + 算法 + 工作教训等后端相干技术
对于我往年的打算请查看:flag-2022
更多博客内容请查看 bigshake