大家好,我是李二狗!一起坚持!一起学习!
每日一课,无论长短,有所学有所得
业精于勤技在专,行则将至事必成
优秀的代码习惯一定是伴随着单元测试的,这也是go语言设计的哲学;
国外的很多公司很多优秀的程序员都比较重视TDD,但是在国内十分少见;(TDD:测试驱动开发(test driven devlopment))
无论如何,学习并使用golang的单元测试,不是浪费时间,而是让你的代码更加优雅健硕!
测试文件
_test.gogo testgo build
测试函数
测试文件中的三种函数类型:
TestBenchmarkExample
测试命令
go test
go test*_test.go测试函数
然后生成一个临时的main包用于调用相应的测试函数,然后构建并运行、报告测试结果,最后清理测试中生成的临时文件;
接下来分别介绍单元测试函数、基准函数、示例函数:
单元测试函数
// 文件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() {
}
=== 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 -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
// 修改以上示例代码中的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
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){}
func BenchmarkSplit(b *testing.B) {
// 注意b.N
for i := 0; i < b.N; i++ {
Split("沙河有沙又有河", "沙")
}
}
// 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
// `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
package fib
// Fib 是一个计算第n个斐波那契数的函数
func Fib(n int) int {
if n < 2 {
return n
}
return Fib(n-1) + Fib(n-2)
}
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
示例函数
示例函数能够作为文档直接使用,例如基于web的godoc中能把示例函数与对应的函数或包相关联