回到简体

This commit is contained in:
chai2010
2016-02-15 11:06:34 +08:00
parent 9e878f9944
commit 2b37b23285
177 changed files with 2354 additions and 2354 deletions

View File

@@ -1,7 +1,7 @@
## 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節討論示例函數的細節
在\*_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,10 +1,10 @@
### 11.2.1. 隨機測試
### 11.2.1. 随机测试
表格驅動的測試便於構造基精心挑選的測試數據的測試用例。另一種測試思路是隨機測試,也就是通過構造更泛的隨機輸入來測試探索函的行
表格驱动的测试便于构造基精心挑选的测试数据的测试用例。另一种测试思路是随机测试,也就是通过构造更广泛的随机输入来测试探索函的行
麽對於一個隨機的輸入,我如何能知道希望的輸出結果呢?里有兩種處理策略。第一個是編寫另一個對照函,使用簡單和清晰的算法,然效率低但是行和要測試的函是一致的,然後針對相同的隨機輸入檢査兩者的輸出結果。第二是生成的隨機輸入的數據遵循特定的模式,這樣我們就可以知道期望的出的模式。
么对于一个随机的输入,我如何能知道希望的输出结果呢?里有两种处理策略。第一个是编写另一个对照函,使用简单和清晰的算法,然效率低但是行和要测试的函是一致的,然后针对相同的随机输入检查两者的输出结果。第二是生成的随机输入的数据遵循特定的模式,这样我们就可以知道期望的出的模式。
下面的例子使用的是第二方法randomPalindrome函數用於隨機生成文字符串。
下面的例子使用的是第二方法randomPalindrome函数用于随机生成文字符串。
```Go
import "math/rand"
@@ -37,13 +37,13 @@ func TestRandomPalindromes(t *testing.T) {
}
```
雖然隨機測試會有不定因素,但是它也是至重要的,我可以從失敗測試的日誌獲取足的信息。在我的例子中,入IsPalindrome的p參數將告訴我們眞實的數據,但是對於函數將接受更複雜的輸入,不需要保存所有的入,要日誌中簡單地記録隨機數種子卽可(像上面的方式)。有了這些隨機數初始化子,我可以很容易脩改測試代碼以重現失敗的隨機測試
虽然随机测试会有不定因素,但是它也是至重要的,我可以从失败测试的日志获取足的信息。在我的例子中,入IsPalindrome的p参数将告诉我们真实的数据,但是对于函数将接受更复杂的输入,不需要保存所有的入,要日志中简单地记录随机数种子即可(像上面的方式)。有了这些随机数初始化子,我可以很容易修改测试代码以重现失败的随机测试
使用當前時間作爲隨機種子,在整個過程中的每次運行測試命令時都將探索新的隨機數據。如果你使用的是定期行的自動化測試集成繫統,隨機測試將特别有值。
使用当前时间作为随机种子,在整个过程中的每次运行测试命令时都将探索新的随机数据。如果你使用的是定期行的自动化测试集成系统,随机测试将特别有值。
**練習 11.3:** TestRandomPalindromes測試函數隻測試了迴文字符串。編寫新的隨機測試生成器,用於測試隨機生成的非文字符串。
**练习 11.3:** TestRandomPalindromes测试函数只测试了回文字符串。编写新的随机测试生成器,用于测试随机生成的非文字符串。
**練習 11.4:** 改randomPalindrome函以探索IsPalindrome是否對標點和空格做了正確處理。
**练习 11.4:** 改randomPalindrome函以探索IsPalindrome是否对标点和空格做了正确处理。

View File

