update tw

This commit is contained in:
chai2010
2015-12-18 10:53:03 +08:00
parent 510c741a6f
commit c66a96ee52
106 changed files with 864 additions and 864 deletions

View File

@@ -1,8 +1,8 @@
## 11.1. go test
`go test` 是一個按照一定的約定和組織的測試代碼的驅動程序. 在包目內, 以 `_test.go` 爲後綴名的源文件並不是`go build`構建包的以部分, 它們是 `go test` 測試的一部分.
`go test` 是一個按照一定的約定和組織的測試代碼的驅動程序. 在包目內, 以 `_test.go` 爲後綴名的源文件並不是`go build`構建包的以部分, 它們是 `go test` 測試的一部分.
`*_test.go` 文件中, 有三種類型的函數: 測試函數, 基準測試函數, 例子函數. 一個測試函數是以 Test 爲函數名前綴的函數, 用於測試程序的一些邏輯行爲是否正確; `go test` 會調用這些測試函數並報告測試結果是 PASS 或 FAIL. 基準測試函數是以Benchmark爲函數名前綴的函數, 用於衡量一些函數的性能; `go test` 會多次運行基準函數以計算一個平均的執行時間. 例子函數是以Example爲函數名前綴的函數, 提供一個由機器檢測正確性的例子文檔. 我們將在 11.2 節 討論測試函數的細節, 在 11.4 節討論基準測試函數的細節, 在 11.6 討論例子函數的細節.
`go test` 命令會遍所有的 `*_test.go` 文件中上述函數, 然後生成一個臨時的main包調用相應的測試函數, 然後構建並運行, 報告測試結果, 最後清理臨時文件.
`go test` 命令會遍所有的 `*_test.go` 文件中上述函數, 然後生成一個臨時的main包調用相應的測試函數, 然後構建並運行, 報告測試結果, 最後清理臨時文件.

View File

@@ -1,11 +1,11 @@
### 11.2.1. 隨機測試
格驅動的測試便於構造基於精心挑選的測試數據的測試用例. 另一種測試思路是隨機測試, 也就是通過構造更廣汎的隨機輸入來測試探索函數的行爲.
格驅動的測試便於構造基於精心挑選的測試數據的測試用例. 另一種測試思路是隨機測試, 也就是通過構造更廣汎的隨機輸入來測試探索函數的行爲.
對於一隨機的輸入, 我們如何能知道希望的輸齣結果呢? 這裡有兩種策略. 第一是編寫另一函數, 使用簡單和清晰的算法, 雖然效率較低但是行爲和要測試的函數一緻, 然後鍼對相的隨機輸入檢査兩者的輸齣結果. 第二種是生成的隨機輸入的數據遵循特定的模式, 這樣我們就可以知道期望的輸齣的模式.
對於一隨機的輸入, 我們如何能知道希望的輸齣結果呢? 這裡有兩種策略. 第一是編寫另一函數, 使用簡單和清晰的算法, 雖然效率較低但是行爲和要測試的函數一緻, 然後鍼對相的隨機輸入檢査兩者的輸齣結果. 第二種是生成的隨機輸入的數據遵循特定的模式, 這樣我們就可以知道期望的輸齣的模式.
的例子使用的是第二種方法: randomPalindrome 函數用於隨機生成迴文字符串.
的例子使用的是第二種方法: randomPalindrome 函數用於隨機生成迴文字符串.
```Go
import "math/rand"
@@ -39,9 +39,9 @@ func TestRandomPalindromes(t *testing.T) {
}
```
雖然隨機測試有不確定因素, 但是它也是至關重要的, 我們可以從失敗測試的日誌取足夠的信息. 在我們的例子中, 輸入 IsPalindrome 的 p 參數將告訴我們眞實的數據, 但是對於函數將接受更復雜的輸入, 不需要保存所有的輸入, 隻要日誌中簡單地記隨機數種子卽可(像上的方式). 有了這些隨機數初始化種子, 我們可以很容易脩改測試代碼以重現失敗的隨機測試.
雖然隨機測試有不確定因素, 但是它也是至關重要的, 我們可以從失敗測試的日誌取足夠的信息. 在我們的例子中, 輸入 IsPalindrome 的 p 參數將告訴我們眞實的數據, 但是對於函數將接受更復雜的輸入, 不需要保存所有的輸入, 隻要日誌中簡單地記隨機數種子卽可(像上的方式). 有了這些隨機數初始化種子, 我們可以很容易脩改測試代碼以重現失敗的隨機測試.
通過使用前時間作爲隨機種子, 在整過程中的每次運行測試命令時都將探索新的隨機數據. 如果你使用的是定期運行的自動化測試集成繫統, 隨機測試將特別有價值.
通過使用前時間作爲隨機種子, 在整過程中的每次運行測試命令時都將探索新的隨機數據. 如果你使用的是定期運行的自動化測試集成繫統, 隨機測試將特別有價值.
**練習 11.3:** TestRandomPalindromes 隻測試了迴文字符串. 編寫新的隨機測試生成器, 用於測試隨機生成的非迴文字符串.

View File

@@ -1,9 +1,9 @@
### 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
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
package main
@@ -83,9 +83,9 @@ func TestEcho(t *testing.T) {
}
```
要註意的是測試代碼和產品代碼在一個包. 雖然是main包, 也有對應的 main 入口函數, 但是在測試的時候 main 包隻是 TestEcho 測試函數導入的一個普通包, 裡 main 函數並沒有被導齣是被忽略的.
要註意的是測試代碼和產品代碼在一個包. 雖然是main包, 也有對應的 main 入口函數, 但是在測試的時候 main 包隻是 TestEcho 測試函數導入的一個普通包, 裡 main 函數並沒有被導齣是被忽略的.
通過將測試放到格中, 我們很容易添加新的測試用例. 讓我通過增加下的測試用例來看看失敗的情況是怎麽樣的:
通過將測試放到格中, 我們很容易添加新的測試用例. 讓我通過增加下的測試用例來看看失敗的情況是怎麽樣的:
```Go
{true, ",", []string{"a", "b", "c"}, "a b c\n"}, // NOTE: wrong expectation!
@@ -101,8 +101,8 @@ FAIL
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的情況.

View File

