diff --git a/ch11/ch11-02.md b/ch11/ch11-02.md index 9ec84c2..14a7523 100644 --- a/ch11/ch11-02.md +++ b/ch11/ch11-02.md @@ -1,5 +1,141 @@ ## 11.2. 測試函數 + +每个测试函数必须导入 testing 包. 测试函数有如下的签名: + +```Go +func TestName(t *testing.T) { + // ... +} +``` + +测试函数的名字必须以Test开头, 可选的后缀名必须以大写字母开头: + +```Go +func TestSin(t *testing.T) { /* ... */ } +func TestCos(t *testing.T) { /* ... */ } +func TestLog(t *testing.T) { /* ... */ } +``` + +其中 t 参数用于报告测试失败和附件的日志信息. 让我们顶一个一个实例包 gopl.io/ch11/word1, 只有一个函数 IsPalindrome 用于检查一个字符串是否从前向后和从后向前读都一样. (这个实现对于一个字符串是否是回文字符串前后重复测试了两次; 我们稍后会再讨论这个问题.) + +```Go +gopl.io/ch11/word1 +// Package word provides utilities for word games. +package word + +// IsPalindrome reports whether s reads the same forward and backward. +// (Our first attempt.) +func IsPalindrome(s string) bool { + for i := range s { + if s[i] != s[len(s)-1-i] { + return false + } + } + return true +} +``` + +在相同的目录下, word_test.go 文件包含了 TestPalindrome 和 TestNonPalindrome 两个测试函数. 每一个都是测试 IsPalindrome 是否给出正确的结果, 并使用 t.Error 报告失败: + +```Go +package word + +import "testing" + +func TestPalindrome(t *testing.T) { + if !IsPalindrome("detartrated") { + t.Error(`IsPalindrome("detartrated") = false`) + } + if !IsPalindrome("kayak") { + t.Error(`IsPalindrome("kayak") = false`) + } +} + +func TestNonPalindrome(t *testing.T) { + if IsPalindrome("palindrome") { + t.Error(`IsPalindrome("palindrome") = true`) + } +} +``` + +`go test` (或 `go build`) 命令 如果没有参数指定包那么将默认采用当前目录对应的包. 我们可以用下面的命令构建和运行测试. + +``` +$ cd $GOPATH/src/gopl.io/ch11/word1 +$ go test +ok gopl.io/ch11/word1 0.008s +``` + +还比较满意, 我们运行了这个程序, 不过没有提前退出是因为还没有遇到BUG报告. 一个法国名为 Noelle Eve Elleon 的用户抱怨 IsPalindrome 函数不能识别 ‘‘été.’’. 另外一个来自美国中部用户的抱怨是不能识别 ‘‘A man, a plan, a canal: Panama.’’. 执行特殊和小的BUG报告为我们提供了新的更自然的测试用例. + +```Go +func TestFrenchPalindrome(t *testing.T) { + if !IsPalindrome("été") { + t.Error(`IsPalindrome("été") = false`) + } +} + +func TestCanalPalindrome(t *testing.T) { + input := "A man, a plan, a canal: Panama" + if !IsPalindrome(input) { + t.Errorf(`IsPalindrome(%q) = false`, input) + } +} +``` + +为了避免两次输入较长的字符串, 我们使用了提供了有类似 Printf 格式化功能的 Errorf 函数来汇报错误结果. + +当添加了这两个测试用例之后, `go test` 返回了测试失败的信息. + +``` +$ go test +--- FAIL: TestFrenchPalindrome (0.00s) + word_test.go:28: IsPalindrome("été") = false +--- FAIL: TestCanalPalindrome (0.00s) + word_test.go:35: IsPalindrome("A man, a plan, a canal: Panama") = false +FAIL +FAIL gopl.io/ch11/word1 0.014s +``` + +先编写测试用例并观察到测试用例触发了和用户报告的错误相同的描述是一个好的测试习惯. 只有这样, 我们才能定位我们要真正解决的问题. + +先写测试用例的另好处是, 运行测试通常会比手工描述报告的处理更快, 这让我们可以进行快速地迭代. 如果测试集有很多运行缓慢的测试, 我们可以通过只选择运行某些特定的测试来加快测试速度. + +参数 `-v` 用于打印每个测试函数的名字和运行时间: + +``` +$ go test -v +=== RUN TestPalindrome +--- PASS: TestPalindrome (0.00s) +=== RUN TestNonPalindrome +--- PASS: TestNonPalindrome (0.00s) +=== RUN TestFrenchPalindrome +--- FAIL: TestFrenchPalindrome (0.00s) + word_test.go:28: IsPalindrome("été") = false +=== RUN TestCanalPalindrome +--- FAIL: TestCanalPalindrome (0.00s) + word_test.go:35: IsPalindrome("A man, a plan, a canal: Panama") = false +FAIL +exit status 1 +FAIL gopl.io/ch11/word1 0.017s +``` + +参数 `-run` 是一个正则表达式, 只有测试函数名被它正确匹配的测试函数才会被 `go test` 运行: + +``` +$ go test -v -run="French|Canal" +=== RUN TestFrenchPalindrome +--- FAIL: TestFrenchPalindrome (0.00s) + word_test.go:28: IsPalindrome("été") = false +=== RUN TestCanalPalindrome +--- FAIL: TestCanalPalindrome (0.00s) + word_test.go:35: IsPalindrome("A man, a plan, a canal: Panama") = false +FAIL +exit status 1 +FAIL gopl.io/ch11/word1 0.014s +``` + TODO {% include "./ch11-02-1.md" %}