单元测试
Go语言中的测试依赖go test命令,并在后面加上-v后可以显示详细信息。在包目录内,所有以_test.go为后缀名的源代码文件都是go test测试的一部分,不会被go build编译到最终的可执行文件中。在*_test.go文件中有三种类型的函数,单元测试函数、基准测试函数和示例函数。
| 类型 | 格式 | 作用 |
|---|---|---|
| 测试函数 | 函数名前缀为Test | 测试程序的一些逻辑行为是否正确 |
| 基准函数 | 函数名前缀为Benchmark | 测试函数的性能 |
| 示例函数 | 函数名前缀为Example | 为文档提供示例文档 |
-
单元测试
通过设置测试函数对函数功能进行测试,将输出和预期结果进行对比,如果不正确则设置对应的错误级别和错误信息。
package split_string import "strings" // 待测函数,切割字符串 func Split(str string, sep string) []string { var ret []string index := strings.Index(str, sep) for index >= 0 { if index != 0 { ret = append(ret, str[:index]) } str = str[index+len(sep):] index = strings.Index(str, sep) } ret = append(ret, str) return ret }package split_string // 单元测试函数 import ( "reflect" "testing" ) func TestSplit(t *testing.T) { // 程序输出的结果 got := Split("babcbef", "b") // 程序期望的结果 want := []string{"a", "c", "ef"} // 通过反射来帮助对比切片的结果 if !reflect.DeepEqual(got, want) { // 测试失败则给出错误提示 t.Errorf("want:%v but got: %v", want, got) } } func Test2Split(t *testing.T) { // 程序输出的结果 got := Split("a:b:c", ":") // 程序期望的结果 want := []string{"a", "b", "c"} // 通过反射来帮助对比切片的结果 if !reflect.DeepEqual(got, want) { // 测试失败则给出错误提示 t.Errorf("want:%v but got: %v", want, got) } } func Test3Split(t *testing.T) { // 程序输出的结果 got := Split("abcef", "bc") // 程序期望的结果 want := []string{"a", "ef"} // 通过反射来帮助对比切片的结果 if !reflect.DeepEqual(got, want) { // 测试失败则给出错误提示 t.Errorf("want:%v but got: %v", want, got) } } -
测试组
通过建立测试用例切片,然后循环执行所有的测试用例,减少代码的重复量。
package split_string // 单元测试组函数 import ( "reflect" "testing" ) func TestSplitGroup(t *testing.T) { type testCase struct { str string sep string want []string } testGroup := []testCase{ {"babcbef", "b", []string{"a", "c", "ef"}}, {"a:b:c", ":", []string{"a", "b", "c"}}, {"abcef", "bc", []string{"a", "ef"}}, {"沙河有沙又有河", "沙", []string{"河有", "又有河"}}, } for _, tc := range testGroup { got := Split(tc.str, tc.sep) if !reflect.DeepEqual(got, tc.want) { t.Fatalf("want:%v got:%#v", tc.want, got) } } } -
子测试
利用子测试可以查看所有测试用例对应的测试信息,并且可以通过在后面加上
-run=测试用例名(与输出中测试用例名一致)只测试其中一个测试用例package split_string import ( "reflect" "testing" ) func TestSplitSub(t *testing.T) { type testCase struct { str string sep string want []string } testGroup := map[string]testCase{ "case_1": {"babcbef", "b", []string{"a", "c", "ef"}}, "case_2": {"a:b:c", ":", []string{"a", "b", "c"}}, "case_3": {"abcef", "bc", []string{"a", "ef"}}, "case_4": {"沙河有沙又有河", "沙", []string{"河有", "又有河"}}, } for name, tc := range testGroup { // 利用t.run执行子测试,可以看到每个测试用例对应的测试结果 t.Run(name, func(t *testing.T) { got := Split(tc.str, tc.sep) if !reflect.DeepEqual(got, tc.want) { t.Fatalf("want:%v got:%#v", tc.want, got) } }) } } -
测试覆盖率
通过
go test -cover可以查看代码测试的覆盖的百分比,Go还提供了一个额外的-coverprofile参数,用来将覆盖率相关的记录信息输出到一个文件,例如:split $ go test -cover -coverprofile=c.out PASS coverage: 100.0% of statements ok github.com/Q1mi/studygo/code_demo/test_demo/split 0.005s上面的命令会将覆盖率相关的信息输出到当前文件夹下面的
c.out文件中,然后我们执行go tool cover -html=c.out,使用cover工具来处理生成的记录信息,该命令会打开本地的浏览器窗口生成一个HTML报告。通常测试函数覆盖率要求100%,测试覆盖率60% -
基准测试
基准测试就是在一定的工作负载之下检测程序性能的一种方法,它的定义是
Benchmark为前缀,需要一个*testing.B类型的参数b,基准测试必须要执行b.N次。基准测试并不会默认执行,需要增加-bench参数。针对不同的优化方法比较输出结果来改进。package split_string import "testing" // 基准测试 // 执行N遍测试性能 func BenchmarkSplit(b *testing.B) { // 这里的N是个未知数,尽量去跑到最大值 for i := 0; i < b.N; i++ { Split("a:b:c", ":") } }运行
go test -bench=Split命令执行基准测试,输出结果如下。其中数字12表示GOMAXPROCS(CPU内核数)的值,这个对于并发基准测试很重要。14801511和78.45 ns/op表示每次调用Split函数耗时78.45nsPS E:\GoWorkPlace\src\github.com\studygo\split_string> go test -bench=Split goos: windows goarch: amd64 pkg: github.com/studygo/split_string cpu: Intel(R) Core(TM) i7-10750H CPU @ 2.60GHz BenchmarkSplit-12 14801511 78.45 ns/op PASS ok github.com/studygo/split_string 1.412s通过基准测试添加
-benchmem参数,来获得内存分配的统计数据。其中48 B/op表示每次操作内存分配了48字节,1 allocs/op则表示每次操作进行了1次内存分配PS E:\GoWorkPlace\src\github.com\studygo\split_string> go test -bench=Split -benchmem goos: windows goarch: amd64 pkg: github.com/studygo/split_string cpu: Intel(R) Core(TM) i7-10750H CPU @ 2.60GHz BenchmarkSplit-12 15303565 75.82 ns/op 48 B/op 1 allocs/op PASS ok github.com/studygo/split_string 1.407s -
性能比较函数
性能比较函数通常是一个带有参数的函数,被多个不同的Benchmark函数传入不同的值来调用。
// 测试斐波那契数列性能 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) }运行测试时可以在
-bench=后写上公共函数名称前缀就可以调用所有对比的函数PS E:\GoWorkPlace\src\github.com\studygo\split_string> go test -bench=Fib goarch: amd64 pkg: github.com/studygo/split_string cpu: Intel(R) Core(TM) i7-10750H CPU @ 2.60GHz BenchmarkFib1-12 844466212 1.386 ns/op BenchmarkFib2-12 260821142 4.507 ns/op BenchmarkFib3-12 168101977 7.047 ns/op BenchmarkFib10-12 4449705 266.5 ns/op BenchmarkFib20-12 35829 33102 ns/op BenchmarkFib40-12 2 503109500 ns/op PASS ok github.com/studygo/split_string 9.411s默认情况下,每个基准测试至少运行1秒。如果在Benchmark函数返回时没有到1秒,则b.N的值会按1,2,5,10,20,50,…增加,并且函数再次运行。因此针对一次运行时间较长的函数可以
-benchtime标志增加最小基准时间,以产生更准确的结果。 -
时间重置
b.ResetTimer之前的处理不会放到执行时间里,也不会输出到报告中,所以可以在之前做一些不计划作为测试报告的操作.func BenchmarkSplit(b *testing.B) { time.Sleep(5 * time.Second) // 假设需要做一些耗时的无关操作 b.ResetTimer() // 重置计时器 for i := 0; i < b.N; i++ { Split("沙河有沙又有河", "沙") } } -
并行测试
func (b *B) RunParallel(body func(*PB))会以并行的方式执行给定的基准测试。RunParallel会创建出多个goroutine,并将b.N分配给这些goroutine执行, 其中goroutine数量的默认值为GOMAXPROCS。用户如果想要增加非CPU受限(non-CPU-bound)基准测试的并行性, 那么可以在RunParallel之前调用SetParallelism。RunParallel通常会与-cpu标志一同使用。func BenchmarkSplitParallel(b *testing.B) { // b.SetParallelism(1) // 设置使用的CPU数 b.RunParallel(func(pb *testing.PB) { for pb.Next() { Split("沙河有沙又有河", "沙") } }) }还可以通过在测试命令后添加
-cpu参数如go test -bench=. -cpu 1来指定使用的CPU数量。
403

被折叠的 条评论
为什么被折叠?