@@ -1,15 +1,15 @@
### 11.2.3. 白盒測試
一個測試分類的方法是基於測試者是否需要了解被測試對象的內部工作原理. 黑盒測試隻需要測試包公開的文檔和API行, 內部實現對測試代碼是透明的. 相反, 白盒測試有訪問包內部函數和數據結構的權限, 因此可以做到一下普通客戶端無法實現的測試. 例如, 一個飽和測試可以在每個操作之後檢測不變量的數據類型. (白盒測試隻是一個傳統的名稱, 其實稱 clear box 會更準確.)
一個測試分類的方法是基於測試者是否需要了解被測試對象的內部工作原理. 黑盒測試隻需要測試包公開的文檔和API行, 內部實現對測試代碼是透明的. 相反, 白盒測試有訪問包內部函數和數據結構的權限, 因此可以做到一下普通客戶端無法實現的測試. 例如, 一個飽和測試可以在每個操作之後檢測不變量的數據類型. (白盒測試隻是一個傳統的名稱, 其實稱 clear box 會更準確.)
黑盒和白盒這兩種測試方法是互補的. 黑盒測試一般更健壯, 隨着軟件實現的完善測試代碼很少需要更新. 它們可以幫助測試者了解眞是客戶的需求, 可以幫助發現API設計的一些不足之處. 相反, 白盒測試則可以對內部一些棘手的實現提供更多的測試覆蓋.
我們已經看到兩種測試的例子. TestIsPalindrome 測試僅僅使用導齣的 IsPalindrome 函數, 因此它是一個黑盒測試. TestEcho 測試則調用了內部的 echo 函數, 並且更新了內部的 out 全侷變量, 這兩個都是未導齣的, 因此它是白盒測試.
我們開發TestEcho測試的時候, 我們脩改了 echo 函數使用包級的 out 作輸齣對象, 因此測試代碼可以用另一個實現代替標準輸齣, 這樣可以方便對比 echo 的輸齣數據. 使用類似的技術, 我們可以將產品代碼的其他部分也替換一個容易測試的僞對象. 使用僞對象的好處是我們可以方便配置, 容易預測, 更可靠, 也更容易觀察. 時也可以避免一些不良的副作用, 例如更新生產數據庫或信用卡消費行.
我們開發TestEcho測試的時候, 我們脩改了 echo 函數使用包級的 out 作輸齣對象, 因此測試代碼可以用另一個實現代替標準輸齣, 這樣可以方便對比 echo 的輸齣數據. 使用類似的技術, 我們可以將產品代碼的其他部分也替換一個容易測試的僞對象. 使用僞對象的好處是我們可以方便配置, 容易預測, 更可靠, 也更容易觀察. 時也可以避免一些不良的副作用, 例如更新生產數據庫或信用卡消費行.
的代碼演示了用戶提供網絡存儲的web服務中的配額檢測邏輯. 用戶使用了超過 90% 的存儲配額之後將發送提醒郵件.
的代碼演示了用戶提供網絡存儲的web服務中的配額檢測邏輯. 用戶使用了超過 90% 的存儲配額之後將發送提醒郵件.
```Go
gopl.io/ch11/storage1
@@ -110,7 +110,7 @@ func TestCheckQuotaNotifiesUser(t *testing.T) {
}
```
這裡有一個問題: 測試函數返迴後, CheckQuota 將不能正常工作, 因 notifyUsers 依然使用的是測試函數的僞發送郵件函數. (更新全侷對象的時候總會有這種風險.) 我們必鬚脩改測試代碼恢復 notifyUsers 原先的狀態以便後續其他的測試沒有影響, 要確保所有的執行路徑後都能恢復, 包括測試失敗或 panic 情形. 在這種情況下, 我們建議使用 defer 處理恢復的代碼.
這裡有一個問題: 測試函數返迴後, CheckQuota 將不能正常工作, 因 notifyUsers 依然使用的是測試函數的僞發送郵件函數. (更新全侷對象的時候總會有這種風險.) 我們必鬚脩改測試代碼恢復 notifyUsers 原先的狀態以便後續其他的測試沒有影響, 要確保所有的執行路徑後都能恢復, 包括測試失敗或 panic 情形. 在這種情況下, 我們建議使用 defer 處理恢復的代碼.
```Go
func TestCheckQuotaNotifiesUser(t *testing.T) {
@@ -127,8 +127,8 @@ func TestCheckQuotaNotifiesUser(t *testing.T) {
}
```
這種處理模式可以用來暫時保存和恢復所有的全侷變量, 包括命令行標誌參數, 調試選項, 和優化參數; 安裝和移除導緻生產代碼產生一些調試信息的子函數; 還有有些誘導生產代碼進入某些重要狀態的改變, 比如 超時, 錯誤, 甚至是一些刻意製造的並發行.
這種處理模式可以用來暫時保存和恢復所有的全侷變量, 包括命令行標誌參數, 調試選項, 和優化參數; 安裝和移除導緻生產代碼產生一些調試信息的子函數; 還有有些誘導生產代碼進入某些重要狀態的改變, 比如 超時, 錯誤, 甚至是一些刻意製造的並發行.
以這種方式使用全侷變量是安全的, 因 go test 並不會時並發地執行多個測試.
以這種方式使用全侷變量是安全的, 因 go test 並不會時並發地執行多個測試.

View File

