Skip to content

Commit e95e44f

Browse files
committed
完成单元测试和基准测试
1 parent e074208 commit e95e44f

File tree

11 files changed

+680
-13
lines changed

11 files changed

+680
-13
lines changed

SUMMARY.md

+6-4
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,12 @@
4141
- 8.5 archive/tar — tar 归档访问
4242
- 8.6 archive/zip — zip 归档访问
4343
* 第九章 [测试](chapter09/09.0.md)
44-
- 9.1 [testing - 自动化单元测试](chapter09/09.1.md)
45-
- 9.2 iotest - io 测试辅助工具
46-
- 9.3 quick - 黑盒测试辅助工具
47-
- 9.4 httptest - HTTP 测试辅助工具
44+
- 9.1 [testing - 单元测试](chapter09/09.1.md)
45+
- 9.2 [testing - 基准测试](chapter09/09.2.md)
46+
- 9.3 [testing - 子测试](chapter09/09.3.md)
47+
- 9.4 [testing - 运行并验证示例](chapter09/09.4.md)
48+
- 9.5 [testing - 其他功能](chapter09/09.5.md)
49+
- 9.6 [httptest - HTTP 测试辅助工具](chapter09/09.6.md)
4850
* 第十章 [进程、线程与 goroutine](chapter10/10.0.md)
4951
- 10.1 [创建进程](chapter10/10.1.md)
5052
- 10.2 [进程属性和控制](chapter10/10.2.md)

chapter09/09.0.md

+5-8
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
# 第九章 测试 #
22

3-
Go语言从开发初期就注意了测试用例的编写。特别是静态语言,由于调试没有动态语言那么方便,所以能最快最方便地编写一个测试用例就显得非常重要了。
3+
Go 语言从开发初期就注意了测试用例的编写。特别是静态语言,由于调试没有动态语言那么方便,所以能最快最方便地编写一个测试用例就显得非常重要了。
44

5-
本章内容涵盖了Go标准库中的4个包
5+
本章内容涵盖了 Go 标准库中的 2 个包
66

7-
* *testing* 方便进行 Go 包的自动化单元测试
8-
* *testing/iotest* 方便进行 `io` 测试,实现了 `io.Reader``io.Writer`
9-
* *testing/quick* 进行黑盒测试的辅助功能包
7+
* *testing* 方便进行 Go 包的自动化单元测试、基准测试
108
* *net/http/httptest* 提供测试 `HTTP` 的工具
119

12-
1310
# 导航 #
1411

15-
- [第二章](/chapter09/09.0.md)
16-
- 下一节:[testing - 自动化单元测试](09.1.md)
12+
- [第八章](/chapter08/08.0.md)
13+
- 下一节:[testing - 单元测试](09.1.md)

chapter09/09.1.md

