Go语言的测试技术是相对低级的。它依赖一个 go test 测试命令和一组按照约定方式编写的 测试函数,测试命令可以运行这些测试函数。编写相对轻量级的纯测试代码是有效的,而且它很容易延伸到基准测试和示例文档。

go test

编写测试代码和编写普通的Go代码过程是类似的,并不需要学习新的语法、规则或工具。
在包目录内,所有以_test.go为后缀名的源代码文件都是go test测试的一部分,不会被go build编译到最终的可执行文件中。

类型 格式 作用
测试函数 函数名前缀为Test 测试程序的一些逻辑行为是否正确
基准函数 函数名前缀为Benchmark 测试函数的性能
示例函数 函数名前缀为Example 为文档提供示例文档

go test命令会遍历所有的*_test.go文件中符合上述命名规则的函数,然后生成一个临时的main包用于调用相应的测试函数,然后构建并运行、报告测试结果,最后清理测试中生成的临时文件。

测试函数

格式:测试函数的名字必须以Test开头,可选的后缀名必须以大写字母开头,参数t用于报告测试失败和附加的日志信息。

func TestName(t *testing.T){
// ...
}

testing.T的拥有的方法:

func (c *T) Error(args ...interface{})
func (c *T) Errorf(format string, args ...interface{})
func (c *T) Fail()
func (c *T) FailNow()
func (c *T) Failed() bool
func (c *T) Fatal(args ...interface{})
func (c *T) Fatalf(format string, args ...interface{})
func (c *T) Log(args ...interface{})
func (c *T) Logf(format string, args ...interface{})
func (c *T) Name() string
func (t *T) Parallel()
func (t *T) Run(name string, f func(t *T)) bool
func (c *T) Skip(args ...interface{})
func (c *T) SkipNow()
func (c *T) Skipf(format string, args ...interface{})
func (c *T) Skipped() bool

示例:自定义一个split函数,实现split功能:

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_test.go的测试文件,并定义一个测试函数如下:

func TsetSplit(t *testing.T)  {
got := Split("a:b:c",":")
want := []string{"a","b","c"}
if !reflect.DeepEqual(want,got){
t.Errorf("期待:%v,得到:%v",want,got)
}
}

在split包路径下,执行go test命令,可以看到输出结果如下:

testing: warning: no tests to run
PASS

出现pass意味测试通过!
如果有多个测试函数,其中一个测试不过,会出现以下提示:

--- FAIL: TestMoreSplit (0.00s)
split_test.go:40: excepted:[a d], got:[a cd]
FAIL
exit status 1

如果要查看测试函数名称和运行时间,可以使用go test -v

=== RUN   TestMoreSplit
--- FAIL: TestMoreSplit (0.00s)
split_test.go:40: excepted:[a d], got:[a cd]
FAIL

还可以在go test命令后添加-run参数,它对应一个正则表达式,只有函数名匹配上的测试函数才会被go test命令执行。

测试组

如果有多个测试,不必每个测试都写一个函数,可以使用测试组来完成。

//测试组
func TestSplit(t *testing.T) {
type test []struct{
input string
sep string
want []string
}
tests := test{
{input:"a:b:c",sep:":",want:[]string{"a","b","c"}},
{input:"a:b:c",sep:",",want:[]string{"a","b","c"}},
{input:"abcdbce",sep:"bc",want:[]string{"a","d","e"}},
{input:"上海自来水来自海上",sep:"海",want:[]string{"上","自来水来自","上"}},
}
for _,tc := range tests{
got := Split(tc.input,tc.sep)
if !reflect.DeepEqual(got,tc.want){
t.Errorf("期待:%v,获得:%v",tc.want,got)
}
}
}

执行go test -v,获得

=== RUN   TestSplit
--- FAIL: TestSplit (0.00s)
split_test.go:59: 期待:[a b c],获得:[a:b:c]
split_test.go:59: 期待:[a d e],获得:[a cd ce]
split_test.go:59: 期待:[上 自来水来自 上],获得:[上 ��自来水来自 ��上]
FAIL
exit status 1

中文有乱码,这种情况下可以使用%#v的格式化方式查看乱码是什么:
t.Errorf("期待:%#v,获得:%#v",tc.want,got)
得到:
split_test.go:59: 期待:[]string{"上", "自来水来自", "上"},获得:[]string{"上", "\xb5\xb7自来水来自", "\xb5\xb7上"}