@@ -1,8 +1,8 @@
### 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可能返回的错误
<u><i>gopl.io/ch11/echo</i></u>
```Go
@@ -41,7 +41,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
@@ -82,15 +82,15 @@ 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!
```
`go test`出如下:
`go test`出如下:
```
$ go test gopl.io/ch11/echo
@@ -100,6 +100,6 @@ 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,14 +1,14 @@
### 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%的存储配额之后将发送提醒件。
<u><i>gopl.io/ch11/storage1</i></u>
```Go
@@ -48,7 +48,7 @@ func CheckQuota(username string) {
}
```
們想測試這個代碼,但是我們併不希望發送眞實的郵件。因此我們將郵件處理邏輯放到一私有的notifyUser函中。
们想测试这个代码,但是我们并不希望发送真实的邮件。因此我们将邮件处理逻辑放到一私有的notifyUser函中。
<u><i>gopl.io/ch11/storage2</i></u>
```Go
@@ -73,7 +73,7 @@ func CheckQuota(username string) {
}
```
在我可以在測試中用僞郵件發送函替代眞實的郵件發送函。它隻是簡單記録要通知的用戶和郵件的容。
在我可以在测试中用伪邮件发送函替代真实的邮件发送函。它只是简单记录要通知的用户和邮件的容。
```Go
package storage
@@ -107,7 +107,7 @@ func TestCheckQuotaNotifiesUser(t *testing.T) {
}
```
里有一個問題:當測試函數返迴後CheckQuota不能正常工作,因notifyUsers依然使用的是測試函數的僞發送郵件函數(當更新全局象的時候總會有這種風險)。 我們必須脩改測試代碼恢複notifyUsers原先的狀態以便後續其他的測試沒有影,要保所有的行路徑後都能恢,包括測試失敗或panic常的情形。在這種情況下我們建議使用defer語句來延後執行處理恢的代
里有一个问题:当测试函数返回后CheckQuota不能正常工作,因notifyUsers依然使用的是测试函数的伪发送邮件函数(当更新全局象的时候总会有这种风险)。 我们必须修改测试代码恢复notifyUsers原先的状态以便后续其他的测试没有影,要保所有的行路径后都能恢,包括测试失败或panic常的情形。在这种情况下我们建议使用defer语句来延后执行处理恢的代
```Go
func TestCheckQuotaNotifiesUser(t *testing.T) {
@@ -124,6 +124,6 @@ func TestCheckQuotaNotifiesUser(t *testing.T) {
}
```
這種處理模式可以用來暫時保存和恢所有的全局量,包括命令行標誌參數、調試選項和優化參數;安和移除致生産代碼産生一些調試信息的子函數;還有有些誘導生産代碼進入某些重要狀態的改,比如超時、錯誤,甚至是一些刻意造的併發行爲等因素。
这种处理模式可以用来暂时保存和恢所有的全局量,包括命令行标志参数、调试选项和优化参数;安和移除致生产代码产生一些调试信息的子函数;还有有些诱导生产代码进入某些重要状态的改,比如超时、错误,甚至是一些刻意造的并发行为等因素。
這種方式使用全局量是安全的,因go test命令併不會同時併發地執行多個測試
这种方式使用全局量是安全的,因go test命令并不会同时并发地执行多个测试

View File

@@ -1,20 +1,20 @@
### 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)
這樣的行在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)
過迴避循環導入依賴,擴展測試包可以更活的編寫測試,特别是集成測試(需要測試多個組件之的交互),可以像普通用程序那自由地入其他包。
过回避循环导入依赖,扩展测试包可以更活的编写测试,特别是集成测试(需要测试多个组件之的交互),可以像普通用程序那自由地入其他包。
可以用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 %}
@@ -36,9 +36,9 @@ $ go list -f={{.TestGoFiles}} fmt
{% endraw %}
包的測試代碼通常都在些文件中,不fmt包非如此;稍後我們再解export_test.go文件的作用。
包的测试代码通常都在些文件中,不fmt包非如此;稍后我们再解export_test.go文件的作用。
XTestGoFiles表示的是屬於測試擴展包的測試代碼也就是fmt_test包因此它們必須先導入fmt包。同樣,這些文件也是在測試時被構建運行:
XTestGoFiles表示的是属于测试扩展包的测试代码也就是fmt_test包因此它们必须先导入fmt包。同样,这些文件也是在测试时被构建运行:
{% raw %}
@@ -49,11 +49,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
@@ -61,5 +61,5 @@ package fmt
var IsSpace = isSpace
```
這個測試文件併沒有定義測試代碼;它是通fmt.IsSpace簡單導出了部的isSpace函,提供給測試擴展包使用。這個技巧可以泛用於位於測試擴展包的白盒測試
这个测试文件并没有定义测试代码;它是通fmt.IsSpace简单导出了部的isSpace函,提供给测试扩展包使用。这个技巧可以广泛用于位于测试扩展包的白盒测试

View File

@@ -1,10 +1,10 @@
### 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
import (
@@ -25,7 +25,7 @@ func TestSplit(t *testing.T) {
}
```
從這個意義上説,斷言函犯了早抽象的錯誤:僅僅測試兩個整數是否相同,而放了根上下文提供更有意義的錯誤信息的做法。我可以根據具體的錯誤打印一更有值的錯誤信息,就像下面例子那樣。測試在隻有一次重的模式出現時引入抽象。
从这个意义上说,断言函犯了早抽象的错误:仅仅测试两个整数是否相同,而放了根上下文提供更有意义的错误信息的做法。我可以根据具体的错误打印一更有值的错误信息,就像下面例子那样。测试在只有一次重的模式出现时引入抽象。
```Go
func TestSplit(t *testing.T) {
@@ -39,10 +39,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. 避免的不定的測試
### 11.2.6. 避免的不定的测试
如果一個應用程序對於新出的但有效的輸入經常失敗説明程序不夠穩健;同如果一個測試僅僅因爲聲音變化就會導致失也是不合邏輯的。就像一個不夠穩健的程序會挫敗它的用戶一樣,一脆弱性測試同樣會激怒它的維護者。最脆弱的測試代碼會在程序有任何化的時候産生不同的果,時好時壞,處理它們會耗費大量的時間但是併不會得到任何好
如果一个应用程序对于新出的但有效的输入经常失败说明程序不够稳健;同如果一个测试仅仅因为声音变化就会导致失也是不合逻辑的。就像一个不够稳健的程序会挫败它的用户一样,一脆弱性测试同样会激怒它的维护者。最脆弱的测试代码会在程序有任何化的时候产生不同的果,时好时坏,处理它们会耗费大量的时间但是并不会得到任何好
當一個測試函數産生一個複雜的輸出如一個很長的字符串,或一精心設計的數據結構或一文件,它可以用於和預設的“golden”結果數據對比,用這種簡單方式寫測試是誘人的。但是隨着項目的展,出的某些部分很可能會發生變化,管很可能是一個改進的實現導致的。而且不僅僅是輸出部分,函數複雜複製的輸入部分可能也跟着化了,因此測試使用的入也就不在有效了。
当一个测试函数产生一个复杂的输出如一个很长的字符串,或一精心设计的数据结构或一文件,它可以用于和预设的“golden”结果数据对比,用这种简单方式写测试是诱人的。但是随着项目的展,出的某些部分很可能会发生变化,管很可能是一个改进的实现导致的。而且不仅仅是输出部分,函数复杂复制的输入部分可能也跟着化了,因此测试使用的入也就不在有效了。
避免脆弱測試代碼的方法是隻檢測你眞正關心的性。保持測試代碼的簡潔和內部結構的穩定。特别是對斷言部分要有所選擇。不要檢査字符串的全匹配,但是找相的子字符串,因某些子字符串在目的展中是比較穩定不的。通常編寫一個重複雜的輸出中提取必要精信息以用於斷言是值得的,雖然這可能會帶來很多前期的工作,但是它可以助迅速及時脩複因爲項目演化而致的不合邏輯的失敗測試
避免脆弱测试代码的方法是只检测你真正关心的性。保持测试代码的简洁和内部结构的稳定。特别是对断言部分要有所选择。不要检查字符串的全匹配,但是找相的子字符串,因某些子字符串在目的展中是比较稳定不的。通常编写一个重复杂的输出中提取必要精信息以用于断言是值得的,虽然这可能会带来很多前期的工作,但是它可以助迅速及时修复因为项目演化而致的不合逻辑的失败测试

View File

@@ -1,6 +1,6 @@
## 11.2. 測試函數
## 11.2. 测试函数
個測試函數必須導入testing包。測試函數有如下的名:
个测试函数必须导入testing包。测试函数有如下的名:
```Go
func TestName(t *testing.T) {
@@ -8,7 +8,7 @@ func TestName(t *testing.T) {
}
```
測試函數的名字必以Test開頭,可選的後綴名必以大字母開頭
测试函数的名字必以Test开头,可选的后缀名必以大字母开头
```Go
func TestSin(t *testing.T) { /* ... */ }
@@ -16,7 +16,7 @@ func TestCos(t *testing.T) { /* ... */ }
func TestLog(t *testing.T) { /* ... */ }
```
其中t參數用於報告測試失敗和附加的日信息。讓我們定義一個實例包gopl.io/ch11/word1其中有一個函數IsPalindrome用於檢査一個字符串是否前向後和從後向前都是一的。(下面這個實現對於一個字符串是否是文字符串前後重複測試了兩次;我們稍後會再討論這個問題。)
其中t参数用于报告测试失败和附加的日信息。让我们定义一个实例包gopl.io/ch11/word1其中有一个函数IsPalindrome用于检查一个字符串是否前向后和从后向前都是一的。(下面这个实现对于一个字符串是否是文字符串前后重复测试了两次;我们稍后会再讨论这个问题。)
<u><i>gopl.io/ch11/word1</i></u>
```Go
@@ -35,7 +35,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
@@ -58,7 +58,7 @@ func TestNonPalindrome(t *testing.T) {
}
```
`go test`命令如果沒有參數指定包那麽將默認采用前目録對應的包(和`go build`命令一)。我可以用下面的命令建和運行測試
`go test`命令如果没有参数指定包那么将默认采用前目录对应的包(和`go build`命令一)。我可以用下面的命令建和运行测试
```
$ cd $GOPATH/src/gopl.io/ch11/word1
@@ -66,7 +66,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) {
@@ -83,9 +83,9 @@ func TestCanalPalindrome(t *testing.T) {
}
```
了避免兩次輸入較長的字符串,我使用了提供了有似Printf格式化功能的 Errorf函數來滙報錯誤結果。
了避免两次输入较长的字符串,我使用了提供了有似Printf格式化功能的 Errorf函数来汇报错误结果。
添加了這兩個測試用例之`go test`迴了測試失敗的信息。
添加了这两个测试用例之`go test`回了测试失败的信息。
```
$ go test
@@ -97,11 +97,11 @@ FAIL
FAIL gopl.io/ch11/word1 0.014s
```
編寫測試用例併觀察到測試用例觸發了和用戶報告的錯誤相同的描述是一好的測試習慣。隻有這樣,我才能定位我們要眞正解決的問題
编写测试用例并观察到测试用例触发了和用户报告的错误相同的描述是一好的测试习惯。只有这样,我才能定位我们要真正解决的问题
寫測試用例的另外的好是,運行測試通常比手工描述告的理更快,這讓我們可以行快速地迭代。如果測試集有很多運行緩慢的測試,我可以通過隻選擇運行某些特定的測試來加快測試速度。
写测试用例的另外的好是,运行测试通常比手工描述告的理更快,这让我们可以行快速地迭代。如果测试集有很多运行缓慢的测试,我可以通过只选择运行某些特定的测试来加快测试速度。
參數`-v`可用打印每個測試函數的名字和運行時間
参数`-v`可用打印每个测试函数的名字和运行时间
```
$ go test -v
@@ -120,7 +120,7 @@ exit status 1
FAIL gopl.io/ch11/word1 0.017s
```
參數`-run`對應一個正則表達式,隻有測試函數名被它正匹配的測試函數才會`go test`測試命令行:
参数`-run`对应一个正则表达式,只有测试函数名被它正匹配的测试函数才会`go test`测试命令行:
```
$ go test -v -run="French|Canal"
@@ -135,11 +135,11 @@ exit status 1
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我们仔细重写了函
<u><i>gopl.io/ch11/word2</i></u>
```Go
@@ -166,7 +166,7 @@ func IsPalindrome(s string) bool {
}
```
時我們也將之前的所有測試數據合併到了一個測試中的表格中。
时我们也将之前的所有测试数据合并到了一个测试中的表格中。
```Go
func TestIsPalindrome(t *testing.T) {
@@ -196,24 +196,24 @@ func TestIsPalindrome(t *testing.T) {
}
```
在我的新測試阿都通了:
在我的新测试阿都通了:
```
$ 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.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,12 +1,12 @@
## 11.3. 測試覆蓋
## 11.3. 测试覆盖
就其性而言,測試不可能是完整的。計算機科學家Edsger Dijkstra曾説過:“測試可以示存在缺陷,但是不是説沒有BUG。”再多的測試也不能明一程序有BUG。在最好的情下,測試可以增強我們的信心:代在我們測試的環境是可以正常工作的。
就其性而言,测试不可能是完整的。计算机科学家Edsger Dijkstra曾说过:“测试可以示存在缺陷,但是不是说没有BUG。”再多的测试也不能明一程序有BUG。在最好的情下,测试可以增强我们的信心:代在我们测试的环境是可以正常工作的。
測試驅動觸發運行到的被測試函數的代碼數目稱爲測試的覆率。測試覆蓋率併不能量化——甚至連最簡單的動態程序也以精確測量——但是可以啟發併幫助我們編寫的有效的測試代碼
测试驱动触发运行到的被测试函数的代码数目称为测试的覆率。测试覆盖率并不能量化——甚至连最简单的动态程序也以精确测量——但是可以启发并帮助我们编写的有效的测试代码
這些幫助信息中句的覆率是最簡單和最泛使用的。句的覆率是指在測試中至少被行一次的代碼占總代碼數的比例。在本中,我使用`go test`命令中集成的測試覆蓋率工具,度量下面代碼的測試覆蓋率,助我們識别測試和我期望的差距。
这些帮助信息中句的覆率是最简单和最广泛使用的。句的覆率是指在测试中至少被行一次的代码占总代码数的比例。在本中,我使用`go test`命令中集成的测试覆盖率工具,度量下面代码的测试覆盖率,助我们识别测试和我期望的差距。
下面的代是一表格驅動的測試,用於測試第七章的表式求值程序:
下面的代是一表格驱动的测试,用于测试第七章的表式求值程序:
<u><i>gopl.io/ch7/eval</i></u>
```Go
@@ -45,7 +45,7 @@ func TestCoverage(t *testing.T) {
}
```
首先,我們要確保所有的測試都正常通
首先,我们要确保所有的测试都正常通
```
$ go test -v -run=Coverage gopl.io/ch7/eval
@@ -55,7 +55,7 @@ PASS
ok gopl.io/ch7/eval 0.011s
```
下面這個命令可以顯示測試覆蓋率工具的使用用法:
下面这个命令可以显示测试覆盖率工具的使用用法:
```
$ go tool cover
@@ -68,20 +68,20 @@ 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)。
了收集数据,我们运行了测试覆盖率工具,打印了测试日志,生成一HTML告,然后在浏览器中打开(图11.3)。
```
$ go tool cover -html=c.out
@@ -89,12 +89,12 @@ $ 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,8 +1,8 @@
## 11.4. 基準測試
## 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"
@@ -14,7 +14,7 @@ func BenchmarkIsPalindrome(b *testing.B) {
}
```
用下面的命令行基準測試。和普通測試不同的是,默認情況下不行任何基準測試。我需要通`-bench`命令行標誌參數手工指定要行的基準測試函數。該參數是一個正則表達式,用匹配要行的基準測試函數的名字,默值是空的。其中“.”模式可以匹配所有基準測試函數,但是這里總共隻有一個基準測試函數,因此和`-bench=IsPalindrome`參數是等的效果。
用下面的命令行基准测试。和普通测试不同的是,默认情况下不行任何基准测试。我需要通`-bench`命令行标志参数手工指定要行的基准测试函数。该参数是一个正则表达式,用匹配要行的基准测试函数的名字,默值是空的。其中“.”模式可以匹配所有基准测试函数,但是这里总共只有一个基准测试函数,因此和`-bench=IsPalindrome`参数是等的效果。
```
$ cd $GOPATH/src/gopl.io/ch11/word2
@@ -24,13 +24,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
@@ -42,7 +42,7 @@ for i := 0; i < n; i++ {
return true
```
很多情下,一個明顯的優化併不一定就能代碼預期的效果。這個改進在基準測試中隻帶來了4%的性能提
很多情下,一个明显的优化并不一定就能代码预期的效果。这个改进在基准测试中只带来了4%的性能提
```
$ go test -bench=.
@@ -51,7 +51,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))
@@ -62,7 +62,7 @@ for _, r := range s {
}
```
這個改進提陞性能35%報告結果是基2,000,000次迭代的平均運行時間統計
这个改进提升性能35%报告结果是基2,000,000次迭代的平均运行时间统计
```
$ go test -bench=.
@@ -71,7 +71,7 @@ BenchmarkIsPalindrome-8 2000000 697 ns/op
ok gopl.io/ch11/word2 1.468s
```
這個例子所示,快的程序往往是伴隨着較少的存分配。`-benchmem`命令行標誌參數將在報告中包含存的分配數據統計。我可以比較優化前後內存的分配情
这个例子所示,快的程序往往是伴随着较少的存分配。`-benchmem`命令行标志参数将在报告中包含存的分配数据统计。我可以比较优化前后内存的分配情
```
$ go test -bench=. -benchmem
@@ -79,7 +79,7 @@ PASS
BenchmarkIsPalindrome 1000000 1026 ns/op 304 B/op 4 allocs/op
```
這是優化之後的結果:
这是优化之后的结果:
```
$ go test -bench=. -benchmem
@@ -87,11 +87,11 @@ PASS
BenchmarkIsPalindrome 2000000 807 ns/op 128 B/op 1 allocs/op
```
用一次存分配代替多次的存分配省了75%的分配調用次數和減少近一半的存需求。
用一次存分配代替多次的存分配省了75%的分配用次数和减少近一半的存需求。
這個基準測試告訴我們所需的絶對時間依賴給定的具操作,兩個不同的操作所需時間的差也是和不同境相的。例如,如果一個函數需要1ms理1,000元素,那麽處理10000或1百萬將需要多少時間呢?這樣的比揭示了近增長函數的運行時間。另一例子I/O緩存該設置爲多大呢?基準測試可以助我們選擇較小的存但能帶來滿意的性能。第三例子:對於一個確定的工作那算法更好?基準測試可以評估兩種不同算法對於相同的入在不同的景和負載下的優缺點
这个基准测试告诉我们所需的绝对时间依赖给定的具操作,两个不同的操作所需时间的差也是和不同境相的。例如,如果一个函数需要1ms理1,000元素,那么处理10000或1百万将需要多少时间呢?这样的比揭示了近增长函数的运行时间。另一例子I/O缓存该设置为多大呢?基准测试可以助我们选择较小的存但能带来满意的性能。第三例子:对于一个确定的工作那算法更好?基准测试可以评估两种不同算法对于相同的入在不同的景和负载下的优缺点
一般比較基準測試都是結構類似的代。它通常是采用一個參數的函數,從幾個標誌的基準測試函數入口調用,就像這樣
一般比较基准测试都是结构类似的代。它通常是采用一个参数的函数,从几个标志的基准测试函数入口用,就像这样
```Go
func benchmark(b *testing.B, size int) { /* ... */ }
@@ -100,13 +100,13 @@ 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的實現相比有多快?
**练习 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的runtime運行時包提供了程序運行時控製分析特性的接口。
对于一些非测试程序也很容易支持分析的特性,具体的实现方式和程序是短时间运行的小工具还是长时间运行的服务会有很大不同因此Go的runtime运行时包提供了程序运行时控制分析特性的接口。
一旦我們已經收集到了用分析的采樣數據,我就可以使用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,10 +57,10 @@ 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工具可以 http://www.graphviz.org 下載。參數`-web`生成一有向文件包含了CPU的使用和最熱點的函等信息。
对于一些更微妙的问题你可能需要使用pprof的图形显示功能。这个需要安GraphViz工具可以 http://www.graphviz.org 下载。参数`-web`生成一有向文件包含了CPU的使用和最热点的函等信息。
這一節我們隻是簡單看了下Go言的分析工具。如果想了解更多,可以閲讀Go官方博客的“Profiling Go Programs”一文。
这一节我们只是简单看了下Go言的分析工具。如果想了解更多,可以阅读Go官方博客的“Profiling Go Programs”一文。

View File

@@ -1,6 +1,6 @@
## 11.6. 示例函
## 11.6. 示例函
第三`go test`特别理的函是示例函以Example爲函數名開頭。示例函數沒有函數參數和返值。下面是IsPalindrome函數對應的示例函
第三`go test`特别理的函是示例函以Example为函数名开头。示例函数没有函数参数和返值。下面是IsPalindrome函数对应的示例函
```Go
func ExampleIsPalindrome() {
@@ -12,14 +12,14 @@ func ExampleIsPalindrome() {
}
```
示例函有三個用處。最主要的一是作爲文檔:一包的例子可以更簡潔直觀的方式演示函的用法,比文字描述更直接易懂,特别是作爲一個提醒或快速參考時。一示例函也可以方便展示屬於同一接口的幾種類型或函直接的關繫,所有的文都必須關聯到一地方,就像一個類型或函數聲明都一到包一。同,示例函數和註釋併不一,示例函是完整眞實的Go代,需要接受編譯器的編譯時檢査,這樣可以保示例代碼不會腐爛成不能使用的舊代碼
示例函有三个用处。最主要的一是作为文档:一包的例子可以更简洁直观的方式演示函的用法,比文字描述更直接易懂,特别是作为一个提醒或快速参考时。一示例函也可以方便展示属于同一接口的几种类型或函直接的关系,所有的文都必须关联到一地方,就像一个类型或函数声明都一到包一。同,示例函数和注释并不一,示例函是完整真实的Go代,需要接受编译器的编译时检查,这样可以保示例代码不会腐烂成不能使用的旧代码
示例函數的後綴名部分godoc的web文檔會將一個示例函數關聯到某個具體函數或包本身因此ExampleIsPalindrome示例函數將是IsPalindrome函數文檔的一部分Example示例函數將是包文的一部分。
示例函数的后缀名部分godoc的web文档会将一个示例函数关联到某个具体函数或包本身因此ExampleIsPalindrome示例函数将是IsPalindrome函数文档的一部分Example示例函数将是包文的一部分。
示例文的第二個用處是在`go test`執行測試的時候也行示例函數測試。如果示例函數內含有似上面例子中的`// Output:`格式的註釋,那麽測試工具會執行這個示例函,然後檢測這個示例函數的標準輸出和註釋是否匹配。
示例文的第二个用处是在`go test`执行测试的时候也行示例函数测试。如果示例函数内含有似上面例子中的`// Output:`格式的注释,那么测试工具会执行这个示例函,然后检测这个示例函数的标准输出和注释是否匹配。
示例函的第三目的提供一個眞實的演練場。 http://golang.org 就是由godoc提供的文檔服務它使用了Go Playground提高的技術讓用戶可以在瀏覽器中在線編輯和運行每示例函,就像11.4所示的那樣。這通常是學習函數使用或Go言特性最快捷的方式。
示例函的第三目的提供一个真实的演练场。 http://golang.org 就是由godoc提供的文档服务它使用了Go Playground提高的技术让用户可以在浏览器中在线编辑和运行每示例函,就像11.4所示的那样。这通常是学习函数使用或Go言特性最快捷的方式。
![](../images/ch11-04.png)
書最後的兩掌是討論reflect和unsafe包一般的Go用很少直接使用它。因此,如果你還沒有寫過任何眞實的Go程序的話,現在可以忽略剩部分而直接編碼了。
书最后的两掌是讨论reflect和unsafe包一般的Go用很少直接使用它。因此,如果你还没有写过任何真实的Go程序的话,现在可以忽略剩部分而直接编码了。

View File

@@ -1,13 +1,13 @@
# 第十一章 測試
# 第十一章 测试
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代码过程是似的;它不需要学习新的符号、规则和工具。