From 28dd62b2c2a9fd58cf1cbf16a5fcb61087b79258 Mon Sep 17 00:00:00 2001 From: chai2010 Date: Sat, 12 Dec 2015 16:07:04 +0800 Subject: [PATCH] Update ch11-02.md --- ch11/ch11-02.md | 82 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/ch11/ch11-02.md b/ch11/ch11-02.md index 14a7523..0ba5b8f 100644 --- a/ch11/ch11-02.md +++ b/ch11/ch11-02.md @@ -136,7 +136,87 @@ exit status 1 FAIL gopl.io/ch11/word1 0.014s ``` -TODO + +当然, 一旦我们已经修复了失败的测试用例, 在我们提交代码更新之前, 我们应该以不带参数的 `go test` 命令运行全部的测试用例, 以确保更新没有引入新的问题. + +我们现在的任务就是修复这些错误. 简要分析后发现第一个BUG的原因是我们采用了 byte 而不是 rune 序列, 所以像 "été" 中的 é 等非 ASCII 字符不能正确处理. 第二个BUG是因为没有忽略空格和字母的大小写导致的. + +针对上述两个BUG, 我们仔细重写了函数: + +```Go +gopl.io/ch11/word2 +// Package word provides utilities for word games. +package word + +import "unicode" + +// IsPalindrome reports whether s reads the same forward and backward. +// Letter case is ignored, as are non-letters. +func IsPalindrome(s string) bool { + var letters []rune + for _, r := range s { + if unicode.IsLetter(r) { + letters = append(letters, unicode.ToLower(r)) + } + } + for i := range letters { + if letters[i] != letters[len(letters)-1-i] { + return false + } + } + return true +} +``` + +同时我们也将之前的所有测试数据合并到了一个测试中的表格中. + +```Go +func TestIsPalindrome(t *testing.T) { + var tests = []struct { + input string + want bool + }{ + {"", true}, + {"a", true}, + {"aa", true}, + {"ab", false}, + {"kayak", true}, + {"detartrated", true}, + {"A man, a plan, a canal: Panama", true}, + {"Evil I did dwell; lewd did I live.", true}, + {"Able was I ere I saw Elba", true}, + {"été", true}, + {"Et se resservir, ivresse reste.", true}, + {"palindrome", false}, // non-palindrome + {"desserts", false}, // semi-palindrome + } + for _, test := range tests { + if got := IsPalindrome(test.input); got != test.want { + t.Errorf("IsPalindrome(%q) = %v", test.input, got) + } + } +} +``` + +我们的新测试阿都通过了: + +``` +$ go test gopl.io/ch11/word2 +ok gopl.io/ch11/word2 0.015s +``` + +这种表格驱动的测试在Go中很常见的. 我们很容易想表格添加新的测试数据, 并且后面的测试逻辑也没有冗余, 这样我们可以更好地完善错误信息. + +失败的测试的输出并不包括调用 t.Errorf 时刻的堆栈调用信息. 不像其他语言或测试框架的 assert 断言, t.Errorf 调用也没有引起 panic 或停止测试的执行. 即使表格中前面的数据导致了测试的失败, 表格后面的测试数据依然会运行测试, 因此在一个测试中我们可能了解多个失败的信息. + +如果我们真的需要停止测试, 或许是因为初始化失败或可能是早先的错误导致了后续错误等原因, 我们可以使用 t.Fatal 或 t.Fatalf 停止测试. 它们必须在和测试函数同一个 goroutine 内调用. + +测试失败的信息一般的形式是 "f(x) = y, want z", f(x) 解释了失败的操作和对应的输出, y 是实际的运行结果, z 是期望的正确的结果. 就像前面检查回文字符串的例子, 实际的函数用于 f(x) 部分. 如果显示 x 是表格驱动型测试中比较重要的部分, 因为同一个断言可能对应不同的表格项执行多次. 要避免无用和冗余的信息. 在测试类似 IsPalindrome 返回布尔类型的函数时, 可以忽略并没有额外信息的 z 部分. 如果 x, y 或 z 是 y 的长度, 输出一个相关部分的简明总结即可. 测试的作者应该要努力帮助程序员诊断失败的测试. + +**练习 11.1:** 为 4.3节 中的 charcount 程序编写测试. + +**练习 11.2:** 为 (§6.5)的 IntSet 编写一组测试, 用于检查每个操作后的行为和基于内置 map 的集合等价 , 后面 练习11.7 将会用到. + {% include "./ch11-02-1.md" %}