go语言实战 - 测试

go语言实战 - 测试

第九章 测试和性能

单元测试

  • 单元测试是用来测试包或者程序的一部分代码或者一组代码的函数
  • 正向路径测试,保证代码不产生错误,正常运行的测试
  • 负向路径测试,保证代码产生错误,并且是预期的错误
  • go中的单元测试
    • 基础测试,只用一组参数和结果进行测试
    • 表组测试,多组参数和结果进行测试
    • mock数据来减少外部依赖

基础单元测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
const (
checkMark = "\u2713"
ballotX = "\u2717"
)
func TestDownload(t *testing.T) {
uri := "https://api.weixin.qq.com/cgi-bin/user/info"
statusCode := 200
t.Log("Given the need to test downloading content")
{
t.Logf("\tWhen checking \"%s\" for status code \"%d\"", uri, statusCode)
{
resp, err := http.Get(uri)
if err != nil {
t.Fatal("\t\tShould be able to make the Get call", ballotX, err)
}
t.Log("\t\tShould be able to make the Get call", checkMark)
defer resp.Body.Close()
if resp.StatusCode == statusCode {
t.Logf("\t\tShould reveive a \"%d\" status %v", statusCode, checkMark)
} else {
t.Logf("\t\tShould reveive a \"%d\" status %v %v", statusCode, ballotX, resp.StatusCode)
}
}
}
}
/*
=== RUN TestDownload
--- PASS: TestDownload (0.27s)
01_test.go:17: Given the need to test downloading content
01_test.go:19: When checking "https://api.weixin.qq.com/cgi-bin/user/info" for status code "200"
01_test.go:25: Should be able to make the Get call ✓
01_test.go:30: Should reveive a "200" status ✓
PASS
*/
  • go语言只认为以_test.go结尾的文件是测试文件
  • testing包提供了从测试框架到报告测试的输出和状态的各种测试功能的支持
  • 测试函数必须是公开的函数,并且以Test开头,函数签名必须接收一个指向testing.T类型的指针,并且不返回任何值

表组测试

  • 测试可以接受一组不通的输入并且产生不同的输出的代码,推荐使用表组测试的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
func TestDownload2(t *testing.T) {
var urls = []struct{
url string
statusCode int
} {
{
url: "https://api.weixin.qq.com/cgi-bin/user/info",
statusCode: 200,
},
{
url: "https://api.weixin.qq.com/cgi-bin/user",
statusCode: http.StatusNotFound,
},
}
t.Log("Given the need to test downloading content")
{
for _, u := range urls {
uri := u.url
statusCode := u.statusCode
t.Logf("\tWhen checking \"%s\" for status code \"%d\"", uri, statusCode)
{
resp, err := http.Get(uri)
if err != nil {
t.Fatal("\t\tShould be able to make the Get call", ballotX, err)
}
t.Log("\t\tShould be able to make the Get call", checkMark)
defer resp.Body.Close()
if resp.StatusCode == statusCode {
t.Logf("\t\tShould reveive a \"%d\" status %v", statusCode, checkMark)
} else {
t.Logf("\t\tShould reveive a \"%d\" status %v %v", statusCode, ballotX, resp.StatusCode)
}
}
}
}
}

Mock调用

  • httptest包可以让开发人员模仿基于http的网络调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
var feed = `<?xml version="1.0" encoding="UTF-8"?>
<rss>
<channel>
<title>Going Go Programming</title>
<description>Golang : https://github.com/goinggo</description>
<link>http://www.goinggo.net/</link>
<item>
<pubDate>Sun, 15 Mar 2015 15:04:00 +0000</pubDate>
<title>Object Oriented Programming Mechanics</title>
<description>Go is an object oriented language.</description>
<link>http://www.goinggo.net/2015/03/object-oriented</link>
</item>
</channel>
</rss>
`
func mockServer() *httptest.Server {
f := func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
w.Header().Set("Content-Type", "application/xml")
_, _ = fmt.Fprintln(w, feed)
}
return httptest.NewServer(http.HandlerFunc(f))
}
func TestDownload3(t *testing.T) {
statusCode := 200
server := mockServer()
defer server.Close()
t.Log("Given the need to test downloading content")
{
t.Logf("\tWhen checking \"%s\" for status code \"%d\"", server.URL, statusCode)
{
resp, err := http.Get(server.URL)
if err != nil {
t.Fatal("\t\tShould be able to make the Get call", ballotX, err)
}
t.Log("\t\tShould be able to make the Get call", checkMark)
defer resp.Body.Close()
if resp.StatusCode == statusCode {
t.Logf("\t\tShould reveive a \"%d\" status %v", statusCode, checkMark)
} else {
t.Logf("\t\tShould reveive a \"%d\" status %v %v", statusCode, ballotX, resp.StatusCode)
}
}
}
}

示例

  • 开发人员可以创建自己的示例
  • 函数开头为Example
  • 示例代码的函数名字必须基于已经存在的公开的函数或者方法
  • 最好将示例的输出列在下方

基准测试

  • 基准测试是测试代码性能的方法
  • 基准测试必须以Benchmark开头,接受一个执行testing.B类型的指针作为唯一参数
  • 基准测试默认框架会在持续1s的时间内,反复调用需要测试的函数,测试框架每次调用测试函数时,都会增加b.N的值
  • 如果只想运行基准测试,而跳过单元测试,则用命令go test -v -run="none" -bench="BenchmarkSprintf"即可
  • -benchtime=3s可以变更基准测试的运行时间,默认为1s
  • b.ResetTimer用来重置计时器
  • -benchmem展示每次操作分配内存的次数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package main
import (
"fmt"
"strconv"
"testing"
)
// 找出最快的奖整数转换成字符串的方法
func BenchmarkSprintf(b *testing.B) {
number := 10
b.ResetTimer()
for i := 0; i < b.N; i++ {
fmt.Sprintf("%d", number)
}
}
func BenchmarkFormat(b *testing.B) {
number := int64(10)
b.ResetTimer()
for i := 0; i < b.N; i++ {
strconv.FormatInt(number, 10)
}
}
func BenchmarkItoa(b *testing.B) {
number := 10
b.ResetTimer()
for i := 0; i < b.N; i++ {
strconv.Itoa(number)
}
}
/*
goos: darwin
goarch: amd64
pkg: api/books/go-in-action/9-testing
BenchmarkSprintf-12 20000000 70.6 ns/op
BenchmarkFormat-12 1000000000 2.56 ns/op
BenchmarkItoa-12 1000000000 2.42 ns/op
PASS
*/

小结

  • 测试功能被内置到go语言中,go语言提供了必要的测试工具
  • go test工具用来运行测试
  • 测试文件总是以_test作为文件名的结尾
  • 表组测试是利用一个测试函数测试多组值的好办法
  • 表中的实例代码,既能用于测试,也能用于文档
  • 基准测试提供了探查代码性能的机制
Donate comment here