mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2024-12-25 14:28:58 +00:00
make
This commit is contained in:
parent
3e80d25c15
commit
c187ed90f8
@ -1,9 +1,9 @@
|
|||||||
### 11.2.2. 測試一個命令
|
### 11.2.2. 測試一個命令
|
||||||
|
|
||||||
|
|
||||||
对于测试包 `go test` 是一个的有用的工具, 但是稍加努力我们也可以用它来测试可执行程序. 如果一个包的名字是 main, 那么在构建时会生成一个可执行程序, 不过 main 包可以作为一个包被测试器代码导入.
|
對於測試包 `go test` 是一個的有用的工具, 但是稍加努力我們也可以用它來測試可執行程序. 如果一個包的名字是 main, 那麽在構建時會生成一個可執行程序, 不過 main 包可以作為一個包被測試器代碼導入.
|
||||||
|
|
||||||
让我们为 2.3.2节 的 echo 程序编写一个测试. 我们先将程序拆分为两个函数: echo 函数完成真正的工作, main 函数用于处理命令行输入参数和echo可能返回的错误.
|
讓我們為 2.3.2節 的 echo 程序編寫一個測試. 我們先將程序拆分為兩個函數: echo 函數完成眞正的工作, main 函數用於處理命令行輸入參數和echo可能返迴的錯誤.
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
gopl.io/ch11/echo
|
gopl.io/ch11/echo
|
||||||
@ -42,7 +42,7 @@ func echo(newline bool, sep string, args []string) error {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
在测试中吗我们可以用各种参数和标标志调用 echo 函数, 然后检测它的输出是否正确, 我们通过增加参数来减少 echo 函数对全局变量的依赖. 我们还增加了一个全局名为 out 的变量来替代直接使用 os.Stdout, 这样测试代码可以根据需要将 out 修改为不同的对象以便于检查. 下面就是 echo_test.go 文件中的测试代码:
|
在測試中嗎我們可以用各種參數和標標誌調用 echo 函數, 然後檢測它的輸齣是否正確, 我們通過增加參數來減少 echo 函數對全侷變量的依賴. 我們還增加了一個全侷名為 out 的變量來替代直接使用 os.Stdout, 這樣測試代碼可以根據需要將 out 脩改為不衕的對象以便於檢査. 下麪就是 echo_test.go 文件中的測試代碼:
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
package main
|
package main
|
||||||
@ -83,15 +83,15 @@ func TestEcho(t *testing.T) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
要注意的是测试代码和产品代码在同一个包. 虽然是main包, 也有对应的 main 入口函数, 但是在测试的时候 main 包只是 TestEcho 测试函数导入的一个普通包, 里面 main 函数并没有被导出是被忽略的.
|
要註意的是測試代碼和產品代碼在衕一個包. 雖然是main包, 也有對應的 main 入口函數, 但是在測試的時候 main 包隻是 TestEcho 測試函數導入的一個普通包, 裡麪 main 函數並沒有被導齣是被忽略的.
|
||||||
|
|
||||||
通过将测试放到表格中, 我们很容易添加新的测试用例. 让我通过增加下面的测试用例来看看失败的情况是怎么样的:
|
通過將測試放到錶格中, 我們很容易添加新的測試用例. 讓我通過增加下麪的測試用例來看看失敗的情況是怎麽樣的:
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
{true, ",", []string{"a", "b", "c"}, "a b c\n"}, // NOTE: wrong expectation!
|
{true, ",", []string{"a", "b", "c"}, "a b c\n"}, // NOTE: wrong expectation!
|
||||||
```
|
```
|
||||||
|
|
||||||
`go test` 输出如下:
|
`go test` 輸齣如下:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ go test gopl.io/ch11/echo
|
$ go test gopl.io/ch11/echo
|
||||||
@ -101,8 +101,8 @@ FAIL
|
|||||||
FAIL gopl.io/ch11/echo 0.006s
|
FAIL gopl.io/ch11/echo 0.006s
|
||||||
```
|
```
|
||||||
|
|
||||||
错误信息描述了尝试的操作(使用Go类似语法), 实际的行为, 和期望的行为. 通过这样的错误信息, 你可以在检视代码之前就很容易定位错误的原因.
|
錯誤信息描述了嘗試的操作(使用Go類似語法), 實際的行為, 和期望的行為. 通過這樣的錯誤信息, 你可以在檢視代碼之前就很容易定位錯誤的原因.
|
||||||
|
|
||||||
要注意的是在测试代码中并没有调用 log.Fatal 或 os.Exit, 因为调用这类函数会导致程序提前退出; 调用这些函数的特权应该放在 main 函数中. 如果真的有以外的事情导致函数发送 panic, 测试驱动应该尝试 recover, 然后将当前测试当作失败处理. 如果是可预期的错误, 例如非法的用户输入, 找不到文件, 或配置文件不当等应该通过返回一个非空的 error 的方式处理. 幸运的是(上面的意外只是一个插曲), 我们的 echo 示例是比较简单的也没有需要返回非空error的情况.
|
要註意的是在測試代碼中並沒有調用 log.Fatal 或 os.Exit, 因為調用這類函數會導緻程序提前退齣; 調用這些函數的特權應該放在 main 函數中. 如果眞的有以外的事情導緻函數發送 panic, 測試驅動應該嘗試 recover, 然後將噹前測試噹作失敗處理. 如果是可預期的錯誤, 例如非法的用戶輸入, 找不到文件, 或配置文件不噹等應該通過返迴一個非空的 error 的方式處理. 倖運的是(上麪的意外隻是一個插麯), 我們的 echo 示例是比較簡單的也沒有需要返迴非空error的情況.
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
### 11.2.3. 白盒測試
|
### 11.2.3. 白盒測試
|
||||||
|
|
||||||
|
|
||||||
一个测试分类的方法是基于测试者是否需要了解被测试对象的内部工作原理. 黑盒测试只需要测试包公开的文档和API行为, 内部实现对测试代码是透明的. 相反, 白盒测试有访问包内部函数和数据结构的权限, 因此可以做到一下普通客户端无法实现的测试. 例如, 一个饱和测试可以在每个操作之后检测不变量的数据类型. (白盒测试只是一个传统的名称, 其实称为 clear box 会更准确.)
|
一個測試分類的方法是基於測試者是否需要了解被測試對象的內部工作原理. 黑盒測試隻需要測試包公開的文檔和API行為, 內部實現對測試代碼是透明的. 相反, 白盒測試有訪問包內部函數和數據結構的權限, 因此可以做到一下普通客戶端無法實現的測試. 例如, 一個飽和測試可以在每個操作之後檢測不變量的數據類型. (白盒測試隻是一個傳統的名稱, 其實稱為 clear box 會更準確.)
|
||||||
|
|
||||||
黑盒和白盒这两种测试方法是互补的. 黑盒测试一般更健壮, 随着软件实现的完善测试代码很少需要更新. 它们可以帮助测试者了解真是客户的需求, 可以帮助发现API设计的一些不足之处. 相反, 白盒测试则可以对内部一些棘手的实现提供更多的测试覆盖.
|
黑盒和白盒這兩種測試方法是互補的. 黑盒測試一般更健壯, 隨着軟件實現的完善測試代碼很少需要更新. 它們可以幫助測試者了解眞是客戶的需求, 可以幫助發現API設計的一些不足之處. 相反, 白盒測試則可以對內部一些棘手的實現提供更多的測試覆蓋.
|
||||||
|
|
||||||
我们已经看到两种测试的例子. TestIsPalindrome 测试仅仅使用导出的 IsPalindrome 函数, 因此它是一个黑盒测试. TestEcho 测试则调用了内部的 echo 函数, 并且更新了内部的 out 全局变量, 这两个都是未导出的, 因此它是白盒测试.
|
我們已經看到兩種測試的例子. TestIsPalindrome 測試僅僅使用導齣的 IsPalindrome 函數, 因此它是一個黑盒測試. TestEcho 測試則調用了內部的 echo 函數, 並且更新了內部的 out 全侷變量, 這兩個都是未導齣的, 因此它是白盒測試.
|
||||||
|
|
||||||
当我们开发TestEcho测试的时候, 我们修改了 echo 函数使用包级的 out 作为输出对象, 因此测试代码可以用另一个实现代替标准输出, 这样可以方便对比 echo 的输出数据. 使用类似的技术, 我们可以将产品代码的其他部分也替换为一个容易测试的伪对象. 使用伪对象的好处是我们可以方便配置, 容易预测, 更可靠, 也更容易观察. 同时也可以避免一些不良的副作用, 例如更新生产数据库或信用卡消费行为.
|
噹我們開發TestEcho測試的時候, 我們脩改了 echo 函數使用包級的 out 作為輸齣對象, 因此測試代碼可以用另一個實現代替標準輸齣, 這樣可以方便對比 echo 的輸齣數據. 使用類似的技術, 我們可以將產品代碼的其他部分也替換為一個容易測試的僞對象. 使用僞對象的好處是我們可以方便配置, 容易預測, 更可靠, 也更容易觀察. 衕時也可以避免一些不良的副作用, 例如更新生產數據庫或信用卡消費行為.
|
||||||
|
|
||||||
下面的代码演示了为用户提供网络存储的web服务中的配额检测逻辑. 当用户使用了超过 90% 的存储配额之后将发送提醒邮件.
|
下麪的代碼演示了為用戶提供網絡存儲的web服務中的配額檢測邏輯. 噹用戶使用了超過 90% 的存儲配額之後將發送提醒郵件.
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
gopl.io/ch11/storage1
|
gopl.io/ch11/storage1
|
||||||
@ -50,7 +50,7 @@ func CheckQuota(username string) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
我们想测试这个代码, 但是我们并不希望发送真实的邮件. 因此我们将邮件处理逻辑放到一个私有的 notifyUser 函数.
|
我們想測試這個代碼, 但是我們並不希望發送眞實的郵件. 因此我們將郵件處理邏輯放到一個俬有的 notifyUser 函數.
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
gopl.io/ch11/storage2
|
gopl.io/ch11/storage2
|
||||||
@ -76,7 +76,7 @@ func CheckQuota(username string) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
现在我们可以在测试中用伪邮件发送函数替代真实的邮件发送函数. 它只是简单记录要通知的用户和邮件的内容.
|
現在我們可以在測試中用僞郵件發送函數替代眞實的郵件發送函數. 它隻是簡單記録要通知的用戶和郵件的內容.
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
package storage
|
package storage
|
||||||
@ -110,7 +110,7 @@ func TestCheckQuotaNotifiesUser(t *testing.T) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
这里有一个问题: 当测试函数返回后, CheckQuota 将不能正常工作, 因为 notifyUsers 依然使用的是测试函数的伪发送邮件函数. (当更新全局对象的时候总会有这种风险.) 我们必须修改测试代码恢复 notifyUsers 原先的状态以便后续其他的测试没有影响, 要确保所有的执行路径后都能恢复, 包括测试失败或 panic 情形. 在这种情况下, 我们建议使用 defer 处理恢复的代码.
|
這裡有一個問題: 噹測試函數返迴後, CheckQuota 將不能正常工作, 因為 notifyUsers 依然使用的是測試函數的僞發送郵件函數. (噹更新全侷對象的時候總會有這種風險.) 我們必鬚脩改測試代碼恢復 notifyUsers 原先的狀態以便後續其他的測試沒有影響, 要確保所有的執行路徑後都能恢復, 包括測試失敗或 panic 情形. 在這種情況下, 我們建議使用 defer 處理恢復的代碼.
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
func TestCheckQuotaNotifiesUser(t *testing.T) {
|
func TestCheckQuotaNotifiesUser(t *testing.T) {
|
||||||
@ -127,8 +127,8 @@ func TestCheckQuotaNotifiesUser(t *testing.T) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
这种处理模式可以用来暂时保存和恢复所有的全局变量, 包括命令行标志参数, 调试选项, 和优化参数; 安装和移除导致生产代码产生一些调试信息的钩子函数; 还有有些诱导生产代码进入某些重要状态的改变, 比如 超时, 错误, 甚至是一些刻意制造的并发行为.
|
這種處理模式可以用來暫時保存和恢復所有的全侷變量, 包括命令行標誌參數, 調試選項, 和優化參數; 安裝和移除導緻生產代碼產生一些調試信息的鈎子函數; 還有有些誘導生產代碼進入某些重要狀態的改變, 比如 超時, 錯誤, 甚至是一些刻意製造的並發行為.
|
||||||
|
|
||||||
以这种方式使用全局变量是安全的, 因为 go test 并不会同时并发地执行多个测试.
|
以這種方式使用全侷變量是安全的, 因為 go test 並不會衕時並發地執行多個測試.
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,47 +1,60 @@
|
|||||||
### 11.2.4. 擴展測試包
|
### 11.2.4. 擴展測試包
|
||||||
|
|
||||||
考虑下这两个包: net/url 包, 提供了 URL 解析的功能; net/http 包, 提供了web服务和HTTP客户端的功能. 如我们所料, 上层的 net/http 包依赖下层的 net/url 包. 然后, net/url 包中的一个测试是演示不同URL和HTTP客户端的交互行为. 也就是说, 一个下层包的测试代码导入了上层的包.
|
考慮下這兩個包: net/url 包, 提供了 URL 解析的功能; net/http 包, 提供了web服務和HTTP客戶端的功能. 如我們所料, 上層的 net/http 包依賴下層的 net/url 包. 然後, net/url 包中的一個測試是演示不衕URL和HTTP客戶端的交互行為. 也就是説, 一個下層包的測試代碼導入了上層的包.
|
||||||
|
|
||||||
![](../images/ch11-01.png)
|
![](../images/ch11-01.png)
|
||||||
|
|
||||||
这样的行为在 net/url 包的测试代码中会导致包的循环依赖, 正如 图11.1中向上箭头所示, 同时正如我们在 10.1节所说, Go语言规范是禁止包的循环依赖的.
|
這樣的行為在 net/url 包的測試代碼中會導緻包的循環依賴, 正如 圖11.1中嚮上箭頭所示, 衕時正如我們在 10.1節所説, Go語言規範是禁止包的循環依賴的.
|
||||||
|
|
||||||
我们可以通过测试扩展包的方式解决循环依赖的问题, 也就是在 net/url 包所在的目录声明一个 url_test 测试扩展包. 其中测试扩展包名的 `_test` 后缀告诉 go test 工具它应该建立一个额外的包来运行测试. 我们将这个扩展测试包的导入路径视作是 net/url_test 会更容易理解, 但实际上它并不能被其他任何包导入.
|
我們可以通過測試擴展包的方式解決循環依賴的問題, 也就是在 net/url 包所在的目録聲明一個 url_test 測試擴展包. 其中測試擴展包名的 `_test` 後綴告訴 go test 工具它應該建立一個額外的包來運行測試. 我們將這個擴展測試包的導入路徑視作是 net/url_test 會更容易理解, 但實際上它並不能被其他任何包導入.
|
||||||
|
|
||||||
因为测试扩展包是一个独立的包, 因此可以导入测试代码依赖的其他的辅助包; 包内的测试代码可能无法做到. 在设计层面, 测试扩展包是在所以它依赖的包的上层, 正如 图11.2所示.
|
因為測試擴展包是一個獨立的包, 因此可以導入測試代碼依賴的其他的輔助包; 包內的測試代碼可能無法做到. 在設計層麪, 測試擴展包是在所以它依賴的包的上層, 正如 圖11.2所示.
|
||||||
|
|
||||||
![](../images/ch11-02.png)
|
![](../images/ch11-02.png)
|
||||||
|
|
||||||
通过回避循环导入依赖, 扩展测试包可以更灵活的测试, 特别是集成测试(需要测试多个组件之间的交互), 可以像普通应用程序那样自由地导入其他包.
|
通過迴避循環導入依賴, 擴展測試包可以更靈活的測試, 特彆是集成測試(需要測試多個組件之間的交互), 可以像普通應用程序那樣自由地導入其他包.
|
||||||
|
|
||||||
我们可以用 go list 工具查看包对应目录中哪些Go源文件是产品代码, 哪些是包内测试, 还哪些测试扩展包. 我们以 fmt 包作为一个例子. GoFiles 表示产品代码对应的Go源文件列表; 也就是 go build 命令要编译的部分:
|
我們可以用 go list 工具査看包對應目録中哪些Go源文件是產品代碼, 哪些是包內測試, 還哪些測試擴展包. 我們以 fmt 包作為一個例子. GoFiles 錶示產品代碼對應的Go源文件列錶; 也就是 go build 命令要編譯的部分:
|
||||||
|
|
||||||
|
{% raw %}
|
||||||
|
|
||||||
```
|
```
|
||||||
$ go list -f={{.GoFiles}} fmt
|
$ go list -f={{.GoFiles}} fmt
|
||||||
[doc.go format.go print.go scan.go]
|
[doc.go format.go print.go scan.go]
|
||||||
```
|
```
|
||||||
|
|
||||||
TestGoFiles 表示的是 fmt 包内部测试测试代码, 以 _test.go 为后缀文件名, 不过只在测试时被构建:
|
{% endraw %}
|
||||||
|
|
||||||
|
TestGoFiles 錶示的是 fmt 包內部測試測試代碼, 以 _test.go 為後綴文件名, 不過隻在測試時被構建:
|
||||||
|
|
||||||
|
{% raw %}
|
||||||
|
|
||||||
```
|
```
|
||||||
$ go list -f={{.TestGoFiles}} fmt
|
$ go list -f={{.TestGoFiles}} fmt
|
||||||
[export_test.go]
|
[export_test.go]
|
||||||
```
|
```
|
||||||
|
|
||||||
包的测试代码通常都在这些文件中, 不过 fmt 包并非如此; 稍后我们再解释 export_test.go 文件的作用.
|
{% endraw %}
|
||||||
|
|
||||||
XTestGoFiles 表示的是属于测试扩展包的测试代码, 也就是 fmt_test 包, 因此它们必须先导入 fmt 包. 同样, 这些文件也只是在测试时被构建运行:
|
包的測試代碼通常都在這些文件中, 不過 fmt 包並非如此; 稍後我們再解釋 export_test.go 文件的作用.
|
||||||
|
|
||||||
|
XTestGoFiles 錶示的是屬於測試擴展包的測試代碼, 也就是 fmt_test 包, 因此它們必鬚先導入 fmt 包. 衕樣, 這些文件也隻是在測試時被構建運行:
|
||||||
|
|
||||||
|
|
||||||
|
{% raw %}
|
||||||
|
|
||||||
```
|
```
|
||||||
$ go list -f={{.XTestGoFiles}} fmt
|
$ go list -f={{.XTestGoFiles}} fmt
|
||||||
[fmt_test.go scan_test.go stringer_test.go]
|
[fmt_test.go scan_test.go stringer_test.go]
|
||||||
```
|
```
|
||||||
|
|
||||||
有时候测试扩展包需要访问被测试包内部的代码, 例如在一个为了避免循环导入而被独立到外部测试扩展包的白盒测试. 在这种情况下, 我们可以通过一些技巧解决: 我们在包内的一个 _test.go 文件中导出一个内部的实现给测试扩展包. 因为这些代码只有在测试时才需要, 因此一般放在 export_test.go 文件中.
|
{% endraw %}
|
||||||
|
|
||||||
例如, fmt 包的 fmt.Scanf 需要 unicode.IsSpace 函数提供的功能. 但是为了避免太多的依赖, fmt 包并没有导入包含巨大表格数据的 unicode 包; 相反fmt包有一个叫 isSpace 内部的简易实现.
|
有時候測試擴展包需要訪問被測試包內部的代碼, 例如在一個為了避免循環導入而被獨立到外部測試擴展包的白盒測試. 在這種情況下, 我們可以通過一些技巧解決: 我們在包內的一個 _test.go 文件中導齣一個內部的實現給測試擴展包. 因為這些代碼隻有在測試時纔需要, 因此一般放在 export_test.go 文件中.
|
||||||
|
|
||||||
为了确保 fmt.isSpace 和 unicode.IsSpace 函数的行为一致, fmt 包谨慎地包含了一个测试. 是一个在测试扩展包内的测试, 因此是无法直接访问到 isSpace 内部函数的, 因此 fmt 通过一个秘密出口导出了 isSpace 函数. export_test.go 文件就是专门用于测试扩展包的秘密出口.
|
例如, fmt 包的 fmt.Scanf 需要 unicode.IsSpace 函數提供的功能. 但是為了避免太多的依賴, fmt 包並沒有導入包含鉅大錶格數據的 unicode 包; 相反fmt包有一個叫 isSpace 內部的簡易實現.
|
||||||
|
|
||||||
|
為了確保 fmt.isSpace 和 unicode.IsSpace 函數的行為一緻, fmt 包謹慎地包含了一個測試. 是一個在測試擴展包內的測試, 因此是無法直接訪問到 isSpace 內部函數的, 因此 fmt 通過一個祕密齣口導齣了 isSpace 函數. export_test.go 文件就是專門用於測試擴展包的祕密齣口.
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
package fmt
|
package fmt
|
||||||
@ -49,5 +62,5 @@ package fmt
|
|||||||
var IsSpace = isSpace
|
var IsSpace = isSpace
|
||||||
```
|
```
|
||||||
|
|
||||||
这个测试文件并没有定义测试代码; 它只是通过 fmt.IsSpace 简单导出了内部的 isSpace 函数, 提供给测试扩展包使用. 这个技巧可以广泛用于位于测试扩展包的白盒测试.
|
這個測試文件並沒有定義測試代碼; 它隻是通過 fmt.IsSpace 簡單導齣了內部的 isSpace 函數, 提供給測試擴展包使用. 這個技巧可以廣汎用於位於測試擴展包的白盒測試.
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
### 11.2.5. 編寫有效的測試
|
### 11.2.5. 編寫有效的測試
|
||||||
|
|
||||||
|
|
||||||
许多Go新人会惊异与它的极简的测试框架. 很多其他语言的测试框架都提供了识别测试函数的机制(通常使用反射或元数据), 通过设置一些 ‘‘setup’’ 和 ‘‘teardown’’ 的钩子函数来执行测试用例运行的初始化或之后的清理操作, 同时测试工具箱还提供了很多类似assert断言, 比较值, 格式化输出错误信息和停止一个识别的测试等辅助函数(通常使用异常机制). 虽然这些机制可以使得测试非常简洁, 但是测试输出的日志却像火星文一般难以理解. 此外, 虽然测试最终也会输出 PASS 或 FAIL 的报告, 但是它们提供的信息格式却非常不利于代码维护者快速定位问题, 因为失败的信息的具体含义是非常隐患的, 比如 "assert: 0 == 1" 或 成页的海量跟踪日志.
|
許多Go新人會驚異與它的極簡的測試框架. 很多其他語言的測試框架都提供了識彆測試函數的機製(通常使用反射或元數據), 通過設置一些 ‘‘setup’’ 和 ‘‘teardown’’ 的鈎子函數來執行測試用例運行的初始化或之後的清理操作, 衕時測試工具箱還提供了很多類似assert斷言, 比較值, 格式化輸齣錯誤信息和停止一個識彆的測試等輔助函數(通常使用異常機製). 雖然這些機製可以使得測試非常簡潔, 但是測試輸齣的日誌卻像火星文一般難以理解. 此外, 雖然測試最終也會輸齣 PASS 或 FAIL 的報告, 但是它們提供的信息格式卻非常不利於代碼維護者快速定位問題, 因為失敗的信息的具體含義是非常隱患的, 比如 "assert: 0 == 1" 或 成頁的海量跟蹤日誌.
|
||||||
|
|
||||||
Go语言的测试风格则形成鲜明对比. 它期望测试者自己完成大部分的工作, 定义函数避免重复, 就像普通编程那样. 编写测试并不是一个机械的填充过程; 一个测试也有自己的接口, 尽管它的维护者也是测试仅有的一个用户. 一个好的测试不应该引发其他无关的错误信息, 它只要清晰简洁地描述问题的症状即可, 有时候可能还需要一些上下文信息. 在理想情况下, 维护者可以在不看代码的情况下就能根据错误信息定位错误产生的原因. 一个好的测试不应该在遇到一点小错误就立刻退出测试, 它应该尝试报告更多的测试, 因此我们可能从多个失败测试的模式中发现错误产生的规律.
|
Go語言的測試風格則形成鮮明對比. 它期望測試者自己完成大部分的工作, 定義函數避免重復, 就像普通編程那樣. 編寫測試並不是一個機械的填充過程; 一個測試也有自己的接口, 盡管它的維護者也是測試僅有的一個用戶. 一個好的測試不應該引發其他無關的錯誤信息, 它隻要清晰簡潔地描述問題的癥狀卽可, 有時候可能還需要一些上下文信息. 在理想情況下, 維護者可以在不看代碼的情況下就能根據錯誤信息定位錯誤產生的原因. 一個好的測試不應該在遇到一點小錯誤就立刻退齣測試, 它應該嘗試報告更多的測試, 因此我們可能從多個失敗測試的模式中發現錯誤產生的規律.
|
||||||
|
|
||||||
下面的断言函数比较两个值, 然后生成一个通用的错误信息, 并停止程序. 它很方便使用也确实有效果, 但是当识别的时候, 错误时打印的信息几乎是没有价值的. 它并没有为解决问题提供一个很好的入口.
|
下麪的斷言函數比較兩個值, 然後生成一個通用的錯誤信息, 並停止程序. 它很方便使用也確實有效果, 但是噹識彆的時候, 錯誤時打印的信息幾乎是沒有價值的. 它並沒有為解決問題提供一個很好的入口.
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
import (
|
import (
|
||||||
@ -26,7 +26,7 @@ func TestSplit(t *testing.T) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
从这个意义上说, 断言函数犯了过早抽象的错误: 仅仅测试两个整数是否相同, 而放弃了根据上下文提供更有意义的错误信息的做法. 我们可以根据具体的错误打印一个更有价值的错误信息, 就像下面例子那样. 测试在只有一次重复的模式出现时引入抽象.
|
從這個意義上説, 斷言函數犯了過早抽象的錯誤: 僅僅測試兩個整數是否相衕, 而放棄了根據上下文提供更有意義的錯誤信息的做法. 我們可以根據具體的錯誤打印一個更有價值的錯誤信息, 就像下麪例子那樣. 測試在隻有一次重復的模式齣現時引入抽象.
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
func TestSplit(t *testing.T) {
|
func TestSplit(t *testing.T) {
|
||||||
@ -40,10 +40,10 @@ func TestSplit(t *testing.T) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
现在的测试不仅报告了调用的具体函数, 它的输入, 和结果的意义; 并且打印的真实返回的值和期望返回的值; 并且即使断言失败依然会继续尝试运行更多的测试. 一旦我们写了这样结构的测试, 下一步自然不是用更多的if语句来扩展测试用例, 我们可以用像 IsPalindrome 的表驱动测试那样来准备更多的 s, sep 测试用例.
|
現在的測試不僅報告了調用的具體函數, 它的輸入, 和結果的意義; 並且打印的眞實返迴的值和期望返迴的值; 並且卽使斷言失敗依然會繼續嘗試運行更多的測試. 一旦我們寫了這樣結構的測試, 下一步自然不是用更多的if語句來擴展測試用例, 我們可以用像 IsPalindrome 的錶驅動測試那樣來準備更多的 s, sep 測試用例.
|
||||||
|
|
||||||
前面的例子并不需要额外的辅助函数, 如果如果有可以使测试代码更简单的方法我们也乐意接受. (我们将在 13.3节 看到一个 reflect.DeepEqual 辅助函数.) 开始一个好的测试的关键是通过实现你真正想要的具体行为, 然后才是考虑然后简化测试代码. 最好的结果是直接从库的抽象接口开始, 针对公共接口编写一些测试函数.
|
前麪的例子並不需要額外的輔助函數, 如果如果有可以使測試代碼更簡單的方法我們也樂意接受. (我們將在 13.3節 看到一個 reflect.DeepEqual 輔助函數.) 開始一個好的測試的關鍵是通過實現你眞正想要的具體行為, 然後纔是考慮然後簡化測試代碼. 最好的結果是直接從庫的抽象接口開始, 針對公共接口編寫一些測試函數.
|
||||||
|
|
||||||
**练习11.5:** 用表格驱动的技术扩展TestSplit测试, 并打印期望的输出结果.
|
**練習11.5:** 用錶格驅動的技術擴展TestSplit測試, 並打印期望的輸齣結果.
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
### 11.2.6. 避免的不穩定的測試
|
### 11.2.6. 避免的不穩定的測試
|
||||||
|
|
||||||
如果一个应用程序对于新出现的但有效的输入经常失败说明程序不够稳健; 同样如果一个测试仅仅因为声音变化就会导致失败也是不合逻辑的. 就像一个不够稳健的程序会挫败它的用户一样, 一个脆弱性测试同样会激怒它的维护者. 最脆弱的测试代码会在程序没有任何变化的时候产生不同的结果, 时好时坏, 处理它们会耗费大量的时间但是并不会得到任何好处.
|
如果一個應用程序對於新齣現的但有效的輸入經常失敗説明程序不夠穩健; 衕樣如果一個測試僅僅因為聲音變化就會導緻失敗也是不閤邏輯的. 就像一個不夠穩健的程序會挫敗它的用戶一樣, 一個脆弱性測試衕樣會激怒它的維護者. 最脆弱的測試代碼會在程序沒有任何變化的時候產生不衕的結果, 時好時壞, 處理它們會耗費大量的時間但是並不會得到任何好處.
|
||||||
|
|
||||||
当一个测试函数产生一个复杂的输出如一个很长的字符串, 或一个精心设计的数据结构, 或一个文件, 它可以用于和预设的‘‘golden’’结果数据对比, 用这种简单方式写测试是诱人的. 但是随着项目的发展, 输出的某些部分很可能会发生变化, 尽管很可能是一个改进的实现导致的. 而且不仅仅是输出部分, 函数复杂复制的输入部分可能也跟着变化了, 因此测试使用的输入也就不在有效了.
|
噹一個測試函數產生一個復雜的輸齣如一個很長的字符串, 或一個精心設計的數據結構, 或一個文件, 它可以用於和預設的‘‘golden’’結果數據對比, 用這種簡單方式寫測試是誘人的. 但是隨着項目的發展, 輸齣的某些部分很可能會發生變化, 盡管很可能是一個改進的實現導緻的. 而且不僅僅是輸齣部分, 函數復雜復製的輸入部分可能也跟着變化了, 因此測試使用的輸入也就不在有效了.
|
||||||
|
|
||||||
避免脆弱测试代码的方法是只检测你真正关心的属性. 保存测试代码的简洁和内部结构的稳定. 特别是对断言部分要有所选择. 不要检查字符串的全匹配, 但是寻找相关的子字符串, 因为某些子字符串在项目的发展中是比较稳定不变的. 通常编写一个重复杂的输出中提取必要精华信息以用于断言是值得的, 虽然这可能会带来很多前期的工作, 但是它可以帮助迅速及时修复因为项目演化而导致的不合逻辑的失败测试.
|
避免脆弱測試代碼的方法是隻檢測你眞正關心的屬性. 保存測試代碼的簡潔和內部結構的穩定. 特彆是對斷言部分要有所選擇. 不要檢査字符串的全匹配, 但是尋找相關的子字符串, 因為某些子字符串在項目的發展中是比較穩定不變的. 通常編寫一個重復雜的輸齣中提取必要精華信息以用於斷言是值得的, 雖然這可能會帶來很多前期的工作, 但是它可以幫助迅速及時脩復因為項目演化而導緻的不閤邏輯的失敗測試.
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user