+238-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,236 @@
1-
# testing - 自动化单元测试 #
1+
# testing - 单元测试 #
2+
3+
`testing` 为 Go 语言 package 提供自动化测试的支持。通过 `go test` 命令,能够自动执行如下形式的任何函数:
4+
5+
func TestXxx(*testing.T)
6+
7+
注意:Xxx 可以是任何字母数字字符串,但是第一个字母不能是小些字母。
8+
9+
在这些函数中,使用 Error, Fail 或相关方法来发出失败信号。
10+
11+
要编写一个新的测试套件,需要创建一个名称以 _test.go 结尾的文件,该文件包含 `TestXxx` 函数,如上所述。 将该文件放在与被测试的包相同的包中。该文件将被排除在正常的程序包之外,但在运行 “go test” 命令时将被包含。 有关详细信息,请运行 “go help test” 和 “go help testflag” 了解。
12+
13+
如果有需要,可以调用 `*T``*B` 的 Skip 方法,跳过该测试或基准测试:
14+
15+
```go
16+
func TestTimeConsuming(t *testing.T) {
17+
if testing.Short() {
18+
t.Skip("skipping test in short mode.")
19+
}
20+
...
21+
}
22+
```
23+
24+
## 第一个单元测试 ##
25+
26+
要测试的代码:
27+
28+
```go
29+
func Fib(n int) int {
30+
if n < 2 {
31+
return n
32+
}
33+
return Fib(n-1) + Fib(n-2)
34+
}
35+
```
36+
37+
测试代码:
38+
39+
```go
40+
func TestFib(t *testing.T) {
41+
var (
42+
in = 7
43+
expected = 13
44+
)
45+
actual := Fib(in)
46+
if actual != expected {
47+
t.Errorf("Fib(%d) = %d; expected %d", in, actual, expected)
48+
}
49+
}
50+
```
51+
执行 `go test .`,输出:
52+
53+
```
54+
$ go test .
55+
ok chapter09/testing 0.007s
56+
```
57+
58+
表示测试通过。
59+
60+
我们将 `Sum` 函数改为:
61+
62+
```go
63+
func Fib(n int) int {
64+
if n < 2 {
65+
return n
66+
}
67+
return Fib(n-1) + Fib(n-1)
68+
}
69+
```
70+
71+
再执行 `go test .`,输出:
72+
73+
```
74+
$ go test .
75+
--- FAIL: TestSum (0.00s)
76+
t_test.go:16: Fib(10) = 64; expected 13
77+
FAIL
78+
FAIL chapter09/testing 0.009s
79+
```
80+
81+
## Table-Driven Test ##
82+
83+
测试讲究 case 覆盖,按上面的方式,当我们要覆盖更多 case 时,显然通过修改代码的方式很笨拙。这时我们可以采用 Table-Driven 的方式写测试,标准库中有很多测试是使用这种方式写的。
84+
85+
```go
86+
func TestFib(t *testing.T) {
87+
var fibTests = []struct {
88+
in int // input
89+
expected int // expected result
90+
}{
91+
{1, 1},
92+
{2, 1},
93+
{3, 2},
94+
{4, 3},
95+
{5, 5},
96+
{6, 8},
97+
{7, 13},
98+
}
99+
100+
for _, tt := range fibTests {
101+
actual := Fib(tt.in)
102+
if actual != tt.expected {
103+
t.Errorf("Fib(%d) = %d; expected %d", tt.in, actual, tt.expected)
104+
}
105+
}
106+
}
107+
```
108+
因为我们使用的是 `t.Errorf`,其中某个 case 失败,并不会终止测试执行。
109+
110+
## T 类型 ##
111+
112+
单元测试中,传递给测试函数的参数是 `*testing.T` 类型。它用于管理测试状态并支持格式化测试日志。测试日志会在执行测试的过程中不断累积,并在测试完成时转储至标准输出。
113+
114+
当一个测试的测试函数返回时,又或者当一个测试函数调用 `FailNow``Fatal``Fatalf``SkipNow``Skip` 或者 `Skipf` 中的任意一个时,该测试即宣告结束。跟 `Parallel` 方法一样,以上提到的这些方法只能在运行测试函数的 goroutine 中调用。
115+
116+
至于其他报告方法,比如 `Log` 以及 `Error` 的变种, 则可以在多个 goroutine 中同时进行调用。
117+
118+
### 报告方法 ###
119+
120+
上面提到的系列包括方法,带 `f` 的是格式化的,格式化语法参考 `fmt` 包。
121+
122+
T 类型内嵌了 common 类型,common 提供这一系列方法,我们经常会用到的(注意,这里说的测试中断,都是指当前测试函数):
123+
124+
1)当我们遇到一个断言错误的时候,标识这个测试失败,会使用到:
125+
126+
Fail: 测试失败,测试继续,也就是之后的代码依然会执行
127+
FailNow: 测试失败,测试中断
128+
129+
`FailNow ` 方法实现的内部,是通过调用 `runtime.Goexit()` 来中断测试的。
130+
131+
2)当我们遇到一个断言错误,只希望跳过这个错误,但是不希望标识测试失败,会使用到:
132+
133+
SkipNow: 跳过测试,测试中断
134+
135+
`SkipNow` 方法实现的内部,是通过调用 `runtime.Goexit()` 来中断测试的。
136+
137+
3)当我们只希望打印信息,会用到:
138+
139+
Log: 输出信息
140+
Logf: 输出格式化的信息
141+
142+
注意:默认情况下,单元测试成功时,它们打印的信息不会输出,可以通过加上 `-v` 选项,输出这些信息。但对于基准测试,它们总是会被输出。
143+
144+
4)当我们希望跳过这个测试,并且打印出信息,会用到:
145+
146+
Skip: 相当于 Log + SkipNow
147+
Skipf: 相当于 Logf + SkipNow
148+
149+
5)当我们希望断言失败的时候,标识测试失败,并打印出必要的信息,但是测试继续,会用到:
150+
151+
Error: 相当于 Log + Fail
152+
Errorf: 相当于 Logf + Fail
153+
154+
6)当我们希望断言失败的时候,标识测试失败,打印出必要的信息,但中断测试,会用到
155+
156+
Fatal: 相当于 Log + FailNow
157+
Fatalf: 相当于 Logf + FailNow
158+
159+
### Parallel 测试 ###
160+
161+
包中的 Parallel 方法用于表示当前测试只会与其他带有 Parallel 方法的测试并行进行测试。
162+
163+
下面的例子演示的 Parallel 的使用:
164+
165+
```go
166+
var (
167+
data = make(map[string]string)
168+
locker sync.RWMutex
169+
)
170+
171+
func WriteToMap(k, v string) {
172+
locker.Lock()
173+
defer locker.Unlock()
174+
data[k] = v
175+
}
176+
177+
func ReadFromMap(k string) string {
178+
locker.RLock()
179+
defer locker.RUnlock()
180+
return data[k]
181+
}
182+
```
183+
184+
测试代码:
185+
186+
```go
187+
var pairs = []struct {
188+
k string
189+
v string
190+
}{
191+
{"polaris", "徐新华"},
192+
{"studygolang", "Go语言中文网"},
193+
{"stdlib", "Go语言标准库"},
194+
{"polaris1", "徐新华1"},
195+
{"studygolang1", "Go语言中文网1"},
196+
{"stdlib1", "Go语言标准库1"},
197+
{"polaris2", "徐新华2"},
198+
{"studygolang2", "Go语言中文网2"},
199+
{"stdlib2", "Go语言标准库2"},
200+
{"polaris3", "徐新华3"},
201+
{"studygolang3", "Go语言中文网3"},
202+
{"stdlib3", "Go语言标准库3"},
203+
{"polaris4", "徐新华4"},
204+
{"studygolang4", "Go语言中文网4"},
205+
{"stdlib4", "Go语言标准库4"},
206+
}
207+
208+
// 注意 TestWriteToMap 需要在 TestReadFromMap 之前
209+
func TestWriteToMap(t *testing.T) {
210+
t.Parallel()
211+
for _, tt := range pairs {
212+
WriteToMap(tt.k, tt.v)
213+
}
214+
}
215+
216+
func TestReadFromMap(t *testing.T) {
217+
t.Parallel()
218+
for _, tt := range pairs {
219+
actual := ReadFromMap(tt.k)
220+
if actual != tt.v {
221+
t.Errorf("the value of key(%s) is %s, expected: %s", tt.k, actual, tt.v)
222+
}
223+
}
224+
}
225+
```
226+
试验步骤:
227+
228+
1. 注释掉 WriteToMap 和 ReadFromMap 中 locker 保护的代码,同时注释掉测试代码中的 t.Parallel,执行测试,测试通过,即使加上 `-race`,测试依然通过;
229+
2. 只注释掉 WriteToMap 和 ReadFromMap 中 locker 保护的代码,执行测试,测试失败(如果未失败,加上 `-race` 一定会失败);
230+
231+
如果代码能够进行并行测试,在写测试时,尽量加上 Parallel,这样可以测试出一些可能的问题。
232+
233+
关于 Parallel 的更多内容,会在 [子测试](chapter09/09.3.md) 中介绍。
2234

3235
当你写完一个函数,结构体,main之后,你下一步需要的就是测试了。testing包提供了很简单易用的测试包。
4236

@@ -120,4 +352,9 @@ T结构内部是继承自common结构,common结构提供集中方法,是我
120352
Fatalf : Logf + FailNow
121353

122354

355+
# 导航 #
356+
357+
- [第九章](/chapter09/09.0.md)
358+
- 下一节:[testing - 基准测试](09.2.md)
359+
123360

0 commit comments

Comments
 (0)