@@ -1,20 +1,20 @@
### 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)
這樣的行在 net/url 包的測試代碼中會導緻包的循環依賴, 正如 圖11.1中上箭頭所示, 時正如我們在 10.1節所, Go語言規範是禁止包的循環依賴的.
這樣的行在 net/url 包的測試代碼中會導緻包的循環依賴, 正如 圖11.1中上箭頭所示, 時正如我們在 10.1節所, Go語言規範是禁止包的循環依賴的.
我們可以通過測試擴展包的方式解決循環依賴的問題, 也就是在 net/url 包所在的目録聲明一個 url_test 測試擴展包. 其中測試擴展包名的 `_test` 後綴告訴 go test 工具它應該建立一個額外的包來運行測試. 我們將這個擴展測試包的導入路徑視作是 net/url_test 會更容易理解, 但實際上它並不能被其他任何包導入.
測試擴展包是一個獨立的包, 因此可以導入測試代碼依賴的其他的輔助包; 包內的測試代碼可能無法做到. 在設計層, 測試擴展包是在所以它依賴的包的上層, 正如 圖11.2所示.
測試擴展包是一個獨立的包, 因此可以導入測試代碼依賴的其他的輔助包; 包內的測試代碼可能無法做到. 在設計層, 測試擴展包是在所以它依賴的包的上層, 正如 圖11.2所示.
![](../images/ch11-02.png)
通過迴避循環導入依賴, 擴展測試包可以更靈活的測試, 特是集成測試(需要測試多個組件之間的交互), 可以像普通應用程序那樣自由地導入其他包.
通過迴避循環導入依賴, 擴展測試包可以更靈活的測試, 特是集成測試(需要測試多個組件之間的交互), 可以像普通應用程序那樣自由地導入其他包.
我們可以用 go list 工具査看包對應目録中哪些Go源文件是產品代碼, 哪些是包內測試, 還哪些測試擴展包. 我們以 fmt 包作一個例子. GoFiles 示產品代碼對應的Go源文件列; 也就是 go build 命令要編譯的部分:
我們可以用 go list 工具査看包對應目録中哪些Go源文件是產品代碼, 哪些是包內測試, 還哪些測試擴展包. 我們以 fmt 包作一個例子. GoFiles 示產品代碼對應的Go源文件列; 也就是 go build 命令要編譯的部分:
{% raw %}
@@ -25,7 +25,7 @@ $ go list -f={{.GoFiles}} fmt
{% endraw %}
TestGoFiles 示的是 fmt 包內部測試測試代碼, 以 _test.go 後綴文件名, 不過隻在測試時被構建:
TestGoFiles 示的是 fmt 包內部測試測試代碼, 以 _test.go 後綴文件名, 不過隻在測試時被構建:
{% raw %}
@@ -38,7 +38,7 @@ $ go list -f={{.TestGoFiles}} fmt
包的測試代碼通常都在這些文件中, 不過 fmt 包並非如此; 稍後我們再解釋 export_test.go 文件的作用.
XTestGoFiles 示的是屬於測試擴展包的測試代碼, 也就是 fmt_test 包, 因此它們必鬚先導入 fmt 包. 樣, 這些文件也隻是在測試時被構建運行:
XTestGoFiles 示的是屬於測試擴展包的測試代碼, 也就是 fmt_test 包, 因此它們必鬚先導入 fmt 包. 樣, 這些文件也隻是在測試時被構建運行:
{% raw %}
@@ -50,11 +50,11 @@ $ go list -f={{.XTestGoFiles}} fmt
{% endraw %}
有時候測試擴展包需要訪問被測試包內部的代碼, 例如在一個了避免循環導入而被獨立到外部測試擴展包的白盒測試. 在這種情況下, 我們可以通過一些技巧解決: 我們在包內的一個 _test.go 文件中導齣一個內部的實現給測試擴展包. 因這些代碼隻有在測試時纔需要, 因此一般放在 export_test.go 文件中.
有時候測試擴展包需要訪問被測試包內部的代碼, 例如在一個了避免循環導入而被獨立到外部測試擴展包的白盒測試. 在這種情況下, 我們可以通過一些技巧解決: 我們在包內的一個 _test.go 文件中導齣一個內部的實現給測試擴展包. 因這些代碼隻有在測試時纔需要, 因此一般放在 export_test.go 文件中.
例如, fmt 包的 fmt.Scanf 需要 unicode.IsSpace 函數提供的功能. 但是了避免太多的依賴, fmt 包並沒有導入包含鉅大格數據的 unicode 包; 相反fmt包有一個叫 isSpace 內部的簡易實現.
例如, fmt 包的 fmt.Scanf 需要 unicode.IsSpace 函數提供的功能. 但是了避免太多的依賴, fmt 包並沒有導入包含鉅大格數據的 unicode 包; 相反fmt包有一個叫 isSpace 內部的簡易實現.
了確保 fmt.isSpace 和 unicode.IsSpace 函數的行一緻, fmt 包謹慎地包含了一個測試. 是一個在測試擴展包內的測試, 因此是無法直接訪問到 isSpace 內部函數的, 因此 fmt 通過一個祕密齣口導齣了 isSpace 函數. export_test.go 文件就是專門用於測試擴展包的祕密齣口.
了確保 fmt.isSpace 和 unicode.IsSpace 函數的行一緻, fmt 包謹慎地包含了一個測試. 是一個在測試擴展包內的測試, 因此是無法直接訪問到 isSpace 內部函數的, 因此 fmt 通過一個祕密齣口導齣了 isSpace 函數. export_test.go 文件就是專門用於測試擴展包的祕密齣口.
```Go
package fmt

View File

@@ -1,11 +1,11 @@
### 11.2.5. 編寫有效的測試
許多Go新人會驚異與它的極簡的測試框架. 很多其他語言的測試框架都提供了識測試函數的機製(通常使用反射或元數據), 通過設置一些 setupteardown子函數來執行測試用例運行的初始化或之後的清理操作, 時測試工具箱還提供了很多類似assert斷言, 比較值, 格式化輸齣錯誤信息和停止一個識的測試等輔助函數(通常使用異常機製). 雖然這些機製可以使得測試非常簡潔, 但是測試輸齣的日誌卻像火星文一般難以理解. 此外, 雖然測試最終也會輸齣 PASS 或 FAIL 的報告, 但是它們提供的信息格式卻非常不利於代碼維護者快速定位問題, 因失敗的信息的具體含義是非常隱患的, 比如 "assert: 0 == 1" 或 成頁的海量跟蹤日誌.
許多Go新人會驚異與它的極簡的測試框架. 很多其他語言的測試框架都提供了識測試函數的機製(通常使用反射或元數據), 通過設置一些 setupteardown子函數來執行測試用例運行的初始化或之後的清理操作, 時測試工具箱還提供了很多類似assert斷言, 比較值, 格式化輸齣錯誤信息和停止一個識的測試等輔助函數(通常使用異常機製). 雖然這些機製可以使得測試非常簡潔, 但是測試輸齣的日誌卻像火星文一般難以理解. 此外, 雖然測試最終也會輸齣 PASS 或 FAIL 的報告, 但是它們提供的信息格式卻非常不利於代碼維護者快速定位問題, 因失敗的信息的具體含義是非常隱患的, 比如 "assert: 0 == 1" 或 成頁的海量跟蹤日誌.
Go語言的測試風格則形成鮮明對比. 它期望測試者自己完成大部分的工作, 定義函數避免重復, 就像普通編程那樣. 編寫測試並不是一個機械的填充過程; 一個測試也有自己的接口, 管它的維護者也是測試僅有的一個用戶. 一個好的測試不應該引發其他無關的錯誤信息, 它隻要清晰簡潔地描述問題的癥狀卽可, 有時候可能還需要一些上下文信息. 在理想情況下, 維護者可以在不看代碼的情況下就能根據錯誤信息定位錯誤產生的原因. 一個好的測試不應該在遇到一點小錯誤就立刻退齣測試, 它應該嘗試報告更多的測試, 因此我們可能從多個失敗測試的模式中發現錯誤產生的規律.
Go語言的測試風格則形成鮮明對比. 它期望測試者自己完成大部分的工作, 定義函數避免重復, 就像普通編程那樣. 編寫測試並不是一個機械的填充過程; 一個測試也有自己的接口, 管它的維護者也是測試僅有的一個用戶. 一個好的測試不應該引發其他無關的錯誤信息, 它隻要清晰簡潔地描述問題的癥狀卽可, 有時候可能還需要一些上下文信息. 在理想情況下, 維護者可以在不看代碼的情況下就能根據錯誤信息定位錯誤產生的原因. 一個好的測試不應該在遇到一點小錯誤就立刻退齣測試, 它應該嘗試報告更多的測試, 因此我們可能從多個失敗測試的模式中發現錯誤產生的規律.
的斷言函數比較兩個值, 然後生成一個通用的錯誤信息, 並停止程序. 它很方便使用也確實有效果, 但是噹識彆的時候, 錯誤時打印的信息幾乎是沒有價值的. 它並沒有解決問題提供一個很好的入口.
的斷言函數比較兩個值, 然後生成一個通用的錯誤信息, 並停止程序. 它很方便使用也確實有效果, 但是當識別的時候, 錯誤時打印的信息幾乎是沒有價值的. 它並沒有解決問題提供一個很好的入口.
```Go
import (
@@ -26,7 +26,7 @@ func TestSplit(t *testing.T) {
}
```
從這個意義上, 斷言函數犯了過早抽象的錯誤: 僅僅測試兩個整數是否相, 而放棄了根據上下文提供更有意義的錯誤信息的做法. 我們可以根據具體的錯誤打印一個更有價值的錯誤信息, 就像下例子那樣. 測試在隻有一次重復的模式齣現時引入抽象.
從這個意義上, 斷言函數犯了過早抽象的錯誤: 僅僅測試兩個整數是否相, 而放棄了根據上下文提供更有意義的錯誤信息的做法. 我們可以根據具體的錯誤打印一個更有價值的錯誤信息, 就像下例子那樣. 測試在隻有一次重復的模式齣現時引入抽象.
```Go
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測試, 並打印期望的輸齣結果.

View File

@@ -1,8 +1,8 @@
### 11.2.6. 避免的不穩定的測試
如果一個應用程序對於新齣現的但有效的輸入經常失敗明程序不夠穩健; 樣如果一個測試僅僅因聲音變化就會導緻失敗也是不邏輯的. 就像一個不夠穩健的程序會挫敗它的用戶一樣, 一個脆弱性測試樣會激怒它的維護者. 最脆弱的測試代碼會在程序沒有任何變化的時候產生不的結果, 時好時壞, 處理它們會耗費大量的時間但是並不會得到任何好處.
如果一個應用程序對於新齣現的但有效的輸入經常失敗明程序不夠穩健; 樣如果一個測試僅僅因聲音變化就會導緻失敗也是不邏輯的. 就像一個不夠穩健的程序會挫敗它的用戶一樣, 一個脆弱性測試樣會激怒它的維護者. 最脆弱的測試代碼會在程序沒有任何變化的時候產生不的結果, 時好時壞, 處理它們會耗費大量的時間但是並不會得到任何好處.
一個測試函數產生一個復雜的輸齣如一個很長的字符串, 或一個精心設計的數據結構, 或一個文件, 它可以用於和預設的golden結果數據對比, 用這種簡單方式寫測試是誘人的. 但是隨着項目的發展, 輸齣的某些部分很可能會發生變化, 管很可能是一個改進的實現導緻的. 而且不僅僅是輸齣部分, 函數復雜復製的輸入部分可能也跟着變化了, 因此測試使用的輸入也就不在有效了.
一個測試函數產生一個復雜的輸齣如一個很長的字符串, 或一個精心設計的數據結構, 或一個文件, 它可以用於和預設的golden結果數據對比, 用這種簡單方式寫測試是誘人的. 但是隨着項目的發展, 輸齣的某些部分很可能會發生變化, 管很可能是一個改進的實現導緻的. 而且不僅僅是輸齣部分, 函數復雜復製的輸入部分可能也跟着變化了, 因此測試使用的輸入也就不在有效了.
避免脆弱測試代碼的方法是隻檢測你眞正關心的屬性. 保存測試代碼的簡潔和內部結構的穩定. 特是對斷言部分要有所選擇. 不要檢査字符串的全匹配, 但是尋找相關的子字符串, 因某些子字符串在項目的發展中是比較穩定不變的. 通常編寫一個重復雜的輸齣中提取必要精華信息以用於斷言是值得的, 雖然這可能會帶來很多前期的工作, 但是它可以幫助迅速及時脩復因項目演化而導緻的不邏輯的失敗測試.
避免脆弱測試代碼的方法是隻檢測你眞正關心的屬性. 保存測試代碼的簡潔和內部結構的穩定. 特是對斷言部分要有所選擇. 不要檢査字符串的全匹配, 但是尋找相關的子字符串, 因某些子字符串在項目的發展中是比較穩定不變的. 通常編寫一個重復雜的輸齣中提取必要精華信息以用於斷言是值得的, 雖然這可能會帶來很多前期的工作, 但是它可以幫助迅速及時脩復因項目演化而導緻的不邏輯的失敗測試.

View File

@@ -1,7 +1,7 @@
## 11.2. 測試函數
測試函數必導入 testing 包. 測試函數有如下的簽名:
測試函數必導入 testing 包. 測試函數有如下的簽名:
```Go
func TestName(t *testing.T) {
@@ -9,7 +9,7 @@ func TestName(t *testing.T) {
}
```
測試函數的名字必以Test開頭, 可選的後綴名必以大寫字母開頭:
測試函數的名字必以Test開頭, 可選的後綴名必以大寫字母開頭:
```Go
func TestSin(t *testing.T) { /* ... */ }
@@ -17,7 +17,7 @@ func TestCos(t *testing.T) { /* ... */ }
func TestLog(t *testing.T) { /* ... */ }
```
其中 t 參數用於報告測試失敗和附件的日誌信息. 讓我們頂一箇一箇實例包 gopl.io/ch11/word1, 隻有一函數 IsPalindrome 用於檢査一字符串是否從前後和從後前讀都一樣. (這實現對於一字符串是否是迴文字符串前後重復測試了兩次; 我們稍後會再討論這問題.)
其中 t 參數用於報告測試失敗和附件的日誌信息. 讓我們頂一個一個實例包 gopl.io/ch11/word1, 隻有一函數 IsPalindrome 用於檢査一字符串是否從前後和從後前讀都一樣. (這實現對於一字符串是否是迴文字符串前後重復測試了兩次; 我們稍後會再討論這問題.)
```Go
gopl.io/ch11/word1
@@ -36,7 +36,7 @@ func IsPalindrome(s string) bool {
}
```
在相的目下, word_test.go 文件包含了 TestPalindrome 和 TestNonPalindrome 兩測試函數. 每一都是測試 IsPalindrome 是否給齣正確的結果, 使用 t.Error 報告失敗:
在相的目下, word_test.go 文件包含了 TestPalindrome 和 TestNonPalindrome 兩測試函數. 每一都是測試 IsPalindrome 是否給齣正確的結果, 使用 t.Error 報告失敗:
```Go
package word
@@ -59,7 +59,7 @@ func TestNonPalindrome(t *testing.T) {
}
```
`go test` (或 `go build`) 命令 如果沒有參數指定包那將默認寀用噹前目對應的包. 我們可以用下的命令構建和運行測試.
`go test` (或 `go build`) 命令 如果沒有參數指定包那將默認採用當前目對應的包. 我們可以用下的命令構建和運行測試.
```
$ cd $GOPATH/src/gopl.io/ch11/word1
@@ -67,7 +67,7 @@ $ go test
ok gopl.io/ch11/word1 0.008s
```
還比較滿意, 我們運行了這程序, 不過沒有提前退齣是因爲還沒有遇到BUG報告. 一法國名爲 Noelle Eve Elleon 的用戶抱怨 IsPalindrome 函數不能識別 été.. 另外一來自美國中部用戶的抱怨是不能識別 A man, a plan, a canal: Panama.. 執行特殊和小的BUG報告爲我們提供了新的更自然的測試用例.
還比較滿意, 我們運行了這程序, 不過沒有提前退齣是因爲還沒有遇到BUG報告. 一法國名爲 Noelle Eve Elleon 的用戶抱怨 IsPalindrome 函數不能識別 été.. 另外一來自美國中部用戶的抱怨是不能識別 A man, a plan, a canal: Panama.. 執行特殊和小的BUG報告爲我們提供了新的更自然的測試用例.
```Go
func TestFrenchPalindrome(t *testing.T) {
@@ -84,9 +84,9 @@ func TestCanalPalindrome(t *testing.T) {
}
```
爲了避免兩次輸入較長的字符串, 我們使用了提供了有類似 Printf 格式化功能的 Errorf 函數來報錯誤結果.
爲了避免兩次輸入較長的字符串, 我們使用了提供了有類似 Printf 格式化功能的 Errorf 函數來報錯誤結果.
添加了這兩測試用例之後, `go test` 返迴了測試失敗的信息.
添加了這兩測試用例之後, `go test` 返迴了測試失敗的信息.
```
$ go test
@@ -98,11 +98,11 @@ FAIL
FAIL gopl.io/ch11/word1 0.014s
```
先編寫測試用例觀察到測試用例觸發了和用戶報告的錯誤相的描述是一好的測試習慣. 隻有這樣, 我們纔能定位我們要眞正解決的問題.
先編寫測試用例觀察到測試用例觸發了和用戶報告的錯誤相的描述是一好的測試習慣. 隻有這樣, 我們纔能定位我們要眞正解決的問題.
先寫測試用例的另好處是, 運行測試通常會比手工描述報告的處理更快, 這讓我們可以進行快速地迭代. 如果測試集有很多運行緩慢的測試, 我們可以通過隻選擇運行某些特定的測試來加快測試速度.
參數 `-v` 用於打印每測試函數的名字和運行時間:
參數 `-v` 用於打印每測試函數的名字和運行時間:
```
$ go test -v
@@ -121,7 +121,7 @@ exit status 1
FAIL gopl.io/ch11/word1 0.017s
```
參數 `-run` 是一正則達式, 隻有測試函數名被它正確匹配的測試函數纔會被 `go test` 運行:
參數 `-run` 是一正則達式, 隻有測試函數名被它正確匹配的測試函數纔會被 `go test` 運行:
```
$ go test -v -run="French|Canal"
@@ -137,11 +137,11 @@ FAIL gopl.io/ch11/word1 0.014s
```
然, 一旦我們已經脩復了失敗的測試用例, 在我們提交代碼更新之前, 我們應該以不帶參數的 `go test` 命令運行全部的測試用例, 以確保更新沒有引入新的問題.
然, 一旦我們已經脩復了失敗的測試用例, 在我們提交代碼更新之前, 我們應該以不帶參數的 `go test` 命令運行全部的測試用例, 以確保更新沒有引入新的問題.
我們現在的任務就是脩復這些錯誤. 簡要分析後發現第一BUG的原因是我們用了 byte 而不是 rune 序列, 所以像 "été" 中的 é 等非 ASCII 字符不能正確處理. 第二BUG是因爲沒有忽略空格和字母的大小寫導緻的.
我們現在的任務就是脩復這些錯誤. 簡要分析後發現第一BUG的原因是我們用了 byte 而不是 rune 序列, 所以像 "été" 中的 é 等非 ASCII 字符不能正確處理. 第二BUG是因爲沒有忽略空格和字母的大小寫導緻的.
鍼對上述兩BUG, 我們仔細重寫了函數:
鍼對上述兩BUG, 我們仔細重寫了函數:
```Go
gopl.io/ch11/word2
@@ -168,7 +168,7 @@ func IsPalindrome(s string) bool {
}
```
時我們也將之前的所有測試數據閤併到了一測試中的格中.
時我們也將之前的所有測試數據合並到了一測試中的格中.
```Go
func TestIsPalindrome(t *testing.T) {
@@ -205,17 +205,17 @@ $ go test gopl.io/ch11/word2
ok gopl.io/ch11/word2 0.015s
```
這種格驅動的測試在Go中很常見的. 我們很容易想格添加新的測試數據, 且後的測試邏輯也沒有冗餘, 這樣我們可以更好地完善錯誤信息.
這種格驅動的測試在Go中很常見的. 我們很容易想格添加新的測試數據, 且後的測試邏輯也沒有冗餘, 這樣我們可以更好地完善錯誤信息.
失敗的測試的輸齣不包括調用 t.Errorf 時刻的堆棧調用信息. 不像其他語言或測試框架的 assert 斷言, t.Errorf 調用也沒有引起 panic 或停止測試的執行. 卽使格中前的數據導緻了測試的失敗, 格後的測試數據依然會運行測試, 因此在一測試中我們可能了解多失敗的信息.
失敗的測試的輸齣不包括調用 t.Errorf 時刻的堆棧調用信息. 不像其他語言或測試框架的 assert 斷言, t.Errorf 調用也沒有引起 panic 或停止測試的執行. 卽使格中前的數據導緻了測試的失敗, 格後的測試數據依然會運行測試, 因此在一測試中我們可能了解多失敗的信息.
如果我們眞的需要停止測試, 或許是因爲初始化失敗或可能是早先的錯誤導緻了後續錯誤等原因, 我們可以使用 t.Fatal 或 t.Fatalf 停止測試. 它們必在和測試函數衕一箇 goroutine 內調用.
如果我們眞的需要停止測試, 或許是因爲初始化失敗或可能是早先的錯誤導緻了後續錯誤等原因, 我們可以使用 t.Fatal 或 t.Fatalf 停止測試. 它們必在和測試函數同一個 goroutine 內調用.
測試失敗的信息一般的形式是 "f(x) = y, want z", f(x) 解釋了失敗的操作和對應的輸齣, y 是實際的運行結果, z 是期望的正確的結果. 就像前檢査迴文字符串的例子, 實際的函數用於 f(x) 部分. 如果顯示 x 是格驅動型測試中比較重要的部分, 因爲衕一箇斷言可能對應不衕的錶格項執行多次. 要避免無用和冗餘的信息. 在測試類似 IsPalindrome 返迴佈爾類型的函數時, 可以忽略沒有額外信息的 z 部分. 如果 x, y 或 z 是 y 的長度, 輸齣一相關部分的簡明總結卽可. 測試的作者應該要努力幫助程序員診斷失敗的測試.
測試失敗的信息一般的形式是 "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 將會用到.
**練習 11.2:** 爲 (§6.5)的 IntSet 編寫一組測試, 用於檢査每操作後的行爲和基於內置 map 的集等價 , 後 練習11.7 將會用到.
{% include "./ch11-02-1.md" %}

View File

@@ -1,15 +1,15 @@
## 11.3. 測試覆蓋率
就其性質而言, 測試不可能是完整的. 計算機科學傢 Edsger Dijkstra 曾過: "測試可以顯示存在缺陷, 但是不是沒有BUG." 再多的測試也不能明一個包沒有BUG. 在最好的情況下, 測試可以增強我們的信息, 包在我們測試的環境是可以正常工作的.
就其性質而言, 測試不可能是完整的. 計算機科學傢 Edsger Dijkstra 曾過: "測試可以顯示存在缺陷, 但是不是沒有BUG." 再多的測試也不能明一個包沒有BUG. 在最好的情況下, 測試可以增強我們的信息, 包在我們測試的環境是可以正常工作的.
由測試驅動觸發運行到的被測試函數的代碼數目稱爲測試的覆蓋率. 測試覆蓋率不能量化 — 甚至連最簡單的動態程序也難以精確測量 — 但是可以啓發併幫助我們編寫的有效的測試代碼.
由測試驅動觸發運行到的被測試函數的代碼數目稱爲測試的覆蓋率. 測試覆蓋率不能量化 — 甚至連最簡單的動態程序也難以精確測量 — 但是可以啟發並幫助我們編寫的有效的測試代碼.
這些幫助信息中語句的覆蓋率是最簡單和最廣汎使用的. 語句的覆蓋率是指在測試中至少被運行一次的代碼佔總代碼數的比例. 在本節中, 我們使用 `go test` 中集成的測試覆蓋率工具, 來度量下代碼的測試覆蓋率, 幫助我們識測試和我們期望間的差距.
這些幫助信息中語句的覆蓋率是最簡單和最廣汎使用的. 語句的覆蓋率是指在測試中至少被運行一次的代碼佔總代碼數的比例. 在本節中, 我們使用 `go test` 中集成的測試覆蓋率工具, 來度量下代碼的測試覆蓋率, 幫助我們識測試和我們期望間的差距.
The code below is a table-driven test for the expression evaluator we built back in Chapter 7:
的代碼是一個格驅動的測試, 用於測試第七章的達式求值程序:
的代碼是一個格驅動的測試, 用於測試第七章的達式求值程序:
```Go
gopl.io/ch7/eval
@@ -59,7 +59,7 @@ PASS
ok gopl.io/ch7/eval 0.011s
```
這個命令可以顯示測試覆蓋率工具的用法信息:
這個命令可以顯示測試覆蓋率工具的用法信息:
```
$ go tool cover
@@ -72,18 +72,18 @@ Open a web browser displaying annotated source code:
...
```
`go tool` 命令運行Go工具鏈的底層可執行程序. 這些底層可執行程序放在 $GOROOT/pkg/tool/${GOOS}_${GOARCH} 目. 因爲 `go build` 的原因, 我們很小直接調用這些底層工具.
`go tool` 命令運行Go工具鏈的底層可執行程序. 這些底層可執行程序放在 $GOROOT/pkg/tool/${GOOS}_${GOARCH} 目. 因爲 `go build` 的原因, 我們很小直接調用這些底層工具.
現在我們可以用 `-coverprofile` 標誌數重新運行:
現在我們可以用 `-coverprofile` 標誌數重新運行:
```
$ go test -run=Coverage -coverprofile=c.out gopl.io/ch7/eval
ok gopl.io/ch7/eval 0.032s coverage: 68.5% of statements
```
這個標誌數通過插入生成鉤子代碼來統計覆蓋率數據. 也就是, 在運行每個測試前, 它會脩改要測試代碼的副本, 在每個塊都會設置一個佈爾標誌變量. 被脩改後的被測試代碼運行退齣時, 將統計日誌數據寫入 c.out 文件, 打印一部分執行的語句的一個總結. (如果你需要的是摘要,使用 `go test -cover`.)
這個標誌數通過插入生成鉤子代碼來統計覆蓋率數據. 也就是, 在運行每個測試前, 它會脩改要測試代碼的副本, 在每個塊都會設置一個佈爾標誌變量. 被脩改後的被測試代碼運行退齣時, 將統計日誌數據寫入 c.out 文件, 打印一部分執行的語句的一個總結. (如果你需要的是摘要,使用 `go test -cover`.)
如果使用了 `-covermode=count` 標誌數, 那麽將在每個代碼塊插入一個計數器而不是佈爾標誌量. 在統計結果中記了每個塊的執行次數, 這可以用於衡量哪些是被頻繁執行的熱點代碼.
如果使用了 `-covermode=count` 標誌數, 那麽將在每個代碼塊插入一個計數器而不是佈爾標誌量. 在統計結果中記了每個塊的執行次數, 這可以用於衡量哪些是被頻繁執行的熱點代碼.
爲了收集數據, 我們運行了測試覆蓋率工具, 打印了測試日誌, 生成一個HTML報告, 然後在瀏覽器中打開(圖11.3).
@@ -93,14 +93,14 @@ $ go tool cover -html=c.out
![](../images/ch11-03.png)
色的代碼塊被測試覆蓋到了, 紅色的則示沒有被覆蓋到. 爲了清晰起見, 我們將的背景紅色文本的背景設置成了陰影效果. 我們可以馬上發現 unary 操作的 Eval 方法沒有被執行到. 如果我們對這部分未被覆蓋的代碼添加下的測試, 然後重新運行上的命令, 那麽我們將會看到那個紅色部分的代碼也變成色了:
色的代碼塊被測試覆蓋到了, 紅色的則示沒有被覆蓋到. 爲了清晰起見, 我們將的背景紅色文本的背景設置成了陰影效果. 我們可以馬上發現 unary 操作的 Eval 方法沒有被執行到. 如果我們對這部分未被覆蓋的代碼添加下的測試, 然後重新運行上的命令, 那麽我們將會看到那個紅色部分的代碼也變成色了:
```
{"-x * -x", eval.Env{"x": 2}, "4"}
```
不過兩個 panic 語句依然是紅色的. 這是沒有問題的, 因爲這兩個語句不會被執行到.
不過兩個 panic 語句依然是紅色的. 這是沒有問題的, 因爲這兩個語句不會被執行到.
實現 100% 的測試覆蓋率聽起來很好, 但是在具體實踐中通常是不可行的, 也不是值得推薦的做法. 因爲那隻能明代碼被執行過而已, 不意味着代碼是沒有BUG的; 因爲對於邏輯復雜的語句需要對不的輸入執行多次. 有一些語句, 例如上的 panic 語句則永遠都不會被執行到. 另外, 還有一些隱晦的錯誤在現實中很少遇到也很難編寫對應的測試代碼. 測試從本質上來是一個比較務實的工作, 編寫測試代碼和編寫應用代碼的成本對比是需要考慮的. 測試覆蓋率工具可以幫助我們快速識測試薄弱的地方, 但是設計好的測試用例和編寫應用代碼一樣需要嚴密的思考.
實現 100% 的測試覆蓋率聽起來很好, 但是在具體實踐中通常是不可行的, 也不是值得推薦的做法. 因爲那隻能明代碼被執行過而已, 不意味着代碼是沒有BUG的; 因爲對於邏輯復雜的語句需要對不的輸入執行多次. 有一些語句, 例如上的 panic 語句則永遠都不會被執行到. 另外, 還有一些隱晦的錯誤在現實中很少遇到也很難編寫對應的測試代碼. 測試從本質上來是一個比較務實的工作, 編寫測試代碼和編寫應用代碼的成本對比是需要考慮的. 測試覆蓋率工具可以幫助我們快速識測試薄弱的地方, 但是設計好的測試用例和編寫應用代碼一樣需要嚴密的思考.

View File

@@ -1,9 +1,9 @@
## 11.4. 基準測試
基準測試是測量一程序在固定工作負載下的性能. 在Go語言中, 基準測試函數和普通測試函數類似, 但是以Benchmark爲前綴名, 且帶有一 `*testing.B` 類型的參數; `*testing.B` 除了提供和 `*testing.T` 類似的方法, 還有額外一些和性能測量相關的方法. 它還提供了一整數N, 用於指定操作執行的循環次數.
基準測試是測量一程序在固定工作負載下的性能. 在Go語言中, 基準測試函數和普通測試函數類似, 但是以Benchmark爲前綴名, 且帶有一 `*testing.B` 類型的參數; `*testing.B` 除了提供和 `*testing.T` 類似的方法, 還有額外一些和性能測量相關的方法. 它還提供了一整數N, 用於指定操作執行的循環次數.
是 IsPalindrome 函數的基準測試, 其中循環將執行N次.
是 IsPalindrome 函數的基準測試, 其中循環將執行N次.
```Go
import "testing"
@@ -15,7 +15,7 @@ func BenchmarkIsPalindrome(b *testing.B) {
}
```
我們用下的命令運行基準測試. 和普通測試不的是, 默認情況下不運行任何基準測試. 我們需要通過 `-bench` 命令行標誌參數手工指定要運行的基準測試函數. 該參數是一正則達式, 用於匹配要執行的基準測試函數的名字, 默認值是空的. 其中 . 模式將可以匹配所有基準測試函數, 但是這裡總共隻有一基準測試函數, 因此 和 `-bench=IsPalindrome` 參數是等價的效果.
我們用下的命令運行基準測試. 和普通測試不的是, 默認情況下不運行任何基準測試. 我們需要通過 `-bench` 命令行標誌參數手工指定要運行的基準測試函數. 該參數是一正則達式, 用於匹配要執行的基準測試函數的名字, 默認值是空的. 其中 . 模式將可以匹配所有基準測試函數, 但是這裡總共隻有一基準測試函數, 因此 和 `-bench=IsPalindrome` 參數是等價的效果.
```
$ cd $GOPATH/src/gopl.io/ch11/word2
@@ -25,13 +25,13 @@ BenchmarkIsPalindrome-8 1000000 1035 ns/op
ok gopl.io/ch11/word2 2.179s
```
基準測試名的數字後綴部分, 這裡是8, 示運行時對應的 GOMAXPROCS 的值, 這對於一些和發相關的基準測試是重要的信息.
基準測試名的數字後綴部分, 這裡是8, 示運行時對應的 GOMAXPROCS 的值, 這對於一些和發相關的基準測試是重要的信息.
報告顯示每次調用 IsPalindrome 函數花費 1.035微秒, 是執行 1,000,000 次的平均時間. 因爲基準測試驅動器不知道每基準測試函數運行所花的時候, 它會嘗試在眞正運行基準測試前先嘗試用較小的 N 運行測試來估算基準測試函數所需要的時間, 然後推斷一較大的時間保証穩定的測量結果.
報告顯示每次調用 IsPalindrome 函數花費 1.035微秒, 是執行 1,000,000 次的平均時間. 因爲基準測試驅動器不知道每基準測試函數運行所花的時候, 它會嘗試在眞正運行基準測試前先嘗試用較小的 N 運行測試來估算基準測試函數所需要的時間, 然後推斷一較大的時間保証穩定的測量結果.
循環在基準測試函數內實現, 而不是放在基準測試框架內實現, 這樣可以讓每基準測試函數有機會在循環啟動前執行初始化代碼, 這樣不會顯著影響每次迭代的平均運行時間. 如果還是擔心初始化代碼部分對測量時間帶來乾擾, 那可以通過 testing.B 參數的方法來臨時關閉或重置計時器, 不過這些一般很少會用到.
循環在基準測試函數內實現, 而不是放在基準測試框架內實現, 這樣可以讓每基準測試函數有機會在循環啟動前執行初始化代碼, 這樣不會顯著影響每次迭代的平均運行時間. 如果還是擔心初始化代碼部分對測量時間帶來乾擾, 那可以通過 testing.B 參數的方法來臨時關閉或重置計時器, 不過這些一般很少會用到.
現在我們有了一基準測試和普通測試, 我們可以很容易測試新的讓程序運行更快的想法. 也許最明顯的優化是在 IsPalindrome 函數中第二循環的停止檢査, 這樣可以避免每比較都做兩次:
現在我們有了一基準測試和普通測試, 我們可以很容易測試新的讓程序運行更快的想法. 也許最明顯的優化是在 IsPalindrome 函數中第二循環的停止檢査, 這樣可以避免每比較都做兩次:
```Go
n := len(letters)/2
@@ -43,7 +43,7 @@ for i := 0; i < n; i++ {
return true
```
不過很多情況下, 一明顯的優化不一定就能代碼預期的效果. 這改進在基準測試中值帶來了 4% 的性能提昇.
不過很多情況下, 一明顯的優化不一定就能代碼預期的效果. 這改進在基準測試中值帶來了 4% 的性能提昇.
```
$ go test -bench=.
@@ -52,7 +52,7 @@ BenchmarkIsPalindrome-8 1000000 992 ns/op
ok gopl.io/ch11/word2 2.093s
```
另一改進想法是在開始爲每字符預先分配一足夠大的數組, 這樣就可以避免在 append 調用時可能會導緻內存的多次重新分配. 聲明一 letters 數組變量, 指定適的大小, 像這樣,
另一改進想法是在開始爲每字符預先分配一足夠大的數組, 這樣就可以避免在 append 調用時可能會導緻內存的多次重新分配. 聲明一 letters 數組變量, 指定適的大小, 像這樣,
```Go
letters := make([]rune, 0, len(s))
@@ -63,7 +63,7 @@ for _, r := range s {
}
```
改進提昇性能約 35%, 報告結果是基於 2,000,000 次迭代的平均運行時間統計.
改進提昇性能約 35%, 報告結果是基於 2,000,000 次迭代的平均運行時間統計.
```
$ go test -bench=.
@@ -72,7 +72,7 @@ BenchmarkIsPalindrome-8 2000000 697 ns/op
ok gopl.io/ch11/word2 1.468s
```
如這例子所示, 快的程序往往是有很少的內存分配. `-benchmem` 命令行標誌參數將在報告中包含內存的分配數據統計. 我們可以比較優化前後內存的分配情況:
如這例子所示, 快的程序往往是有很少的內存分配. `-benchmem` 命令行標誌參數將在報告中包含內存的分配數據統計. 我們可以比較優化前後內存的分配情況:
```
$ go test -bench=. -benchmem
@@ -90,9 +90,9 @@ BenchmarkIsPalindrome 2000000 807 ns/op 128 B/op 1 allocs/op
一次內存分配代替多次的內存分配節省了75%的分配調用次數和減少近一半的內存需求.
基準測試告訴我們所需的絕對時間依賴給定的具體操作, 兩箇不衕的操作所需時間的差異也是和不環境相關的. 例如, 如果一函數需要 1ms 處理 1,000 元素, 那處理 10000 或 1百萬 將需要多少時間呢? 這樣的比較揭示了漸近增長函數的運行時間. 另一例子: I/O 緩存該設置爲多大呢? 基準測試可以幫助我們選擇較小的緩存但能帶來滿意的性能. 第三例子: 對於一確定的工作那種算法更好? 基準測試可以評估兩種不算法對於相的輸入在不的場景和負載下的優缺點.
基準測試告訴我們所需的絕對時間依賴給定的具體操作, 兩個不同的操作所需時間的差異也是和不環境相關的. 例如, 如果一函數需要 1ms 處理 1,000 元素, 那處理 10000 或 1百萬 將需要多少時間呢? 這樣的比較揭示了漸近增長函數的運行時間. 另一例子: I/O 緩存該設置爲多大呢? 基準測試可以幫助我們選擇較小的緩存但能帶來滿意的性能. 第三例子: 對於一確定的工作那種算法更好? 基準測試可以評估兩種不算法對於相的輸入在不的場景和負載下的優缺點.
比較基準測試都是結構類似的代碼. 它們通常是用一參數的函數, 從幾標誌的基準測試函數入口調用, 就像這樣:
比較基準測試都是結構類似的代碼. 它們通常是用一參數的函數, 從幾標誌的基準測試函數入口調用, 就像這樣:
```Go
func benchmark(b *testing.B, size int) { /* ... */ }
@@ -101,11 +101,11 @@ func Benchmark100(b *testing.B) { benchmark(b, 100) }
func Benchmark1000(b *testing.B) { benchmark(b, 1000) }
```
通過函數參數來指定輸入的大小, 但是參數變量對於每具體的基準測試都是固定的. 要避免直接脩改 b.N 來控製輸入的大小. 除非你將它作爲一固定大小的迭代計算輸入, 否則基準測試的結果將毫無意義.
通過函數參數來指定輸入的大小, 但是參數變量對於每具體的基準測試都是固定的. 要避免直接脩改 b.N 來控製輸入的大小. 除非你將它作爲一固定大小的迭代計算輸入, 否則基準測試的結果將毫無意義.
基準測試對於編寫代碼是很有幫助的, 但是卽使工作完成了應應保存基準測試代碼. 因爲隨着項目的發展, 或者是輸入的增加, 或者是部署到新的操作繫統或不的處理器, 我們可以再次用基準測試來幫助我們改進設計.
基準測試對於編寫代碼是很有幫助的, 但是卽使工作完成了應應保存基準測試代碼. 因爲隨着項目的發展, 或者是輸入的增加, 或者是部署到新的操作繫統或不的處理器, 我們可以再次用基準測試來幫助我們改進設計.
**練習 11.6:** 爲 2.6.2節 的 練習 2.4 和 練習 2.5 的 PopCount 函數編寫基準測試. 看看基於格算法在不情況下的性能.
**練習 11.6:** 爲 2.6.2節 的 練習 2.4 和 練習 2.5 的 PopCount 函數編寫基準測試. 看看基於格算法在不情況下的性能.
**練習 11.7:** 爲 *IntSet (§6.5) 的 Add, UnionWith 和 其他方法編寫基準測試, 使用大量隨機齣入. 你可以讓這些方法跑多快? 選擇字的大小對於性能的影響如何? IntSet 和基於內建 map 的實現相比有多快?

View File

@@ -1,22 +1,22 @@
## 11.5. 剖析
測量基準對於衡量特定操作的性能是有幫助的, 但是, 我們視圖讓程序跑的更快的時候, 我們通常不知道從哪裡開始優化. 每個碼農都應該知道 Donald Knuth 在1974年的 Structured Programming with go to Statements 上所的格言. 雖然經常被解讀爲不重視性能的意思, 但是從原文我們可以看到不的含義:
測量基準對於衡量特定操作的性能是有幫助的, 但是, 我們視圖讓程序跑的更快的時候, 我們通常不知道從哪裡開始優化. 每個碼農都應該知道 Donald Knuth 在1974年的 Structured Programming with go to Statements 上所的格言. 雖然經常被解讀爲不重視性能的意思, 但是從原文我們可以看到不的含義:
> 毫無疑問, 效率會導緻各種濫用. 程序員需要浪費大量的時間思考, 或者擔心, 被部分程序的速度所乾擾, 實際上這些試提昇效率的行爲可能生強烈的負影響, 特彆是噹調試和維護的時候. 我們不應該過度糾結於細節的優化, 應該約97%的場景: 過早的優化是萬惡之源.
> 毫無疑問, 效率會導緻各種濫用. 程序員需要浪費大量的時間思考, 或者擔心, 被部分程序的速度所乾擾, 實際上這些試提昇效率的行爲可能生強烈的負影響, 特別是當調試和維護的時候. 我們不應該過度糾結於細節的優化, 應該約97%的場景: 過早的優化是萬惡之源.
>
> 我們然不應該放棄那關鍵的3%的機會. 一個好的程序員不會因爲這個理由而滿足, 他們會明智地觀察和識哪些是關鍵的代碼; 但是隻有在關鍵代碼已經被確認的前提下纔會進行優化. 對於判斷哪些部分是關鍵代碼是經常容易犯經驗性錯誤的地方, 因此程序員普通使用的測量工具, 使得他們的直覺很不靠譜.
> 我們然不應該放棄那關鍵的3%的機會. 一個好的程序員不會因爲這個理由而滿足, 他們會明智地觀察和識哪些是關鍵的代碼; 但是隻有在關鍵代碼已經被確認的前提下纔會進行優化. 對於判斷哪些部分是關鍵代碼是經常容易犯經驗性錯誤的地方, 因此程序員普通使用的測量工具, 使得他們的直覺很不靠譜.
我們想仔細觀察我們程序的運行速度的時候, 最好的技術是如何識關鍵代碼. 自動化的剖析技術是基於程序執行期間一些抽樣數據, 然後推斷後的執行狀態; 最終生一個運行時間的統計數據文件.
我們想仔細觀察我們程序的運行速度的時候, 最好的技術是如何識關鍵代碼. 自動化的剖析技術是基於程序執行期間一些抽樣數據, 然後推斷後的執行狀態; 最終生一個運行時間的統計數據文件.
Go語言支持多種類型的剖析性能分析, 每一種關註不的方, 但它們都涉及到每個樣記的感興趣的一繫列事件消息, 每個事件都包含函數調用時函數調用堆棧的信息. 內建的 `go test` 工具對幾種分析方式都提供了支持.
Go語言支持多種類型的剖析性能分析, 每一種關註不的方, 但它們都涉及到每個樣記的感興趣的一繫列事件消息, 每個事件都包含函數調用時函數調用堆棧的信息. 內建的 `go test` 工具對幾種分析方式都提供了支持.
CPU分析文件標識了函數執行時所需要的CPU時間. 前運行的繫統程在每隔幾毫秒都會遇到操作繫統的中斷事件, 每次中斷時都會記一個分析文件然後恢復正常的運行.
CPU分析文件標識了函數執行時所需要的CPU時間. 前運行的繫統程在每隔幾毫秒都會遇到操作繫統的中斷事件, 每次中斷時都會記一個分析文件然後恢復正常的運行.
堆分析則記了程序的內存使用情況. 每個內存分配操作都會觸發內部平均內存分配例程, 每個 512KB 的內存申請都會觸發一個事件.
堆分析則記了程序的內存使用情況. 每個內存分配操作都會觸發內部平均內存分配例程, 每個 512KB 的內存申請都會觸發一個事件.
阻塞分析則記了goroutine最大的阻塞操作, 例如繫統調用, 管道發送和接收, 還有取鎖等. 分析庫會記每個goroutine被阻塞時的相關操作.
阻塞分析則記了goroutine最大的阻塞操作, 例如繫統調用, 管道發送和接收, 還有取鎖等. 分析庫會記每個goroutine被阻塞時的相關操作.
在測試環境下隻需要一個標誌數就可以生成各種分析文件. 一次使用多個標誌數時需要心, 因爲分析操作本身也可能會影像程序的運行.
在測試環境下隻需要一個標誌數就可以生成各種分析文件. 一次使用多個標誌數時需要心, 因爲分析操作本身也可能會影像程序的運行.
```
$ go test -cpuprofile=cpu.out
@@ -24,13 +24,13 @@ $ go test -blockprofile=block.out
$ go test -memprofile=mem.out
```
對於一些非測試程序也很容易支持分析的特性, 具體的實現方式和程序是短時間運行的小工具還是長時間運行的服務會有很大不, 因此Go的runtim運行時包提供了程序運行時控製分析特性的接口.
對於一些非測試程序也很容易支持分析的特性, 具體的實現方式和程序是短時間運行的小工具還是長時間運行的服務會有很大不, 因此Go的runtim運行時包提供了程序運行時控製分析特性的接口.
一旦我們已經收集到了用於分析的樣數據, 我們就可以使用 pprof 據來分析這些數據. 這是Go工具箱自帶的一個工具, 但不是一個日常工具, 它對應 `go tool pprof` 命令. 該命令有許多特性和選項, 但是最重要的有兩個, 就是生成這個概要文件的可執行程序和對於的分析日誌文件.
一旦我們已經收集到了用於分析的樣數據, 我們就可以使用 pprof 據來分析這些數據. 這是Go工具箱自帶的一個工具, 但不是一個日常工具, 它對應 `go tool pprof` 命令. 該命令有許多特性和選項, 但是最重要的有兩個, 就是生成這個概要文件的可執行程序和對於的分析日誌文件.
爲了提高分析效率和減少空間, 分析日誌本身不包含函數的名字; 它隻包含函數對應的地址. 也就是pprof需要和分析日誌對於的可執行程序. 雖然 `go test` 命令通常會丟棄臨時用的測試程序, 但是在用分析的時候會將測試程序保存爲 foo.test 文件, 其中 foo 部分對於測試包的名字.
爲了提高分析效率和減少空間, 分析日誌本身不包含函數的名字; 它隻包含函數對應的地址. 也就是pprof需要和分析日誌對於的可執行程序. 雖然 `go test` 命令通常會丟棄臨時用的測試程序, 但是在用分析的時候會將測試程序保存爲 foo.test 文件, 其中 foo 部分對於測試包的名字.
的命令演示了如何生成一個CPU分析文件. 我們選擇 `net/http` 包的一個基準測試. 通常是基於一個已經確定了是關鍵代碼的部分進行基準測試. 基準測試會默認包含單元測試, 這裡我們用 -run=NONE 禁止單元測試.
的命令演示了如何生成一個CPU分析文件. 我們選擇 `net/http` 包的一個基準測試. 通常是基於一個已經確定了是關鍵代碼的部分進行基準測試. 基準測試會默認包含單元測試, 這裡我們用 -run=NONE 禁止單元測試.
```
$ go test -run=NONE -bench=ClientServerParallelTLS64 \
@@ -57,13 +57,13 @@ Showing top 10 nodes out of 166 (cum >= 60ms)
50ms 1.39% 71.59% 60ms 1.67% crypto/elliptic.p256Sum
```
`-text` 標誌數用於指定輸齣格式, 在這裡每行是一個函數, 根據使用CPU的時間來排序. 其中 `-nodecount=10` 標誌數限製了隻輸齣前10行的結果. 對於嚴重的性能問題, 這個文本格式基本可以幫助査明原因了.
`-text` 標誌數用於指定輸齣格式, 在這裡每行是一個函數, 根據使用CPU的時間來排序. 其中 `-nodecount=10` 標誌數限製了隻輸齣前10行的結果. 對於嚴重的性能問題, 這個文本格式基本可以幫助査明原因了.
這個概要文件告訴我們, HTTPS基準測試中 `crypto/elliptic.p256ReduceDegree` 函數佔用了將近一般的CPU資源. 相比之下, 如果一個概要文件中主要是runtime包的內存分配的函數, 那麽減少內存消耗可能是一個值得試的優化策略.
這個概要文件告訴我們, HTTPS基準測試中 `crypto/elliptic.p256ReduceDegree` 函數佔用了將近一般的CPU資源. 相比之下, 如果一個概要文件中主要是runtime包的內存分配的函數, 那麽減少內存消耗可能是一個值得試的優化策略.
對於一些更微妙的問題, 你可能需要使用 pprof 的圖形顯示功能. 這個需要安裝 GraphViz 工具, 可以從 www.graphviz.org 下載. `-web` 用於生成一個有圖文件, 包含CPU的使用和最特點的函數等信息.
對於一些更微妙的問題, 你可能需要使用 pprof 的圖形顯示功能. 這個需要安裝 GraphViz 工具, 可以從 www.graphviz.org 下載. `-web` 用於生成一個有圖文件, 包含CPU的使用和最特點的函數等信息.
這一節我們隻是簡單看了下Go語言的分析據工具. 如果想了解更多, 可以讀 Go官方博客的 Profiling Go Programs 一文.
這一節我們隻是簡單看了下Go語言的分析據工具. 如果想了解更多, 可以讀 Go官方博客的 Profiling Go Programs 一文.

View File

@@ -1,6 +1,6 @@
## 11.6. 示例函數
第三種 `go test` 特別處理的函數是示例函數, 以 Example 函數名開頭. 示例函數沒有函數參數和返迴值. 下是 IsPalindrome 函數對應的示例函數:
第三種 `go test` 特別處理的函數是示例函數, 以 Example 函數名開頭. 示例函數沒有函數參數和返迴值. 下是 IsPalindrome 函數對應的示例函數:
```Go
func ExampleIsPalindrome() {
@@ -12,13 +12,13 @@ func ExampleIsPalindrome() {
}
```
示例函數有三個用處. 最主要的一個是用於文檔: 一個包的例子可以更簡潔直觀的方式來演示函數的用法, 會文字描述會更直接易懂, 特別是作一個提醒或快速參考時. 一個例子函數也可以方便展示屬於一個接口的幾種類型或函數直接的關繫, 所有的文檔都必關聯到一個地方, 就像一個類型或函數聲明都統一到包一樣. 時, 示例函數和註釋並不一樣, 示例函數是完整眞是的Go代碼, 需要介紹編譯器的編譯時檢査, 這樣可以保証示例代碼不會腐爛成不能使用的舊代碼.
示例函數有三個用處. 最主要的一個是用於文檔: 一個包的例子可以更簡潔直觀的方式來演示函數的用法, 會文字描述會更直接易懂, 特別是作一個提醒或快速參考時. 一個例子函數也可以方便展示屬於一個接口的幾種類型或函數直接的關繫, 所有的文檔都必關聯到一個地方, 就像一個類型或函數聲明都統一到包一樣. 時, 示例函數和註釋並不一樣, 示例函數是完整眞是的Go代碼, 需要介紹編譯器的編譯時檢査, 這樣可以保証示例代碼不會腐爛成不能使用的舊代碼.
根據示例函數的後綴名部分, godoc 的web文檔會將一個示例函數關聯到某個具體函數或包本身, 因此 ExampleIsPalindrome 示例函數將是 IsPalindrome 函數文檔的一部分, Example 示例函數將是包文檔的一部分.
示例文檔的第二個用處是在 `go test` 執行測試的時候也運行示例函數測試. 如果示例函數內含有類似上例子中的 `/ Output:` 這樣的註釋, 那測試工具會執行這個示例函數, 然後檢測這個示例函數的標準輸齣和註釋是否匹配.
示例文檔的第二個用處是在 `go test` 執行測試的時候也運行示例函數測試. 如果示例函數內含有類似上例子中的 `/ Output:` 這樣的註釋, 那測試工具會執行這個示例函數, 然後檢測這個示例函數的標準輸齣和註釋是否匹配.
示例函數的第三個目的提供一個眞實的演練場. golang.org 是由 dogoc 提供的服務, 它使用了 Go Playground 技讓用戶可以在瀏覽器中在綫編輯和運行每個示例函數, 就像 圖 11.4 所示的那樣. 這通常是學習函數使用或Go語言特性的最快方式.
示例函數的第三個目的提供一個眞實的演練場. golang.org 是由 dogoc 提供的服務, 它使用了 Go Playground 技讓用戶可以在瀏覽器中在綫編輯和運行每個示例函數, 就像 圖 11.4 所示的那樣. 這通常是學習函數使用或Go語言特性的最快方式.
![](../images/ch11-04.png)

View File

@@ -1,16 +1,16 @@
# 第十一章 測試
Maurice Wilkes, 第一個存儲程序計算機 EDSAC 的設計者, 1949年在他的實驗室爬樓梯時有一個頓悟. 在《計算機先驅迴憶》(Memoirs of a Computer Pioneer), 他迴憶到: "忽然間有一種醍醐灌頂的感覺, 我整個後半生的美好時光都將在尋找程序BUG中度過了.". 肯定從那之後的每一個存儲程序的碼農都可以情 Wilkes 的想法, 雖然也許不是沒有人睏惑於他對軟件開的難度的天眞看法.
Maurice Wilkes, 第一個存儲程序計算機 EDSAC 的設計者, 1949年在他的實驗室爬樓梯時有一個頓悟. 在《計算機先驅迴憶》(Memoirs of a Computer Pioneer), 他迴憶到: "忽然間有一種醍醐灌頂的感覺, 我整個後半生的美好時光都將在尋找程序BUG中度過了.". 肯定從那之後的每一個存儲程序的碼農都可以情 Wilkes 的想法, 雖然也許不是沒有人睏惑於他對軟件開的難度的天眞看法.
現在的程序已經遠比 Wilkes 時代的更大也更復雜, 也有許多技可以讓軟件的復雜性可得到控製. 其中有兩種技在實踐中明是比較有效的. 第一種是代碼在被正式部署前需要進行代碼評審. 第二種是測試, 是本章的討論主題.
現在的程序已經遠比 Wilkes 時代的更大也更復雜, 也有許多技可以讓軟件的復雜性可得到控製. 其中有兩種技在實踐中明是比較有效的. 第一種是代碼在被正式部署前需要進行代碼評審. 第二種是測試, 是本章的討論主題.
我們說測試的時候一般是指自動化測試, 也就是寫一些小的程序用來檢測被測試代碼(產品代碼)的行和預期的一樣, 這些通常都是精心挑選的執行某些特定的功能或者是通過隨機性的輸入要驗邊界的處理.
我們說測試的時候一般是指自動化測試, 也就是寫一些小的程序用來檢測被測試代碼(產品代碼)的行和預期的一樣, 這些通常都是精心挑選的執行某些特定的功能或者是通過隨機性的輸入要驗邊界的處理.
軟件測試是一個鉅大的領域. 測試的任務一般佔據了一些程序員的部分時間和另一些程序員的全部時間. 和軟件測試技相關的圖書或博客文章有成韆上萬之多. 每一種主流的編程語言, 都有一打的用於測試的軟件包, 也有大量的測試相關的理論, 每種都吸引了大量技先驅和追隨者. 這些都足以說服那些想要編寫有效測試的程序員重新學習一套全新的技能.
軟件測試是一個鉅大的領域. 測試的任務一般佔據了一些程序員的部分時間和另一些程序員的全部時間. 和軟件測試技相關的圖書或博客文章有成韆上萬之多. 每一種主流的編程語言, 都有一打的用於測試的軟件包, 也有大量的測試相關的理論, 每種都吸引了大量技先驅和追隨者. 這些都足以說服那些想要編寫有效測試的程序員重新學習一套全新的技能.
Go語言的測試技是相對低級的. 它依賴一個 'go test' 測試命令, 和一組按照約定方式編寫的測試函數, 測試命令可以運行測試函數. 編寫相對輕量級的純測試代碼是有效的, 而且它很容易延伸到基準測試和示例文檔.
Go語言的測試技是相對低級的. 它依賴一個 'go test' 測試命令, 和一組按照約定方式編寫的測試函數, 測試命令可以運行測試函數. 編寫相對輕量級的純測試代碼是有效的, 而且它很容易延伸到基準測試和示例文檔.
在實踐中, 編寫測試代碼和編寫程序本身沒有多大區. 我們編寫的每一個函數也是鍼對每個具體的任務. 我們必鬚小心處理邊界條件, 思考適的數據結構, 推斷適的輸入應該產生什樣的結果輸齣. 編程測試代碼和編寫普通的Go代碼過程是類似的; 它不需要學習新的符號, 規則和工具.
在實踐中, 編寫測試代碼和編寫程序本身沒有多大區. 我們編寫的每一個函數也是鍼對每個具體的任務. 我們必鬚小心處理邊界條件, 思考適的數據結構, 推斷適的輸入應該產生什樣的結果輸齣. 編程測試代碼和編寫普通的Go代碼過程是類似的; 它不需要學習新的符號, 規則和工具.