gotest单元函数测试
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
gotest单元函数测试⾸先安装单元测试包,go get /smartystreets/goconvey/convey
源程序如下,定义了加减乘除4个函数
package test222
import (
"errors"
)
func Add(a, b int) int {
return a + b
}
func Subtract(a, b int) int {
return a - b
}
func Multiply(a, b int) int {
return a * b
}
func Division(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("被除数不能为 0")
}
return a / b, nil
}
测试程序如下
package test222
import (
"testing"
. "/smartystreets/goconvey/convey"
)
func TestAdd(t *testing.T) {
Convey("将两数相加", t, func() {
So(Add(1, 2), ShouldEqual, 4)
})
}
func TestSubtract(t *testing.T) {
Convey("将两数相减", t, func() {
So(Subtract(1, 2), ShouldEqual, -1)
})
}
func TestMultiply(t *testing.T) {
Convey("将两数相乘", t, func() {
So(Multiply(3, 2), ShouldEqual, 6)
})
}
func TestDivision(t *testing.T) {
Convey("将两数相除", t, func() {
Convey("除以⾮ 0 数", func() {
num, err := Division(10, 2)
So(err, ShouldBeNil)
So(num, ShouldEqual, 5)
})
Convey("除以 0", func() {
_, err := Division(10, 0)
So(err, ShouldNotBeNil)
})
})
}
测试⽂件的格式有⼏点要求:
1 测试程序的package名必须和源程序相同
2 ⽂件名必须是*_test.go的类型,*代表要测试的⽂件名
3 函数名必须以Test开头如:TestXxx或Test_xxx
4 测试函数TestXxx()的参数是*testing.T
5 测试内容都包含在Convey函数中
测试⽅法:
1 测试单个⽂件
go test -v func_test.go func.go
2 测试单个⽅法
go test -v -test.run TestAdd
---------------------
性能测试系统可以给出代码的性能数据,帮助测试者分析性能问题。
提⽰
单元测试(unit testing),是指对软件中的最⼩可测试单元进⾏检查和验证。
对于单元测试中单元的含义,⼀般要根据实际情况去判定其具体含义,如中单元指⼀个函数,⾥单元指⼀个类,图形化的软件中可以指⼀个窗⼝或⼀个菜单等。
总的来说,单元就是⼈为规定的最⼩的被测功能模块。
单元测试是在软件开发过程中要进⾏的最低级别的测试活动,软件的独⽴单元将在与程序的其他部分相隔离的情况下进⾏测试。
单元测试——测试和验证代码的框架
要开始⼀个单元测试,需要准备⼀个 go 源码⽂件,在命名⽂件时需要让⽂件必须以_test结尾。
单元测试源码⽂件可以由多个测试⽤例组成,每个测试⽤例函数需要以Test为前缀,例如:
func TestXXX( t *testing.T )
测试⽤例⽂件不会参与正常源码编译,不会被包含到可执⾏⽂件中。
测试⽤例⽂件使⽤ go test 指令来执⾏,没有也不需要 main() 作为函数⼊⼝。
所有在以_test结尾的源码内以Test开头的函数会⾃动被执⾏。
测试⽤例可以不传⼊ *testing.T 参数。
helloworld 的测试代码(具体位置是./src/chapter11/gotest/helloworld_test.go):
1. package code11_3
2.
3. import "testing"
4.
5. func TestHelloWorld(t *testing.T) {
6. t.Log("hello world")
7. }
代码说明如下:
第 5 ⾏,单元测试⽂件 (*_test.go) ⾥的测试⼊⼝必须以 Test 开始,参数为 *testing.T 的函数。
⼀个单元测试⽂件可以有多个测试⼊⼝。
第 6 ⾏,使⽤ testing 包的 T 结构提供的 Log() ⽅法打印字符串。
1) 单元测试命令⾏
单元测试使⽤ go test 命令启动,例如:
$ go test helloworld_test.go
ok command-line-arguments 0.003s
$ go test -v helloworld_test.go
=== RUN TestHelloWorld
--- PASS: TestHelloWorld (0.00s)
helloworld_test.go:8: hello world
PASS
ok command-line-arguments 0.004s
代码说明如下:
第 1 ⾏,在 go test 后跟 helloworld_test.go ⽂件,表⽰测试这个⽂件⾥的所有测试⽤例。
第 2 ⾏,显⽰测试结果,ok 表⽰测试通过,command-line-arguments 是测试⽤例需要⽤到的⼀个包名,0.003s 表⽰测试花费的时间。
第 3 ⾏,显⽰在附加参数中添加了-v,可以让测试时显⽰详细的流程。
第 4 ⾏,表⽰开始运⾏名叫 TestHelloWorld 的测试⽤例。
第 5 ⾏,表⽰已经运⾏完 TestHelloWorld 的测试⽤例,PASS 表⽰测试成功。
第 6 ⾏打印字符串 hello world。
2) 运⾏指定单元测试⽤例
go test 指定⽂件时默认执⾏⽂件内的所有测试⽤例。
可以使⽤-run参数选择需要的测试⽤例单独执⾏,参考下⾯的代码。
⼀个⽂件包含多个测试⽤例(具体位置是./src/chapter11/gotest/select_test.go)
1. package code11_3
2.
3. import "testing"
4.
5. func TestA(t *testing.T) {
6. t.Log("A")
7. }
8.
9. func TestAK(t *testing.T) {
10. t.Log("AK")
11. }
12.
13. func TestB(t *testing.T) {
14. t.Log("B")
15. }
16.
17. func TestC(t *testing.T) {
18. t.Log("C")
19. }
这⾥指定 TestA 进⾏测试:
$ go test -v -run TestA select_test.go
=== RUN TestA
--- PASS: TestA (0.00s)
select_test.go:6: A
=== RUN TestAK
--- PASS: TestAK (0.00s)
select_test.go:10: AK
PASS
ok command-line-arguments 0.003s
TestA 和 TestAK 的测试⽤例都被执⾏,原因是-run跟随的测试⽤例的名称⽀持正则表达式,使⽤-run TestA$即可只执⾏ TestA 测试⽤例。
3) 标记单元测试结果
当需要终⽌当前测试⽤例时,可以使⽤ FailNow,参考下⾯的代码。
测试结果标记(具体位置是./src/chapter11/gotest/fail_test.go)
1. func TestFailNow(t *testing.T) {
2. t.FailNow()
3. }
还有⼀种只标记错误不终⽌测试的⽅法,代码如下:
1. func TestFail(t *testing.T) {
2.
3. fmt.Println("before fail")
4.
5. t.Fail()
6.
7. fmt.Println("after fail")
8. }
测试结果如下:
=== RUN TestFail
before fail
after fail
--- FAIL: TestFail (0.00s)
FAIL
exit status 1
FAIL command-line-arguments 0.002s
从⽇志中看出,第 5 ⾏调⽤ Fail() 后测试结果标记为失败,但是第 7 ⾏依然被程序执⾏了。
4) 单元测试⽇志
每个测试⽤例可能并发执⾏,使⽤ testing.T 提供的⽇志输出可以保证⽇志跟随这个测试上下⽂⼀起打印输出。
testing.T 提供了⼏种⽇志输出⽅法,详见下表所⽰。
单元测试框架提供的⽇志⽅法
⽅法备注
Log打印⽇志,同时结束测试
Logf格式化打印⽇志,同时结束测试
Error打印错误⽇志,同时结束测试
Errorf格式化打印错误⽇志,同时结束测试
Fatal打印致命⽇志,同时结束测试
Fatalf格式化打印致命⽇志,同时结束测试
开发者可以根据实际需要选择合适的⽇志。
基准测试——获得代码内存占⽤和运⾏效率的性能数据
基准测试可以测试⼀段程序的运⾏性能及耗费 CPU 的程度。
Go 语⾔中提供了基准测试框架,使⽤⽅法类似于单元测试,使⽤者⽆须准备⾼精度的计时器和各种分析⼯具,基准测试本⾝即可以打印出⾮常标准的测试报告。
1) 基础测试基本使⽤
下⾯通过⼀个例⼦来了解基准测试的基本使⽤⽅法。
基准测试(具体位置是./src/chapter11/gotest/benchmark_test.go)
1. package code11_3
2.
3. import "testing"
4.
5. func Benchmark_Add(b *testing.B) {
6. var n int
7. for i := 0; i < b.N; i++ {
8. n++
9. }
10. }
这段代码使⽤基准测试框架测试加法性能。
第 7 ⾏中的 b.N 由基准测试框架提供。
测试代码需要保证函数可重⼊性及⽆状态,也就是说,测试代码不使⽤全局变量等带有记忆性质的数据结构。
避免多次运⾏同⼀段代码时的环境不⼀致,不能假设 N 值范围。
使⽤如下命令⾏开启基准测试:
$ go test -v -bench=. benchmark_test.go
goos: linux
goarch: amd64
Benchmark_Add-4 20000000 0.33 ns/op
PASS
ok command-line-arguments 0.700s
代码说明如下:
第 1 ⾏的-bench=.表⽰运⾏ benchmark_test.go ⽂件⾥的所有基准测试,和单元测试中的-run类似。
第 4 ⾏中显⽰基准测试名称,2000000000 表⽰测试的次数,也就是 testing.B 结构中提供给程序使⽤的 N。
“0.33 ns/op”表⽰每⼀个操作耗费多少时间(纳秒)。
注意:Windows 下使⽤ go test 命令⾏时,-bench=.应写为-bench="."。
2) 基准测试原理
基准测试框架对⼀个测试⽤例的默认测试时间是 1 秒。
开始测试时,当以 Benchmark 开头的基准测试⽤例函数返回时还不到 1 秒,那么testing.B 中的 N 值将按 1、2、5、10、20、50……递增,同时以递增后的值重新调⽤基准测试⽤例函数。
3) ⾃定义测试时间
通过-benchtime参数可以⾃定义测试时间,例如:
$ go test -v -bench=. -benchtime=5s benchmark_test.go
goos: linux
goarch: amd64
Benchmark_Add-4 10000000000 0.33 ns/op
PASS
ok command-line-arguments 3.380s
4) 测试内存
基准测试可以对⼀段代码可能存在的内存分配进⾏统计,下⾯是⼀段使⽤字符串格式化的函数,内部会进⾏⼀些分配操作。
1. func Benchmark_Alloc(b *testing.B) {
2.
3. for i := 0; i < b.N; i++ {
4. fmt.Sprintf("%d", i)
5. }
6. }
在命令⾏中添加-benchmem参数以显⽰内存分配情况,参见下⾯的指令:
$ go test -v -bench=Alloc -benchmem benchmark_test.go
goos: linux
goarch: amd64
Benchmark_Alloc-4 20000000 109 ns/op 16 B/op 2 allocs/op
PASS
ok command-line-arguments 2.311s
代码说明如下:
第 1 ⾏的代码中-bench后添加了 Alloc,指定只测试 Benchmark_Alloc() 函数。
第 4 ⾏代码的“16 B/op”表⽰每⼀次调⽤需要分配 16 个字节,“2 allocs/op”表⽰每⼀次调⽤有两次分配。
开发者根据这些信息可以迅速找到可能的分配点,进⾏优化和调整。
5) 控制计时器
有些测试需要⼀定的启动和初始化时间,如果从 Benchmark() 函数开始计时会很⼤程度上影响测试结果的精准性。
testing.B 提供了⼀系列的⽅法可以⽅便地控制计时器,从⽽让计时器只在需要的区间进⾏测试。
我们通过下⾯的代码来了解计时器的控制。
基准测试中的计时器控制(具体位置是./src/chapter11/gotest/benchmark_test.go):
1. func Benchmark_Add_TimerControl(b *testing.B) {
2.
3. // 重置计时器
4. b.ResetTimer()
5.
6. // 停⽌计时器
7. b.StopTimer()
8.
9. // 开始计时器
10. b.StartTimer()
11.
12. var n int
13. for i := 0; i < b.N; i++ {
14. n++
15. }
16. }
从 Benchmark() 函数开始,Timer 就开始计数。
StopTimer() 可以停⽌这个计数过程,做⼀些耗时的操作,通过 StartTimer() 重新开始计时。
ResetTimer() 可以重置计数器的数据。
计数器内部不仅包含耗时数据,还包括内存分配的数据。