当测试组中测试比较多的时候,可以加个name来查看是哪个测试用例出了问题:

//测试组
func TestSplit(t *testing.T) {
type test struct{ // 定义test结构体
input string
sep string
want []string
}
tests := map[string]test{
"simple": {input:"a:b:c",sep:":",want:[]string{"a","b","c"}},
"simple1": {input:"a:b:c",sep:",",want:[]string{"a","b","c"}},
"simple2": {input:"abcdbce",sep:"bc",want:[]string{"a","d","e"}},
"simple3": {input:"上海自来水来自海上",sep:"海",want:[]string{"上","自来水来自","上"}}, }
for name,tc := range tests{
got := Split(tc.input,tc.sep)
if !reflect.DeepEqual(got,tc.want){
t.Errorf("测试名:%#v,期待:%#v,获得:%#v",name,tc.want,got)
}
}
}

输出的时候就会把测试用例的名字带上:

=== RUN   TestSplit
--- FAIL: TestSplit (0.00s)
split_test.go:66: 测试名:"simple1",期待:[]string{"a", "b", "c"},获得:[]string{"a:b:c"}
split_test.go:66: 测试名:"simple2",期待:[]string{"a", "d", "e"},获得:[]string{"a", "cd", "ce"}
split_test.go:66: 测试名:"simple3",期待:[]string{"上", "自来水来自", "上"},获得:[]string{"上", "\xb5\xb7自来水来自", "\xb5\xb7上"}
FAIL
exit status 1

子测试

    for name,tc := range tests{
t.Run(name, func(t *testing.T) {
got := Split(tc.input,tc.sep)
if !reflect.DeepEqual(got,tc.want){
t.Errorf("测试名:%#v,期待:%#v,获得:%#v",name,tc.want,got)
}
})
}

执行结果:

$go test -v
=== RUN TestSplit
=== RUN TestSplit/simple2
=== RUN TestSplit/simple3
=== RUN TestSplit/simple
=== RUN TestSplit/simple1
--- FAIL: TestSplit (0.00s)
--- FAIL: TestSplit/simple2 (0.00s)
split_test.go:71: 测试名:"simple2",期待:[]string{"a", "d", "e"},获得:[]string{"a", "cd", "ce"}
--- FAIL: TestSplit/simple3 (0.00s)
split_test.go:71: 测试名:"simple3",期待:[]string{"上", "自来水来自", "上"},获得:[]string{"上", "\xb5\xb7自来水来自", "\xb5\xb7上"}
--- PASS: TestSplit/simple (0.00s)
--- FAIL: TestSplit/simple1 (0.00s)
split_test.go:71: 测试名:"simple1",期待:[]string{"a", "b", "c"},获得:[]string{"a:b:c"}
FAIL
exit status 1

可以通过-run=RegExp来指定运行的测试用例,还可以通过/来指定要运行的子测试用例,只测试simple:
$go test -v -run=Split/simple

测试覆盖率

在测试中至少被运行一次的代码占总代码的比例。Go提供内置功能来检查你的代码覆盖率。我们可以使用go test -cover来查看测试覆盖率。

$go test -cover
--- FAIL: TestSplit (0.00s)
--- FAIL: TestSplit/simple2 (0.00s)
split_test.go:71: 测试名:"simple2",期待:[]string{"a", "d", "e"},获得:[]string{"a", "cd", "ce"}
--- FAIL: TestSplit/simple3 (0.00s)
split_test.go:71: 测试名:"simple3",期待:[]string{"上", "自来水来自", "上"},获得:[]string{"上", "\xb5\xb7自来水来自", "\xb5\xb7上"}
--- FAIL: TestSplit/simple1 (0.00s)
split_test.go:71: 测试名:"simple1",期待:[]string{"a", "b", "c"},获得:[]string{"a:b:c"}
FAIL
coverage: 100.0% of statements
exit status 1

Go还提供了一个额外的-coverprofile参数,用来将覆盖率相关的记录信息输出到一个文件。
$ go test -cover -coverprofile=c.out
执行go tool cover -html=c.out,使用cover工具来处理生成的记录信息,该命令会打开本地的浏览器窗口生成一个HTML报告。

用绿色标记的语句块表示被覆盖了,而红色的表示没有被覆盖。

基准测试

基准测试就是在一定的工作负载之下检测程序性能的一种方法。
格式:

