mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2026-01-15 19:57:14 +08:00
reduce file size
This commit is contained in:
@@ -25,10 +25,6 @@
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/plugins/gitbook-plugin-search/search.css">
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/plugins/gitbook-plugin-fontsettings/website.css">
|
||||
|
||||
|
||||
@@ -48,7 +44,7 @@
|
||||
<body>
|
||||
|
||||
|
||||
<div class="book" data-level="11.1" data-chapter-title="go test" data-filepath="ch11/ch11-01.md" data-basepath=".." data-revision="Thu Dec 24 2015 14:42:02 GMT+0800 (中国标准时间)">
|
||||
<div class="book" data-level="11.1" data-chapter-title="go test" data-filepath="ch11/ch11-01.md" data-basepath=".." data-revision="Fri Dec 25 2015 12:32:44 GMT+0800 (中国标准时间)">
|
||||
|
||||
|
||||
<div class="book-summary">
|
||||
@@ -2049,14 +2045,6 @@
|
||||
<script src="../gitbook/app.js"></script>
|
||||
|
||||
|
||||
<script src="../gitbook/plugins/gitbook-plugin-search/lunr.min.js"></script>
|
||||
|
||||
|
||||
|
||||
<script src="../gitbook/plugins/gitbook-plugin-search/search.js"></script>
|
||||
|
||||
|
||||
|
||||
<script src="../gitbook/plugins/gitbook-plugin-sharing/buttons.js"></script>
|
||||
|
||||
|
||||
@@ -2066,7 +2054,7 @@
|
||||
|
||||
<script>
|
||||
require(["gitbook"], function(gitbook) {
|
||||
var config = {"highlight":{},"search":{},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2}};
|
||||
var config = {"highlight":{},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2}};
|
||||
gitbook.start(config);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
### 11.2.1. 隨機測試
|
||||
|
||||
|
||||
表格驅動的測試便於構造基於精心挑選的測試數據的測試用例. 另一種測試思路是隨機測試, 也就是通過構造更廣泛的隨機輸入來測試探索函數的行爲.
|
||||
|
||||
那麽對於一個隨機的輸入, 我們如何能知道希望的輸齣結果呢? 這里有兩種策略. 第一個是編寫另一個函數, 使用簡單和清晰的算法, 雖然效率較低但是行爲和要測試的函數一致, 然後針對相同的隨機輸入檢査兩者的輸齣結果. 第二種是生成的隨機輸入的數據遵循特定的模式, 這樣我們就可以知道期望的輸齣的模式.
|
||||
|
||||
下面的例子使用的是第二種方法: randomPalindrome 函數用於隨機生成迴文字符串.
|
||||
|
||||
```Go
|
||||
import "math/rand"
|
||||
|
||||
// randomPalindrome returns a palindrome whose length and contents
|
||||
// are derived from the pseudo-random number generator rng.
|
||||
func randomPalindrome(rng *rand.Rand) string {
|
||||
n := rng.Intn(25) // random length up to 24
|
||||
runes := make([]rune, n)
|
||||
for i := 0; i < (n+1)/2; i++ {
|
||||
r := rune(rng.Intn(0x1000)) // random rune up to '\u0999'
|
||||
runes[i] = r
|
||||
runes[n-1-i] = r
|
||||
}
|
||||
return string(runes)
|
||||
}
|
||||
|
||||
func TestRandomPalindromes(t *testing.T) {
|
||||
// Initialize a pseudo-random number generator.
|
||||
seed := time.Now().UTC().UnixNano()
|
||||
t.Logf("Random seed: %d", seed)
|
||||
rng := rand.New(rand.NewSource(seed))
|
||||
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
p := randomPalindrome(rng)
|
||||
if !IsPalindrome(p) {
|
||||
t.Errorf("IsPalindrome(%q) = false", p)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
雖然隨機測試有不確定因素, 但是它也是至關重要的, 我們可以從失敗測試的日誌穫取足夠的信息. 在我們的例子中, 輸入 IsPalindrome 的 p 參數將告訴我們眞實的數據, 但是對於函數將接受更複雜的輸入, 不需要保存所有的輸入, 隻要日誌中簡單地記録隨機數種子卽可(像上面的方式). 有了這些隨機數初始化種子, 我們可以很容易脩改測試代碼以重現失敗的隨機測試.
|
||||
|
||||
通過使用當前時間作爲隨機種子, 在整個過程中的每次運行測試命令時都將探索新的隨機數據. 如果你使用的是定期運行的自動化測試集成繫統, 隨機測試將特别有價值.
|
||||
|
||||
**練習 11.3:** TestRandomPalindromes 隻測試了迴文字符串. 編寫新的隨機測試生成器, 用於測試隨機生成的非迴文字符串.
|
||||
|
||||
**練習 11.4:** 脩改 randomPalindrome 函數, 以探索 IsPalindrome 對標點和空格的處理.
|
||||
|
||||
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
### 11.2.2. 測試一個命令
|
||||
|
||||
|
||||
對於測試包 `go test` 是一個的有用的工具, 但是稍加努力我們也可以用它來測試可執行程序. 如果一個包的名字是 main, 那麽在構建時會生成一個可執行程序, 不過 main 包可以作爲一個包被測試器代碼導入.
|
||||
|
||||
讓我們爲 2.3.2節 的 echo 程序編寫一個測試. 我們先將程序拆分爲兩個函數: echo 函數完成眞正的工作, main 函數用於處理命令行輸入參數和echo可能返迴的錯誤.
|
||||
|
||||
```Go
|
||||
gopl.io/ch11/echo
|
||||
// Echo prints its command-line arguments.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
n = flag.Bool("n", false, "omit trailing newline")
|
||||
s = flag.String("s", " ", "separator")
|
||||
)
|
||||
|
||||
var out io.Writer = os.Stdout // modified during testing
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
if err := echo(!*n, *s, flag.Args()); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "echo: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func echo(newline bool, sep string, args []string) error {
|
||||
fmt.Fprint(out, strings.Join(args, sep))
|
||||
if newline {
|
||||
fmt.Fprintln(out)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
在測試中嗎我們可以用各種參數和標標誌調用 echo 函數, 然後檢測它的輸齣是否正確, 我們通過增加參數來減少 echo 函數對全局變量的依賴. 我們還增加了一個全局名爲 out 的變量來替代直接使用 os.Stdout, 這樣測試代碼可以根據需要將 out 脩改爲不同的對象以便於檢査. 下面就是 echo_test.go 文件中的測試代碼:
|
||||
|
||||
```Go
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEcho(t *testing.T) {
|
||||
var tests = []struct {
|
||||
newline bool
|
||||
sep string
|
||||
args []string
|
||||
want string
|
||||
}{
|
||||
{true, "", []string{}, "\n"},
|
||||
{false, "", []string{}, ""},
|
||||
{true, "\t", []string{"one", "two", "three"}, "one\ttwo\tthree\n"},
|
||||
{true, ",", []string{"a", "b", "c"}, "a,b,c\n"},
|
||||
{false, ":", []string{"1", "2", "3"}, "1:2:3"},
|
||||
}
|
||||
for _, test := range tests {
|
||||
descr := fmt.Sprintf("echo(%v, %q, %q)",
|
||||
test.newline, test.sep, test.args)
|
||||
|
||||
out = new(bytes.Buffer) // captured output
|
||||
if err := echo(test.newline, test.sep, test.args); err != nil {
|
||||
t.Errorf("%s failed: %v", descr, err)
|
||||
continue
|
||||
}
|
||||
got := out.(*bytes.Buffer).String()
|
||||
if got != test.want {
|
||||
t.Errorf("%s = %q, want %q", descr, got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
要註意的是測試代碼和産品代碼在同一個包. 雖然是main包, 也有對應的 main 入口函數, 但是在測試的時候 main 包隻是 TestEcho 測試函數導入的一個普通包, 里面 main 函數併沒有被導齣是被忽略的.
|
||||
|
||||
通過將測試放到表格中, 我們很容易添加新的測試用例. 讓我通過增加下面的測試用例來看看失敗的情況是怎麽樣的:
|
||||
|
||||
```Go
|
||||
{true, ",", []string{"a", "b", "c"}, "a b c\n"}, // NOTE: wrong expectation!
|
||||
```
|
||||
|
||||
`go test` 輸齣如下:
|
||||
|
||||
```
|
||||
$ go test gopl.io/ch11/echo
|
||||
--- FAIL: TestEcho (0.00s)
|
||||
echo_test.go:31: echo(true, ",", ["a" "b" "c"]) = "a,b,c", want "a b c\n"
|
||||
FAIL
|
||||
FAIL gopl.io/ch11/echo 0.006s
|
||||
```
|
||||
|
||||
錯誤信息描述了嚐試的操作(使用Go類似語法), 實際的行爲, 和期望的行爲. 通過這樣的錯誤信息, 你可以在檢視代碼之前就很容易定位錯誤的原因.
|
||||
|
||||
要註意的是在測試代碼中併沒有調用 log.Fatal 或 os.Exit, 因爲調用這類函數會導致程序提前退齣; 調用這些函數的特權應該放在 main 函數中. 如果眞的有以外的事情導致函數發送 panic, 測試驅動應該嚐試 recover, 然後將當前測試當作失敗處理. 如果是可預期的錯誤, 例如非法的用戶輸入, 找不到文件, 或配置文件不當等應該通過返迴一個非空的 error 的方式處理. 幸運的是(上面的意外隻是一個插麴), 我們的 echo 示例是比較簡單的也沒有需要返迴非空error的情況.
|
||||
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
### 11.2.3. 白盒測試
|
||||
|
||||
|
||||
一個測試分類的方法是基於測試者是否需要了解被測試對象的內部工作原理. 黑盒測試隻需要測試包公開的文檔和API行爲, 內部實現對測試代碼是透明的. 相反, 白盒測試有訪問包內部函數和數據結構的權限, 因此可以做到一下普通客戶端無法實現的測試. 例如, 一個飽和測試可以在每個操作之後檢測不變量的數據類型. (白盒測試隻是一個傳統的名稱, 其實稱爲 clear box 會更準確.)
|
||||
|
||||
黑盒和白盒這兩種測試方法是互補的. 黑盒測試一般更健壯, 隨着軟件實現的完善測試代碼很少需要更新. 它們可以幫助測試者了解眞是客戶的需求, 可以幫助發現API設計的一些不足之處. 相反, 白盒測試則可以對內部一些棘手的實現提供更多的測試覆蓋.
|
||||
|
||||
我們已經看到兩種測試的例子. TestIsPalindrome 測試僅僅使用導齣的 IsPalindrome 函數, 因此它是一個黑盒測試. TestEcho 測試則調用了內部的 echo 函數, 併且更新了內部的 out 全局變量, 這兩個都是未導齣的, 因此它是白盒測試.
|
||||
|
||||
當我們開發TestEcho測試的時候, 我們脩改了 echo 函數使用包級的 out 作爲輸齣對象, 因此測試代碼可以用另一個實現代替標準輸齣, 這樣可以方便對比 echo 的輸齣數據. 使用類似的技術, 我們可以將産品代碼的其他部分也替換爲一個容易測試的僞對象. 使用僞對象的好處是我們可以方便配置, 容易預測, 更可靠, 也更容易觀察. 同時也可以避免一些不良的副作用, 例如更新生産數據庫或信用卡消費行爲.
|
||||
|
||||
下面的代碼演示了爲用戶提供網絡存儲的web服務中的配額檢測邏輯. 當用戶使用了超過 90% 的存儲配額之後將發送提醒郵件.
|
||||
|
||||
```Go
|
||||
gopl.io/ch11/storage1
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/smtp"
|
||||
)
|
||||
|
||||
func bytesInUse(username string) int64 { return 0 /* ... */ }
|
||||
|
||||
// Email sender configuration.
|
||||
// NOTE: never put passwords in source code!
|
||||
const sender = "notifications@example.com"
|
||||
const password = "correcthorsebatterystaple"
|
||||
const hostname = "smtp.example.com"
|
||||
|
||||
const template = `Warning: you are using %d bytes of storage,
|
||||
%d%% of your quota.`
|
||||
|
||||
func CheckQuota(username string) {
|
||||
used := bytesInUse(username)
|
||||
const quota = 1000000000 // 1GB
|
||||
percent := 100 * used / quota
|
||||
if percent < 90 {
|
||||
return // OK
|
||||
}
|
||||
msg := fmt.Sprintf(template, used, percent)
|
||||
auth := smtp.PlainAuth("", sender, password, hostname)
|
||||
err := smtp.SendMail(hostname+":587", auth, sender,
|
||||
[]string{username}, []byte(msg))
|
||||
if err != nil {
|
||||
log.Printf("smtp.SendMail(%s) failed: %s", username, err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
我們想測試這個代碼, 但是我們併不希望發送眞實的郵件. 因此我們將郵件處理邏輯放到一個私有的 notifyUser 函數.
|
||||
|
||||
```Go
|
||||
gopl.io/ch11/storage2
|
||||
|
||||
var notifyUser = func(username, msg string) {
|
||||
auth := smtp.PlainAuth("", sender, password, hostname)
|
||||
err := smtp.SendMail(hostname+":587", auth, sender,
|
||||
[]string{username}, []byte(msg))
|
||||
if err != nil {
|
||||
log.Printf("smtp.SendEmail(%s) failed: %s", username, err)
|
||||
}
|
||||
}
|
||||
|
||||
func CheckQuota(username string) {
|
||||
used := bytesInUse(username)
|
||||
const quota = 1000000000 // 1GB
|
||||
percent := 100 * used / quota
|
||||
if percent < 90 {
|
||||
return // OK
|
||||
}
|
||||
msg := fmt.Sprintf(template, used, percent)
|
||||
notifyUser(username, msg)
|
||||
}
|
||||
```
|
||||
|
||||
現在我們可以在測試中用僞郵件發送函數替代眞實的郵件發送函數. 它隻是簡單記録要通知的用戶和郵件的內容.
|
||||
|
||||
```Go
|
||||
package storage
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
func TestCheckQuotaNotifiesUser(t *testing.T) {
|
||||
var notifiedUser, notifiedMsg string
|
||||
notifyUser = func(user, msg string) {
|
||||
notifiedUser, notifiedMsg = user, msg
|
||||
}
|
||||
|
||||
// ...simulate a 980MB-used condition...
|
||||
|
||||
const user = "joe@example.org"
|
||||
CheckQuota(user)
|
||||
if notifiedUser == "" && notifiedMsg == "" {
|
||||
t.Fatalf("notifyUser not called")
|
||||
}
|
||||
if notifiedUser != user {
|
||||
t.Errorf("wrong user (%s) notified, want %s",
|
||||
notifiedUser, user)
|
||||
}
|
||||
const wantSubstring = "98% of your quota"
|
||||
if !strings.Contains(notifiedMsg, wantSubstring) {
|
||||
t.Errorf("unexpected notification message <<%s>>, "+
|
||||
"want substring %q", notifiedMsg, wantSubstring)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
這里有一個問題: 當測試函數返迴後, CheckQuota 將不能正常工作, 因爲 notifyUsers 依然使用的是測試函數的僞發送郵件函數. (當更新全局對象的時候總會有這種風險.) 我們必鬚脩改測試代碼恢複 notifyUsers 原先的狀態以便後續其他的測試沒有影響, 要確保所有的執行路徑後都能恢複, 包括測試失敗或 panic 情形. 在這種情況下, 我們建議使用 defer 處理恢複的代碼.
|
||||
|
||||
```Go
|
||||
func TestCheckQuotaNotifiesUser(t *testing.T) {
|
||||
// Save and restore original notifyUser.
|
||||
saved := notifyUser
|
||||
defer func() { notifyUser = saved }()
|
||||
|
||||
// Install the test's fake notifyUser.
|
||||
var notifiedUser, notifiedMsg string
|
||||
notifyUser = func(user, msg string) {
|
||||
notifiedUser, notifiedMsg = user, msg
|
||||
}
|
||||
// ...rest of test...
|
||||
}
|
||||
```
|
||||
|
||||
這種處理模式可以用來暫時保存和恢複所有的全局變量, 包括命令行標誌參數, 調試選項, 和優化參數; 安裝和移除導致生産代碼産生一些調試信息的鉤子函數; 還有有些誘導生産代碼進入某些重要狀態的改變, 比如 超時, 錯誤, 甚至是一些刻意製造的併發行爲.
|
||||
|
||||
以這種方式使用全局變量是安全的, 因爲 go test 併不會同時併發地執行多個測試.
|
||||
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
### 11.2.4. 擴展測試包
|
||||
|
||||
考慮下這兩個包: net/url 包, 提供了 URL 解析的功能; net/http 包, 提供了web服務和HTTP客戶端的功能. 如我們所料, 上層的 net/http 包依賴下層的 net/url 包. 然後, net/url 包中的一個測試是演示不同URL和HTTP客戶端的交互行爲. 也就是説, 一個下層包的測試代碼導入了上層的包.
|
||||
|
||||

|
||||
|
||||
這樣的行爲在 net/url 包的測試代碼中會導致包的循環依賴, 正如 圖11.1中向上箭頭所示, 同時正如我們在 10.1節所説, Go語言規范是禁止包的循環依賴的.
|
||||
|
||||
我們可以通過測試擴展包的方式解決循環依賴的問題, 也就是在 net/url 包所在的目録聲明一個 url_test 測試擴展包. 其中測試擴展包名的 `_test` 後綴告訴 go test 工具它應該建立一個額外的包來運行測試. 我們將這個擴展測試包的導入路徑視作是 net/url_test 會更容易理解, 但實際上它併不能被其他任何包導入.
|
||||
|
||||
因爲測試擴展包是一個獨立的包, 因此可以導入測試代碼依賴的其他的輔助包; 包內的測試代碼可能無法做到. 在設計層面, 測試擴展包是在所以它依賴的包的上層, 正如 圖11.2所示.
|
||||
|
||||

|
||||
|
||||
通過迴避循環導入依賴, 擴展測試包可以更靈活的測試, 特别是集成測試(需要測試多個組件之間的交互), 可以像普通應用程序那樣自由地導入其他包.
|
||||
|
||||
我們可以用 go list 工具査看包對應目録中哪些Go源文件是産品代碼, 哪些是包內測試, 還哪些測試擴展包. 我們以 fmt 包作爲一個例子. GoFiles 表示産品代碼對應的Go源文件列表; 也就是 go build 命令要編譯的部分:
|
||||
|
||||
{% raw %}
|
||||
|
||||
```
|
||||
$ go list -f={{.GoFiles}} fmt
|
||||
[doc.go format.go print.go scan.go]
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
TestGoFiles 表示的是 fmt 包內部測試測試代碼, 以 _test.go 爲後綴文件名, 不過隻在測試時被構建:
|
||||
|
||||
{% raw %}
|
||||
|
||||
```
|
||||
$ go list -f={{.TestGoFiles}} fmt
|
||||
[export_test.go]
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
包的測試代碼通常都在這些文件中, 不過 fmt 包併非如此; 稍後我們再解釋 export_test.go 文件的作用.
|
||||
|
||||
XTestGoFiles 表示的是屬於測試擴展包的測試代碼, 也就是 fmt_test 包, 因此它們必鬚先導入 fmt 包. 同樣, 這些文件也隻是在測試時被構建運行:
|
||||
|
||||
|
||||
{% raw %}
|
||||
|
||||
```
|
||||
$ go list -f={{.XTestGoFiles}} fmt
|
||||
[fmt_test.go scan_test.go stringer_test.go]
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
有時候測試擴展包需要訪問被測試包內部的代碼, 例如在一個爲了避免循環導入而被獨立到外部測試擴展包的白盒測試. 在這種情況下, 我們可以通過一些技巧解決: 我們在包內的一個 _test.go 文件中導齣一個內部的實現給測試擴展包. 因爲這些代碼隻有在測試時纔需要, 因此一般放在 export_test.go 文件中.
|
||||
|
||||
例如, fmt 包的 fmt.Scanf 需要 unicode.IsSpace 函數提供的功能. 但是爲了避免太多的依賴, fmt 包併沒有導入包含鉅大表格數據的 unicode 包; 相反fmt包有一個叫 isSpace 內部的簡易實現.
|
||||
|
||||
爲了確保 fmt.isSpace 和 unicode.IsSpace 函數的行爲一致, fmt 包謹慎地包含了一個測試. 是一個在測試擴展包內的測試, 因此是無法直接訪問到 isSpace 內部函數的, 因此 fmt 通過一個祕密齣口導齣了 isSpace 函數. export_test.go 文件就是專門用於測試擴展包的祕密齣口.
|
||||
|
||||
```Go
|
||||
package fmt
|
||||
|
||||
var IsSpace = isSpace
|
||||
```
|
||||
|
||||
這個測試文件併沒有定義測試代碼; 它隻是通過 fmt.IsSpace 簡單導齣了內部的 isSpace 函數, 提供給測試擴展包使用. 這個技巧可以廣泛用於位於測試擴展包的白盒測試.
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
### 11.2.5. 編寫有效的測試
|
||||
|
||||
|
||||
許多Go新人會驚異與它的極簡的測試框架. 很多其他語言的測試框架都提供了識别測試函數的機製(通常使用反射或元數據), 通過設置一些 ‘‘setup’’ 和 ‘‘teardown’’ 的鉤子函數來執行測試用例運行的初始化或之後的清理操作, 同時測試工具箱還提供了很多類似assert斷言, 比較值, 格式化輸齣錯誤信息和停止一個識别的測試等輔助函數(通常使用異常機製). 雖然這些機製可以使得測試非常簡潔, 但是測試輸齣的日誌卻像火星文一般難以理解. 此外, 雖然測試最終也會輸齣 PASS 或 FAIL 的報告, 但是它們提供的信息格式卻非常不利於代碼維護者快速定位問題, 因爲失敗的信息的具體含義是非常隱患的, 比如 "assert: 0 == 1" 或 成頁的海量跟蹤日誌.
|
||||
|
||||
Go語言的測試風格則形成鮮明對比. 它期望測試者自己完成大部分的工作, 定義函數避免重複, 就像普通編程那樣. 編寫測試併不是一個機械的填充過程; 一個測試也有自己的接口, 盡管它的維護者也是測試僅有的一個用戶. 一個好的測試不應該引發其他無關的錯誤信息, 它隻要清晰簡潔地描述問題的癥狀卽可, 有時候可能還需要一些上下文信息. 在理想情況下, 維護者可以在不看代碼的情況下就能根據錯誤信息定位錯誤産生的原因. 一個好的測試不應該在遇到一點小錯誤就立刻退齣測試, 它應該嚐試報告更多的測試, 因此我們可能從多個失敗測試的模式中發現錯誤産生的規律.
|
||||
|
||||
下面的斷言函數比較兩個值, 然後生成一個通用的錯誤信息, 併停止程序. 它很方便使用也確實有效果, 但是當識别的時候, 錯誤時打印的信息幾乎是沒有價值的. 它併沒有爲解決問題提供一個很好的入口.
|
||||
|
||||
```Go
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
// A poor assertion function.
|
||||
func assertEqual(x, y int) {
|
||||
if x != y {
|
||||
panic(fmt.Sprintf("%d != %d", x, y))
|
||||
}
|
||||
}
|
||||
func TestSplit(t *testing.T) {
|
||||
words := strings.Split("a:b:c", ":")
|
||||
assertEqual(len(words), 3)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
從這個意義上説, 斷言函數犯了過早抽象的錯誤: 僅僅測試兩個整數是否相同, 而放棄了根據上下文提供更有意義的錯誤信息的做法. 我們可以根據具體的錯誤打印一個更有價值的錯誤信息, 就像下面例子那樣. 測試在隻有一次重複的模式齣現時引入抽象.
|
||||
|
||||
```Go
|
||||
func TestSplit(t *testing.T) {
|
||||
s, sep := "a:b:c", ":"
|
||||
words := strings.Split(s, sep)
|
||||
if got, want := len(words), 3; got != want {
|
||||
t.Errorf("Split(%q, %q) returned %d words, want %d",
|
||||
s, sep, got, want)
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
現在的測試不僅報告了調用的具體函數, 它的輸入, 和結果的意義; 併且打印的眞實返迴的值和期望返迴的值; 併且卽使斷言失敗依然會繼續嚐試運行更多的測試. 一旦我們寫了這樣結構的測試, 下一步自然不是用更多的if語句來擴展測試用例, 我們可以用像 IsPalindrome 的表驅動測試那樣來準備更多的 s, sep 測試用例.
|
||||
|
||||
前面的例子併不需要額外的輔助函數, 如果如果有可以使測試代碼更簡單的方法我們也樂意接受. (我們將在 13.3節 看到一個 reflect.DeepEqual 輔助函數.) 開始一個好的測試的關鍵是通過實現你眞正想要的具體行爲, 然後纔是考慮然後簡化測試代碼. 最好的結果是直接從庫的抽象接口開始, 針對公共接口編寫一些測試函數.
|
||||
|
||||
**練習11.5:** 用表格驅動的技術擴展TestSplit測試, 併打印期望的輸齣結果.
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
### 11.2.6. 避免的不穩定的測試
|
||||
|
||||
如果一個應用程序對於新齣現的但有效的輸入經常失敗説明程序不夠穩健; 同樣如果一個測試僅僅因爲聲音變化就會導致失敗也是不合邏輯的. 就像一個不夠穩健的程序會挫敗它的用戶一樣, 一個脆弱性測試同樣會激怒它的維護者. 最脆弱的測試代碼會在程序沒有任何變化的時候産生不同的結果, 時好時壞, 處理它們會耗費大量的時間但是併不會得到任何好處.
|
||||
|
||||
當一個測試函數産生一個複雜的輸齣如一個很長的字符串, 或一個精心設計的數據結構, 或一個文件, 它可以用於和預設的‘‘golden’’結果數據對比, 用這種簡單方式寫測試是誘人的. 但是隨着項目的發展, 輸齣的某些部分很可能會發生變化, 盡管很可能是一個改進的實現導致的. 而且不僅僅是輸齣部分, 函數複雜複製的輸入部分可能也跟着變化了, 因此測試使用的輸入也就不在有效了.
|
||||
|
||||
避免脆弱測試代碼的方法是隻檢測你眞正關心的屬性. 保存測試代碼的簡潔和內部結構的穩定. 特别是對斷言部分要有所選擇. 不要檢査字符串的全匹配, 但是尋找相關的子字符串, 因爲某些子字符串在項目的發展中是比較穩定不變的. 通常編寫一個重複雜的輸齣中提取必要精華信息以用於斷言是值得的, 雖然這可能會帶來很多前期的工作, 但是它可以幫助迅速及時脩複因爲項目演化而導致的不合邏輯的失敗測試.
|
||||
|
||||
@@ -25,10 +25,6 @@
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/plugins/gitbook-plugin-search/search.css">
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/plugins/gitbook-plugin-fontsettings/website.css">
|
||||
|
||||
|
||||
@@ -48,7 +44,7 @@
|
||||
<body>
|
||||
|
||||
|
||||
<div class="book" data-level="11.2" data-chapter-title="測試函數" data-filepath="ch11/ch11-02.md" data-basepath=".." data-revision="Thu Dec 24 2015 14:42:02 GMT+0800 (中国标准时间)">
|
||||
<div class="book" data-level="11.2" data-chapter-title="測試函數" data-filepath="ch11/ch11-02.md" data-basepath=".." data-revision="Fri Dec 25 2015 12:32:44 GMT+0800 (中国标准时间)">
|
||||
|
||||
|
||||
<div class="book-summary">
|
||||
@@ -2513,14 +2509,6 @@ FAIL gopl.io/ch11/echo 0.006s
|
||||
<script src="../gitbook/app.js"></script>
|
||||
|
||||
|
||||
<script src="../gitbook/plugins/gitbook-plugin-search/lunr.min.js"></script>
|
||||
|
||||
|
||||
|
||||
<script src="../gitbook/plugins/gitbook-plugin-search/search.js"></script>
|
||||
|
||||
|
||||
|
||||
<script src="../gitbook/plugins/gitbook-plugin-sharing/buttons.js"></script>
|
||||
|
||||
|
||||
@@ -2530,7 +2518,7 @@ FAIL gopl.io/ch11/echo 0.006s
|
||||
|
||||
<script>
|
||||
require(["gitbook"], function(gitbook) {
|
||||
var config = {"highlight":{},"search":{},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2}};
|
||||
var config = {"highlight":{},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2}};
|
||||
gitbook.start(config);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -25,10 +25,6 @@
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/plugins/gitbook-plugin-search/search.css">
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/plugins/gitbook-plugin-fontsettings/website.css">
|
||||
|
||||
|
||||
@@ -48,7 +44,7 @@
|
||||
<body>
|
||||
|
||||
|
||||
<div class="book" data-level="11.3" data-chapter-title="測試覆蓋率" data-filepath="ch11/ch11-03.md" data-basepath=".." data-revision="Thu Dec 24 2015 14:42:02 GMT+0800 (中国标准时间)">
|
||||
<div class="book" data-level="11.3" data-chapter-title="測試覆蓋率" data-filepath="ch11/ch11-03.md" data-basepath=".." data-revision="Fri Dec 25 2015 12:32:44 GMT+0800 (中国标准时间)">
|
||||
|
||||
|
||||
<div class="book-summary">
|
||||
@@ -2115,14 +2111,6 @@ ok gopl.io/ch7/eval 0.032s coverage: 68.5% of statements
|
||||
<script src="../gitbook/app.js"></script>
|
||||
|
||||
|
||||
<script src="../gitbook/plugins/gitbook-plugin-search/lunr.min.js"></script>
|
||||
|
||||
|
||||
|
||||
<script src="../gitbook/plugins/gitbook-plugin-search/search.js"></script>
|
||||
|
||||
|
||||
|
||||
<script src="../gitbook/plugins/gitbook-plugin-sharing/buttons.js"></script>
|
||||
|
||||
|
||||
@@ -2132,7 +2120,7 @@ ok gopl.io/ch7/eval 0.032s coverage: 68.5% of statements
|
||||
|
||||
<script>
|
||||
require(["gitbook"], function(gitbook) {
|
||||
var config = {"highlight":{},"search":{},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2}};
|
||||
var config = {"highlight":{},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2}};
|
||||
gitbook.start(config);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -25,10 +25,6 @@
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/plugins/gitbook-plugin-search/search.css">
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/plugins/gitbook-plugin-fontsettings/website.css">
|
||||
|
||||
|
||||
@@ -48,7 +44,7 @@
|
||||
<body>
|
||||
|
||||
|
||||
<div class="book" data-level="11.4" data-chapter-title="基準測試" data-filepath="ch11/ch11-04.md" data-basepath=".." data-revision="Thu Dec 24 2015 14:42:02 GMT+0800 (中国标准时间)">
|
||||
<div class="book" data-level="11.4" data-chapter-title="基準測試" data-filepath="ch11/ch11-04.md" data-basepath=".." data-revision="Fri Dec 25 2015 12:32:44 GMT+0800 (中国标准时间)">
|
||||
|
||||
|
||||
<div class="book-summary">
|
||||
@@ -2112,14 +2108,6 @@ BenchmarkIsPalindrome 2000000 807 ns/op 128 B/op 1 allocs/op
|
||||
<script src="../gitbook/app.js"></script>
|
||||
|
||||
|
||||
<script src="../gitbook/plugins/gitbook-plugin-search/lunr.min.js"></script>
|
||||
|
||||
|
||||
|
||||
<script src="../gitbook/plugins/gitbook-plugin-search/search.js"></script>
|
||||
|
||||
|
||||
|
||||
<script src="../gitbook/plugins/gitbook-plugin-sharing/buttons.js"></script>
|
||||
|
||||
|
||||
@@ -2129,7 +2117,7 @@ BenchmarkIsPalindrome 2000000 807 ns/op 128 B/op 1 allocs/op
|
||||
|
||||
<script>
|
||||
require(["gitbook"], function(gitbook) {
|
||||
var config = {"highlight":{},"search":{},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2}};
|
||||
var config = {"highlight":{},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2}};
|
||||
gitbook.start(config);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -25,10 +25,6 @@
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/plugins/gitbook-plugin-search/search.css">
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/plugins/gitbook-plugin-fontsettings/website.css">
|
||||
|
||||
|
||||
@@ -48,7 +44,7 @@
|
||||
<body>
|
||||
|
||||
|
||||
<div class="book" data-level="11.5" data-chapter-title="剖析" data-filepath="ch11/ch11-05.md" data-basepath=".." data-revision="Thu Dec 24 2015 14:42:02 GMT+0800 (中国标准时间)">
|
||||
<div class="book" data-level="11.5" data-chapter-title="剖析" data-filepath="ch11/ch11-05.md" data-basepath=".." data-revision="Fri Dec 25 2015 12:32:44 GMT+0800 (中国标准时间)">
|
||||
|
||||
|
||||
<div class="book-summary">
|
||||
@@ -2090,14 +2086,6 @@ Showing top 10 nodes out of 166 (cum >= 60ms)
|
||||
<script src="../gitbook/app.js"></script>
|
||||
|
||||
|
||||
<script src="../gitbook/plugins/gitbook-plugin-search/lunr.min.js"></script>
|
||||
|
||||
|
||||
|
||||
<script src="../gitbook/plugins/gitbook-plugin-search/search.js"></script>
|
||||
|
||||
|
||||
|
||||
<script src="../gitbook/plugins/gitbook-plugin-sharing/buttons.js"></script>
|
||||
|
||||
|
||||
@@ -2107,7 +2095,7 @@ Showing top 10 nodes out of 166 (cum >= 60ms)
|
||||
|
||||
<script>
|
||||
require(["gitbook"], function(gitbook) {
|
||||
var config = {"highlight":{},"search":{},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2}};
|
||||
var config = {"highlight":{},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2}};
|
||||
gitbook.start(config);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -25,10 +25,6 @@
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/plugins/gitbook-plugin-search/search.css">
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/plugins/gitbook-plugin-fontsettings/website.css">
|
||||
|
||||
|
||||
@@ -48,7 +44,7 @@
|
||||
<body>
|
||||
|
||||
|
||||
<div class="book" data-level="11.6" data-chapter-title="示例函數" data-filepath="ch11/ch11-06.md" data-basepath=".." data-revision="Thu Dec 24 2015 14:42:02 GMT+0800 (中国标准时间)">
|
||||
<div class="book" data-level="11.6" data-chapter-title="示例函數" data-filepath="ch11/ch11-06.md" data-basepath=".." data-revision="Fri Dec 25 2015 12:32:44 GMT+0800 (中国标准时间)">
|
||||
|
||||
|
||||
<div class="book-summary">
|
||||
@@ -2061,14 +2057,6 @@
|
||||
<script src="../gitbook/app.js"></script>
|
||||
|
||||
|
||||
<script src="../gitbook/plugins/gitbook-plugin-search/lunr.min.js"></script>
|
||||
|
||||
|
||||
|
||||
<script src="../gitbook/plugins/gitbook-plugin-search/search.js"></script>
|
||||
|
||||
|
||||
|
||||
<script src="../gitbook/plugins/gitbook-plugin-sharing/buttons.js"></script>
|
||||
|
||||
|
||||
@@ -2078,7 +2066,7 @@
|
||||
|
||||
<script>
|
||||
require(["gitbook"], function(gitbook) {
|
||||
var config = {"highlight":{},"search":{},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2}};
|
||||
var config = {"highlight":{},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2}};
|
||||
gitbook.start(config);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -25,10 +25,6 @@
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/plugins/gitbook-plugin-search/search.css">
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../gitbook/plugins/gitbook-plugin-fontsettings/website.css">
|
||||
|
||||
|
||||
@@ -48,7 +44,7 @@
|
||||
<body>
|
||||
|
||||
|
||||
<div class="book" data-level="11" data-chapter-title="測試" data-filepath="ch11/ch11.md" data-basepath=".." data-revision="Thu Dec 24 2015 14:42:02 GMT+0800 (中国标准时间)">
|
||||
<div class="book" data-level="11" data-chapter-title="測試" data-filepath="ch11/ch11.md" data-basepath=".." data-revision="Fri Dec 25 2015 12:32:44 GMT+0800 (中国标准时间)">
|
||||
|
||||
|
||||
<div class="book-summary">
|
||||
@@ -2052,14 +2048,6 @@
|
||||
<script src="../gitbook/app.js"></script>
|
||||
|
||||
|
||||
<script src="../gitbook/plugins/gitbook-plugin-search/lunr.min.js"></script>
|
||||
|
||||
|
||||
|
||||
<script src="../gitbook/plugins/gitbook-plugin-search/search.js"></script>
|
||||
|
||||
|
||||
|
||||
<script src="../gitbook/plugins/gitbook-plugin-sharing/buttons.js"></script>
|
||||
|
||||
|
||||
@@ -2069,7 +2057,7 @@
|
||||
|
||||
<script>
|
||||
require(["gitbook"], function(gitbook) {
|
||||
var config = {"highlight":{},"search":{},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2}};
|
||||
var config = {"highlight":{},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2}};
|
||||
gitbook.start(config);
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user