每一个程序都应该学会写测试在去写代码
上面这句话虽然有点夸张,但是也是有一定的道理的。对于新手来说,都是在写一个简单的代码,一般调试后运行都没有多大问题。但是当写的一个项目的代码很多的时候,虽然当时编译通过了,并且能够顺利运行起来了。但是,可能一个小小的边角错误,就会导致系统的整体崩溃。好比我们在刷leetcode,信心满满的将代码写好,并且自己写了几个例子测试完,没问题,一提交发现还是有那么几个边边角角的例子被我们遗漏了。
好了来看看怎么写测试代码吧。
由于go test命令只能在一个相应的目录下执行所有文件,所以我们接下来新建一个项目目录gotest,这样我们所有的代码和测试代码都在这个目录下。
接下来我们在该目录下面创建两个文件:gotest.go
和gotest_test.go
gotest.go
:这个文件里面我们是创建了一个包,里面有一个函数实现了除法运算:
package gotest
import (
"errors"
)
func Division(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("除数不能为0")
}
return a / b, nil
}
1.gotest_test.go:这是我们的单元测试文件,但是记住下面的这些原则:
下面是我们的测试用例的代码:
package gotest
import (
"testing"
)
func TestDivision1(t *testing.T) {
if i, e := Division(6, 2); i != 3 || e != nil { //try a unit test on function
t.Error("除法函数测试没通过") // 如果不是如预期的那么就报错
} else {
t.Log("第一个测试通过了") //记录一些你期望记录的信息
}
}
func TestDivision2(t *testing.T) {
t.Error("就是不通过")
}
我们在项目目录下面执行go test
,就会显示如下信息:
--- FAIL: Test_Division_2 (0.00 seconds)
gotest_test.go:16: 就是不通过
FAIL
exit status 1
FAIL gotest 0.013s
从这个结果显示测试没有通过,因为在第二个测试函数中我们写死了测试不通过的代码t.Error
,那么我们的第一个函数执行的情况怎么样呢?默认情况下执行go test
是不会显示测试通过的信息的,我们需要带上参数go test -v
,这样就会显示如下信息:
=== RUN Test_Division_1
--- PASS: Test_Division_1 (0.00 seconds)
gotest_test.go:11: 第一个测试通过了
=== RUN Test_Division_2
--- FAIL: Test_Division_2 (0.00 seconds)
gotest_test.go:16: 就是不通过
FAIL
exit status 1
FAIL gotest 0.012s
上面的输出详细的展示了这个测试的过程,我们看到测试函数1Test_Division_1
测试通过,而测试函数2Test_Division_2
测试失败了,最后得出结论测试不通过。接下来我们把测试函数2修改成如下代码:
func TestDivision1(t *testing.T) {
if i, e := Division(6, 0); i != 3 || e != nil { //try a unit test on function
t.Error("除法函数测试没通过", e) // 如果不是如预期的那么就报错
} else {
t.Log("第一个测试通过了") //记录一些你期望记录的信息
}
}
然后我们执行go test -v
,就显示如下信息,测试通过了:
D:\data\code\studygo\test\test>go test -v
=== RUN Test_Division_1
Test_Division_1: example_test.go:9: 除法函数测试没通过 除数不能为0
--- FAIL: Test_Division_1 (0.00s)
=== RUN Test_Division_2
Test_Division_2: example_test.go:16: 就是不通过
--- FAIL: Test_Division_2 (0.00s)
FAIL
exit status 1
FAIL test/test 1.906s
压力测试用来检测函数(方法)的性能,和编写单元功能测试的方法类似,此处不再赘述,但需要注意以下几点:
func BenchmarkXXX(b *testing.B) { ... }
go test
不会默认执行压力测试的函数,如果要执行压力测试需要带上参数-test.bench
,语法:-test.bench="test_name_regex"
,例如go test -test.bench=".*"
表示测试全部的压力测试函数testing.B.N
,以使测试可以正常的运行_test.go
结尾下面我们新建一个压力测试文件webbench_test.go
,代码如下所示:
package gotest
import (
"testing"
)
func BenchmarkDivision(b *testing.B) {
for i := 0; i < b.N; i++ { //use b.N for looping
Division(4, 5)
}
}
func BenchmarkTimeConsumingFunction(b *testing.B) {
b.StopTimer() //调用该函数停止压力测试的时间计数
//做一些初始化的工作,例如读取文件数据,数据库连接之类的,
//这样这些时间不影响我们测试函数本身的性能
b.StartTimer() //重新开始时间
for i := 0; i < b.N; i++ {
Division(4, 5)
}
}
执行:go test -run="none" -v -bench=.
D:\data\code\studygo\test\test>go test -run="none" -v -bench=.
goos: windows
goarch: amd64
pkg: test/test
BenchmarkDivision
BenchmarkDivision-8 1000000000 0.293 ns/op
BenchmarkTimeConsumingFunction
BenchmarkTimeConsumingFunction-8 1000000000 0.287 ns/op
PASS
ok test/test 3.119s
-run="none"
是保证在运行制订的基准(压力)测试函数之前没有单元测试会被运行。如果不加这句就会执行同目录下的gotest_test.go
的测试用例。
-bench=.
表示执行所有的压力测试代码。假如我们只想执行一个测试单元比如执行BenchmarkDivision
可以写成-bench="BenchmarkDivision"
如果想测试的时间更长一些可以使用-benchtime="4s"
如果想提供每次操作分配内存的次数,以及总共分配内存的字节数-benmem
执行-benmem
结果:
BenchmarkDivision
BenchmarkDivision-8 1000000000 0.291 ns/op 0 B/op 0 allocs/op
BenchmarkTimeConsumingFunction
BenchmarkTimeConsumingFunction-8 1000000000 0.285 ns/op 0 B/op 0 allocs/op
这次输出的结果会多出两组新的数值:一组数值的单位是B/op
,另一组的单位是allocs/op
。单位为allocs/op
的值表示每次操作从堆上分配内存的次数。单位为B/op
的值表示每次操作分配的字节数。
参考文献:
《go web 编程》
《Go语言实战》