func BenchmarkName(b *testing.B){
// ...
}

基准测试以Benchmark为前缀,需要一个'*testing.B'类型的参数b,基准测试必须要执行b.N次,这样的测试才有对照性,b.N的值是系统根据实际情况去调整的,从而保证测试的稳定性。 testing.B拥有的方法如下:

func (c *B) Error(args ...interface{})
func (c *B) Errorf(format string, args ...interface{})
func (c *B) Fail()
func (c *B) FailNow()
func (c *B) Failed() bool
func (c *B) Fatal(args ...interface{})
func (c *B) Fatalf(format string, args ...interface{})
func (c *B) Log(args ...interface{})
func (c *B) Logf(format string, args ...interface{})
func (c *B) Name() string
func (b *B) ReportAllocs()
func (b *B) ResetTimer()
func (b *B) Run(name string, f func(b *B)) bool
func (b *B) RunParallel(body func(*PB))
func (b *B) SetBytes(n int64)
func (b *B) SetParallelism(p int)
func (c *B) Skip(args ...interface{})
func (c *B) SkipNow()
func (c *B) Skipf(format string, args ...interface{})
func (c *B) Skipped() bool
func (b *B) StartTimer()
func (b *B) StopTimer()

为split编写基准测试:

//基准测试
func BenchmarkSplit(b *testing.B) {
for i:=0;i<b.N;i++{
Split("a:b:c",":")
}
}

通过执行go test -bench=Split命令执行基准测试,输出结果如下:

$go test -bench=Split
goos: darwin
goarch: amd64
pkg: xxxxxx
BenchmarkSplit-8 10000000 203 ns/op
PASS

其中BenchmarkSplit-8表示对Split函数进行基准测试,数字8表示GOMAXPROCS的值,这个对于并发基准测试很重要。10000000和203ns/op表示每次调用Split函数耗时203ns,这个结果是10000000次调用的平均值。
默认情况下,每个基准测试至少运行1秒!
可为基准测试添加-benchmem参数,来获得内存分配的统计数据。

$go test -bench=Split -benchmem
...
BenchmarkSplit-8 10000000 203 ns/op 112 B/op 3 allocs/op
...

112 B/op表示每次操作内存分配了112字节,3 allocs/op则表示每次操作进行了3次内存分配。

性能比较函数

性能比较函数通常是一个带有参数的函数,被多个不同的Benchmark函数传入不同的值来调用。

func benchmark(b *testing.B, size int){/* ... */}
func Benchmark10(b *testing.B){ benchmark(b, 10) }
func Benchmark100(b *testing.B){ benchmark(b, 100) }
func Benchmark1000(b *testing.B){ benchmark(b, 1000) }

使用斐波那契数的函数测试:

// Fib 是一个计算第n个斐波那契数的函数
func Fib(n int) int {
if n <2{
return n
}
return Fib(n-1) + Fib(n+2)
}

性能比较函数:

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) }

默认情况下,每个基准测试至少运行1秒。如果在Benchmark函数返回时没有到1秒,则b.N的值会按1,2,5,10,20,50,…增加,并且函数再次运行。
还可以使用-benchtime标志增加最小基准时间,以产生更准确的结果。

并行测试

func (b *B) RunParallel(body func(*PB))会以并行的方式执行给定的基准测试。 RunParallel会创建出多个goroutine,并将b.N分配给这些 goroutine 执行, 其中 goroutine数量的默认值为GOMAXPROCS。用户如果想要增加非CPU受限(non-CPU-bound)基准测试的并行性, 那么可以在RunParallel之前调用SetParallelism 。RunParallel通常会与-cpu标志一同使用。
go test -bench=. -cpu 1来指定使用的CPU数量。

