本节介绍如何使用go自带的测试包testing以及第三方测试包对程序进行测试。
1. 单元测试
1.1 基本的测试方法
单元测试(unit test),就是一种为验证单元的正确性而设置的自动化测试,一个单元就是程序中的一个模块化部分。一般来说,一个单元通常会与程序中的一个函数或者一个方法相对应。
在Go中,以_test.go
作为后缀的文件会被当做测试文件,比如main_test.go
会被视为main.go
的测试文件。
下面看一个例子,假设我们在main
包内,编写了一个JSON解码器函数decode
:
type Post struct{
Id int `json:"id"`
Content string `json:"content"`
}
func decode(filename string)(post Post,err error){
jsonFile,err := os.Open(filename)
if err!=nil{
fmt.Println("Error opening JSON file:", err)
return
}
defer jsonFile.Close()
decoder:=json.NewDecoder(jsonFile)
err = decoder.Decode(&post)
if err != nil {
fmt.Println("Error decoding JSON:", err)
return
}
return
}
那么,我们接下来需要创建main_test.go
,然后编写:
func TestDecode(t *testing.T){
post,err := decode("post.json")
if err != nil {
t.Error(err)
}
if post.Id != 1 {
t.Error("Wrong id, was expecting 1 but got", post.Id)
}
}
测试文件与被测试的源码文件位于同一个包内,它唯一导入并使用的包为testing
包。函数TestDecode
是一个测试用例,它代表的是对decode
函数的单元测试。
testing.T
结构拥有几个非常有用的函数:
- Log——将给定的文本记录到错误日志里面,与fmt.Println类似;
- Logf——根据给定的格式,将给定的文本记录到错误日志里面,与fmt.Printf类似;
- Fail——将测试函数标记为“已失败”,但允许测试函数继续执行;
- FailNow——将测试函数标记为“已失败”并停止执行测试函数。
运行测试用例的方法是,在控制台输入:
go test
然后就能看到:
D:\Go_Practice>go test
PASS
ok _/D_/Go_Practice 4.478s
1.2 跳过部分测试
有些API暂时没有实现,或者某些功能现在不用测试,因此我们需要跳过。跳过的办法是调用skip
:
func TestLongRunningTest(t *testing.T) {
if testing.Short() {
t.Skip("Skipping long-running test in short mode")
}
time.Sleep(10 * time.Second)
}
如果我们使用:
go test -short
就能跳过被short
引导的部分。
下面是测试部分:
当我们输入go test
时,延时函数被执行:
PASS
ok _/D_/Go_Practice 11.737s
给定short
标志后:
PASS
ok _/D_/Go_Practice 1.756s
1.3 并行测试
只要单元测试可以独立地进行,用户就可以通过并行地运行测试用例来提升测试的速度。
在main_test.go
文件所在的目录中创建一个名为parallel_test.go
的文件,并在文件中键入代码:
func TestParallel_1(t *testing.T){
t.Parallel()
time.Sleep(1*time.Second)
}
func TestParallel_2(t *testing.T){
t.Parallel()
time.Sleep(2*time.Second)
}
这个程序利用time.Sleep
函数,以2个测试用例分别模拟了2个需要耗时1s、2s的任务。只要在终端中执行以下命令,Go就会以并行的方式运行测试:
go test –v –short –parallel 2
1.4 性能测试
性能测试也叫基准测试(benchmarking),和之前的功能测试不同,这种测试的目的是检验程序的性能。
基准测试用例也需要放置到以_test.go
为后缀的文件中,并且每个基准测试函数都需要符合以下格式:
func BenchmarkXxx(*testing.B) { ... }
比如我们自建一个bench_test.go
,然后输入:
func BenchmarkDecode(b *testing.B){
for i:=0;i<b.N;i++{
decode("post.json")
}
}
测试程序要做的就是将被测试的代码执行b.N次,以便准确地检测出代码的响应时间,其中b.N的值将根据被执行的代码而改变。
为了运行基准测试用例,用户需要在执行go test
命令时使用基准测试标志-bench
go test -v -short –bench .
点号表示运行目录下的所有基准测试文件,测试结果如下:
=== RUN TestDecode
--- PASS: TestDecode (0.00s)
=== RUN TestLongRunningTest
--- SKIP: TestLongRunningTest (0.00s)
main_test.go:20: Skipping long-running test in short mode
=== RUN TestParallel_1
=== PAUSE TestParallel_1
=== RUN TestParallel_2
=== PAUSE TestParallel_2
=== CONT TestParallel_1
=== CONT TestParallel_2
--- PASS: TestParallel_1 (1.00s)
--- PASS: TestParallel_2 (2.00s)
goos: windows
goarch: amd64
BenchmarkDecode-8 31851 40615 ns/op
PASS
ok _/D_/Go_Practice 5.401s
结果中的31851为测试时b.N的实际值,也就是函数被循环执行的次数。在这个例子中,迭代进行了31851次,并且每次耗费了40615ns,即0.040615ms。
在进行基准测试时,测试用例的迭代次数是由Go自行决定的,测试程序将进行足够多次的迭代,直到获得一个准确的测量值为止。
上面的命令既运行了基准测试,也运行了功能测试。如果需要,用户也可以通过运行标志-run
来忽略功能测试。
go test -run x -bench .
2. HTTP测试
前面介绍的是如何测试Go的普通程序,这一篇介绍如何进行Web方面的测试。
对Go Web应用的单元测试可以通过testing/httptest
包来完成。这个包提供了模拟一个Web服务器所需的设施,用户可以利用net/http
包中的客户端函数向这个服务器发送HTTP请求,然后获取模拟服务器返回的HTTP响应。
func TestHandleGet( t *testing.T){
mux:=http.NewServeMux()
mux.HandleFunc("/order",handleRequest)
writer:=httptest.NewRecorder()
request,_:=http.NewRequest("GET","order/20005",nil)
mux.ServeHTTP(writer,request)
if writer.Code != 200 {// 对记录器记载的响应结果进行检查
t.Errorf("Response code is %v", writer.Code)
}
var order Orders
json.Unmarshal(writer.Body.Bytes(),&order)
if order.Order_num!=20005{
t.Error("Cannot retrieve JSON post")
}
}