GO语言测试的更多相关文章

  1. C语言--测试电脑存储模式(大端存储OR小端存储)

    相信大家都知道大端存储和小端存储的概念,这在平时,我们一般不用考虑,但是,在某些场合,这些概念就显得很重要,比如,在 Socket 通信时,我们的电脑是小端存储模式,可是传送数据或者消息给对方电脑时, ...

  2. Go语言测试代码

    第一次学go语言,测试代码 package main import "fmt" var age int; const sex = 0 func init() { fmt.Print ...

  3. [译] Go语言测试进阶版建议与技巧

    阅读本篇文章前,你最好已经知道如何写基本的单元测试.本篇文章共包含3个小建议,以及7个小技巧. 建议一,不要使用框架 Go语言自身已经有一个非常棒的测试框架,它允许你使用Go编写测试代码,不需要再额外 ...

  4. c语言测试芯片好坏

    问题描述有n个(2<n<20)芯片,好的或坏的,并且有比坏的芯片更多的已知的好的芯片.每个芯片都可以用来测试其他芯片.当用一个好的芯片测试其他芯片时,它可以正确地给出被测芯片是好是坏.当用 ...

  5. 一套很有意思的C语言测试题目

    网络上逛博客,发现了一套很有意思的测试题目: https://kobes.ca/ 大家有兴趣可以做一下,考一些关于C语言使用的细节: 中文翻译参考: https://www.cnblogs.com/l ...

  6. 51单片机连接24C02-C语言测试代码

    忙了一天多终于透彻了,自己写的不好使,用别人的逐步分析改成自己的,我写得非常简洁易懂. 我总结3点需要注意的地方 1.关闭非IIC通信器件,比如我的开发板SDA和SCL也连接了DS1302,造成干扰会 ...

  7. Go语言测试:testing

    学习参考来源:https://www.liwenzhou.com/posts/Go/16_test/ go test工具 必须导入包: import "testing" go te ...

  8. 2017-2018-1 20155315 《信息安全系统设计基础》嵌入式C语言测试

    Hours 要求 伪代码 提取Hours 提取时间地址 时间存放在(基址+2)的16位寄存器中,定义一个时间宏存放地址. #define Time_Addr 0xFFFFC0000 #define T ...

  9. Criterion - 一个简单可扩展的 C 语言测试框架

    A dead-simple, yet extensible, C test framework. Philosophy Most test frameworks for C require a lot ...

随机推荐

  1. VsCode 解决vim插件,不能使用ctrl+c的问题(目标卸载im)

    VsCode中,使用VIM插件后,ctrl+c不能用 可以通过 File -> Preference -> Settings中   vim.useCtrlKeys 选项设置为 false ...

  2. 整合91平台接入的ANE

    来源:http://www.swfdiy.com/?p=1328 91平台接入的SDK只有objectC版和java版, 现在如果要在AIR里使用SDK,只能编写ANE整合进来. 91SDK = 几个 ...

  3. iOS应用状态保存和恢复

    当应用被后台Kill掉的时候希望从后台返回的时候显示进入后台之前的内容 在Appdelegate中设置 - (BOOL)application:(UIApplication *)application ...

  4. 自定义一个简单的JDBC连接池

    一.什么是JDBC连接池? 在传统的JDBC连接中,每次获得一个Connection连接都需要加载通过一些繁杂的代码去获取,例如以下代码: public static Connection getCo ...

  5. Django——Xadmin中的功能

    app_label 功能 如果不在标准models.py里面定义model,则必须指定这个model归属于哪个app. 使用 app_label = 'oms' actions 功能 Action插件 ...

  6. SCADA系统构架的安全分析总结

    概念: SCADA 数据采集与监控 Supervisory Control And Data Acquisition  .包括了计算机设备  工业控制组件    网络 组成部件 ,SCADA 系统被用 ...

  7. (Linux基础学习)第五章:Linux中的screen应用

    第1节:安装screen1.加载系统镜像文件,因为screen的安装包在系统镜像文件中图001 2.列出系统上所有的磁盘[root@centos6 ~]# lsblk图002 3.安装screen应用 ...

  8. P2261 [CQOI2007]余数求和[整除分块]

    题目大意 给出正整数 n 和 k 计算 \(G(n, k)=k\ \bmod\ 1 + k\ \bmod\ 2 + k\ \bmod\ 3 + \cdots + k\ \bmod\ n\) 的值 其中 ...

  9. P1052 过河[DP]

    题目描述 在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧.在桥上有一些石子,青蛙很讨厌踩在这些石子上.由于桥的长度和青蛙一次跳过的距离都是正整数,我们可以把独木桥上青蛙可能到达的点看成数 ...

  10. 01 浅谈c++及面向对象编程

    参考链接: 学习完c++但是对c++面向对象编程还是比较模糊,现在花时间总体来总结一下: c++中的对象是使用类来定义的,下面先重点讲一下类的概念. 说到类就要先说一下类的三种特性:封装,继承,多态. ...