生活随笔
收集整理的這篇文章主要介紹了
《Go语言圣经》学习笔记 第十一章 测试
小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
《Go語(yǔ)言圣經(jīng)》學(xué)習(xí)筆記 第十一章 測(cè)試
目錄
go test 測(cè)試函數(shù) 測(cè)試覆蓋率 基準(zhǔn)測(cè)試 剖析 示例函數(shù)
注:學(xué)習(xí)《Go語(yǔ)言圣經(jīng)》筆記,PDF點(diǎn)擊下載,建議看書。 Go語(yǔ)言小白學(xué)習(xí)筆記,書上的內(nèi)容照搬,大佬看了勿噴,以后熟悉了會(huì)總結(jié)成自己的讀書筆記。
Maurice Wilkes, 第一個(gè)存儲(chǔ)程序計(jì)算機(jī)EDSAC的設(shè)計(jì)者, 1949年他在實(shí)驗(yàn)室爬樓梯時(shí)有一個(gè)頓悟。 在《計(jì)算機(jī)先驅(qū)回憶錄》 ( Memoirs of a Computer Pioneer) 里, 他回憶到: “忽然間有一種醍醐灌頂?shù)母杏X(jué), 我整個(gè)后半生的美好時(shí)光都將在尋找程序BUG中度過(guò)了”。 肯定從那之后的大部分正常的碼農(nóng)都會(huì)同情Wilkes過(guò)份悲觀的想法, 雖然也許不是沒(méi)有人困惑于他對(duì)軟件開(kāi)發(fā)的難度的天真看法。 現(xiàn)在的程序已經(jīng)遠(yuǎn)比Wilkes時(shí)代的更大也更復(fù)雜, 也有許多技術(shù)可以讓軟件的復(fù)雜性可得到 控制。 其中有兩種技術(shù)在實(shí)踐中證明是比較有效的。 第一種是代碼在被正式部署前需要進(jìn)行 代碼評(píng)審。 第二種則是測(cè)試, 也就是本章的討論主題。 我們說(shuō)測(cè)試的時(shí)候一般是指自動(dòng)化測(cè)試, 也就是寫一些小的程序用來(lái)檢測(cè)被測(cè)試代碼( 產(chǎn)品 代碼) 的行為和預(yù)期的一樣, 這些通常都是精心設(shè)計(jì)的執(zhí)行某些特定的功能或者是通過(guò)隨機(jī) 性的輸入要驗(yàn)證邊界的處理。 軟件測(cè)試是一個(gè)巨大的領(lǐng)域。 測(cè)試的任務(wù)可能已經(jīng)占據(jù)了一些程序員的部分時(shí)間和另一些程序員的全部時(shí)間。 和軟件測(cè)試技術(shù)相關(guān)的圖書或博客文章有成千上萬(wàn)之多。 對(duì)于每一種主流的編程語(yǔ)言, 都會(huì)有一打的用于測(cè)試的軟件包, 同時(shí)也有大量的測(cè)試相關(guān)的理論, 而且每種都吸引了大量技術(shù)先驅(qū)和追隨者。 這些都足以說(shuō)服那些想要編寫有效測(cè)試的程序員重新學(xué)習(xí)一套全新的技能。 Go語(yǔ)言的測(cè)試技術(shù)是相對(duì)低級(jí)的。 它依賴一個(gè)go test測(cè)試命令和一組按照約定方式編寫的測(cè)試函數(shù), 測(cè)試命令可以運(yùn)行這些測(cè)試函數(shù)。 編寫相對(duì)輕量級(jí)的純測(cè)試代碼是有效的, 而且它很容易延伸到基準(zhǔn)測(cè)試和示例文檔。 在實(shí)踐中, 編寫測(cè)試代碼和編寫程序本身并沒(méi)有多大區(qū)別。 我們編寫的每一個(gè)函數(shù)也是針對(duì)每個(gè)具體的任務(wù)。 我們必須小心處理邊界條件, 思考合適的數(shù)據(jù)結(jié)構(gòu), 推斷合適的輸入應(yīng)該產(chǎn)生什么樣的結(jié)果輸出。 編程測(cè)試代碼和編寫普通的Go代碼過(guò)程是類似的; 它并不需要學(xué)習(xí)新的符號(hào)、 規(guī)則和工具。
1. go test
go test命令是一個(gè)按照一定的約定和組織的測(cè)試代碼的驅(qū)動(dòng)程序。 在包目錄內(nèi), 所有以_test.go為后綴名的源文件并不是go build構(gòu)建包的一部分, 它們是go test測(cè)試的一部分。 在*_test.go文件中, 有三種類型的函數(shù): 測(cè)試函數(shù)、 基準(zhǔn)測(cè)試函數(shù)、 示例函數(shù)。 一個(gè)測(cè)試函數(shù)是以Test為函數(shù)名前綴的函數(shù), 用于測(cè)試程序的一些邏輯行為是否正確; go test命令會(huì)調(diào)用這些測(cè)試函數(shù)并報(bào)告測(cè)試結(jié)果是PASS或FAIL。 基準(zhǔn)測(cè)試函數(shù)是以Benchmark為函數(shù)名前綴的函數(shù), 它們用于衡量一些函數(shù)的性能; go test命令會(huì)多次運(yùn)行基準(zhǔn)函數(shù)以計(jì)算一個(gè)平均的執(zhí)行時(shí)間。 示例函數(shù)是以Example為函數(shù)名前綴的函數(shù), 提供一個(gè)由編譯器保證正確性的示例文 檔。 我們將在11.2節(jié)討論測(cè)試函數(shù)的所有細(xì)節(jié), 病在11.4節(jié)討論基準(zhǔn)測(cè)試函數(shù)的細(xì)節(jié), 然后11.6節(jié)討論示例函數(shù)的細(xì)節(jié)。 go test命令會(huì)遍歷所有的*_test.go文件中符合上述命名規(guī)則的函數(shù), 然后生成一個(gè)臨時(shí)的main包用于調(diào)用相應(yīng)的測(cè)試函數(shù), 然后構(gòu)建并運(yùn)行、 報(bào)告測(cè)試結(jié)果, 最后清理測(cè)試中生成的臨時(shí)文件
2. 測(cè)試函數(shù)
每個(gè)測(cè)試函數(shù)必須導(dǎo)入testing包。 測(cè)試函數(shù)有如下的簽名: 測(cè)試函數(shù)的名字必須以Test開(kāi)頭, 可選的后綴名必須以大寫字母開(kāi)頭: 其中t參數(shù)用于報(bào)告測(cè)試失敗和附加的日志信息。 讓我們定義一個(gè)實(shí)例包gopl.io/ch11/word1,其中只有一個(gè)函數(shù)IsPalindrome用于檢查一個(gè)字符串是否從前向后和從后向前讀都是一樣的。( 下面這個(gè)實(shí)現(xiàn)對(duì)于一個(gè)字符串是否是回文字符串前后重復(fù)測(cè)試了兩次; 我們稍后會(huì)再討論這個(gè)問(wèn)題。 ) gopl.io/ch11/word1 在相同的目錄下, word_test.go測(cè)試文件中包含了TestPalindrome和TestNonPalindrome兩個(gè)測(cè)試函數(shù)。 每一個(gè)都是測(cè)試IsPalindrome是否給出正確的結(jié)果, 并使用t.Error報(bào)告失敗信息: go test 命令如果沒(méi)有參數(shù)指定包那么將默認(rèn)采用當(dāng)前目錄對(duì)應(yīng)的包( 和 go build 命令一樣) 。 我們可以用下面的命令構(gòu)建和運(yùn)行測(cè)試。 結(jié)果還比較滿意, 我們運(yùn)行了這個(gè)程序, 不過(guò)沒(méi)有提前退出是因?yàn)檫€沒(méi)有遇到BUG報(bào)告。 不過(guò)一個(gè)法國(guó)名為“Noelle Eve Elleon”的用戶會(huì)抱怨IsPalindrome函數(shù)不能識(shí)別“été”。 另外一個(gè)來(lái)自美國(guó)中部用戶的抱怨則是不能識(shí)別“A man, a plan, a canal: Panama.”。 執(zhí)行特殊和小的BUG報(bào)告為我們提供了新的更自然的測(cè)試用例。 為了避免兩次輸入較長(zhǎng)的字符串, 我們使用了提供了有類似Printf格式化功能的 Errorf函數(shù)來(lái)匯報(bào)錯(cuò)誤結(jié)果。 當(dāng)添加了這兩個(gè)測(cè)試用例之后, go test 返回了測(cè)試失敗的信息 先編寫測(cè)試用例并觀察到測(cè)試用例觸發(fā)了和用戶報(bào)告的錯(cuò)誤相同的描述是一個(gè)好的測(cè)試習(xí)慣。 只有這樣, 我們才能定位我們要真正解決的問(wèn)題。 先寫測(cè)試用例的另外的好處是, 運(yùn)行測(cè)試通常會(huì)比手工描述報(bào)告的處理更快, 這讓我們可以進(jìn)行快速地迭代。 如果測(cè)試集有很多運(yùn)行緩慢的測(cè)試, 我們可以通過(guò)只選擇運(yùn)行某些特定的測(cè)試來(lái)加快測(cè)試速度。 參數(shù) -v 可用于打印每個(gè)測(cè)試函數(shù)的名字和運(yùn)行時(shí)間: 參數(shù) -run 對(duì)應(yīng)一個(gè)正則表達(dá)式, 只有測(cè)試函數(shù)名被它正確匹配的測(cè)試函數(shù)才會(huì)被 go test 測(cè)試命令運(yùn)行: 當(dāng)然, 一旦我們已經(jīng)修復(fù)了失敗的測(cè)試用例, 在我們提交代碼更新之前, 我們應(yīng)該以不帶參數(shù)的 go test 命令運(yùn)行全部的測(cè)試用例, 以確保修復(fù)失敗測(cè)試的同時(shí)沒(méi)有引入新的問(wèn)題。 我們現(xiàn)在的任務(wù)就是修復(fù)這些錯(cuò)誤。 簡(jiǎn)要分析后發(fā)現(xiàn)第一個(gè)BUG的原因是我們采用了 byte而不是rune序列, 所以像“été”中的é等非ASCII字符不能正確處理。 第二個(gè)BUG是因?yàn)闆](méi)有忽略空格和字母的大小寫導(dǎo)致的。 針對(duì)上述兩個(gè)BUG, 我們仔細(xì)重寫了函數(shù): gopl.io/ch11/word2 同時(shí)我們也將之前的所有測(cè)試數(shù)據(jù)合并到了一個(gè)測(cè)試中的表格中。 現(xiàn)在我們的新測(cè)試阿都通過(guò)了: 這種表格驅(qū)動(dòng)的測(cè)試在Go語(yǔ)言中很常見(jiàn)的。 我們很容易向表格添加新的測(cè)試數(shù)據(jù), 并且后面的測(cè)試邏輯也沒(méi)有冗余, 這樣我們可以有更多的精力地完善錯(cuò)誤信息。 失敗測(cè)試的輸出并不包括調(diào)用t.Errorf時(shí)刻的堆棧調(diào)用信息。 和其他編程語(yǔ)言或測(cè)試框架的assert斷言不同, t.Errorf調(diào)用也沒(méi)有引起panic異常或停止測(cè)試的執(zhí)行。 即使表格中前面的數(shù)據(jù)導(dǎo)致了測(cè)試的失敗, 表格后面的測(cè)試數(shù)據(jù)依然會(huì)運(yùn)行測(cè)試, 因此在一個(gè)測(cè)試中我們可能了解多個(gè)失敗的信息。 如果我們真的需要停止測(cè)試, 或許是因?yàn)槌跏蓟』蚩赡苁窃缦鹊腻e(cuò)誤導(dǎo)致了后續(xù)錯(cuò)誤等原因, 我們可以使用t.Fatal或t.Fatalf停止當(dāng)前測(cè)試函數(shù)。 它們必須在和測(cè)試函數(shù)同一個(gè)goroutine內(nèi)調(diào)用。 測(cè)試失敗的信息一般的形式是“f(x) = y, want z”, 其中f(x)解釋了失敗的操作和對(duì)應(yīng)的輸出, y是實(shí)際的運(yùn)行結(jié)果, z是期望的正確的結(jié)果。 就像前面檢查回文字符串的例子, 實(shí)際的函數(shù)用于f(x)部分。 如果顯示x是表格驅(qū)動(dòng)型測(cè)試中比較重要的部分, 因?yàn)橥粋€(gè)斷言可能對(duì)應(yīng)不同的表格項(xiàng)執(zhí)行多次。 要避免無(wú)用和冗余的信息。 在測(cè)試類似IsPalindrome返回布爾類型的函數(shù)時(shí), 可以忽略并沒(méi)有額外信息的z部分。 如果x、 y或z是y的長(zhǎng)度, 輸出一個(gè)相關(guān)部分的簡(jiǎn)明總結(jié)即可。 測(cè)試的作者應(yīng)該要努力幫助程序員診斷測(cè)試失敗的原因。
1. 隨機(jī)測(cè)試
表格驅(qū)動(dòng)的測(cè)試便于構(gòu)造基于精心挑選的測(cè)試數(shù)據(jù)的測(cè)試用例。 另一種測(cè)試思路是隨機(jī)測(cè)試, 也就是通過(guò)構(gòu)造更廣泛的隨機(jī)輸入來(lái)測(cè)試探索函數(shù)的行為。 那么對(duì)于一個(gè)隨機(jī)的輸入, 我們?nèi)绾文苤老M妮敵鼋Y(jié)果呢? 這里有兩種處理策略。 第一個(gè)是編寫另一個(gè)對(duì)照函數(shù), 使用簡(jiǎn)單和清晰的算法, 雖然效率較低但是行為和要測(cè)試的函數(shù)是一致的, 然后針對(duì)相同的隨機(jī)輸入檢查兩者的輸出結(jié)果。 第二種是生成的隨機(jī)輸入的數(shù)據(jù)遵循特定的模式, 這樣我們就可以知道期望的輸出的模式。 下面的例子使用的是第二種方法: randomPalindrome函數(shù)用于隨機(jī)生成回文字符串。 雖然隨機(jī)測(cè)試會(huì)有不確定因素, 但是它也是至關(guān)重要的, 我們可以從失敗測(cè)試的日志獲取足夠的信息。 在我們的例子中, 輸入IsPalindrome的p參數(shù)將告訴我們真實(shí)的數(shù)據(jù), 但是對(duì)于函數(shù)將接受更復(fù)雜的輸入, 不需要保存所有的輸入, 只要日志中簡(jiǎn)單地記錄隨機(jī)數(shù)種子即可 ( 像上面的方式) 。 有了這些隨機(jī)數(shù)初始化種子, 我們可以很容易修改測(cè)試代碼以重現(xiàn)失敗的隨機(jī)測(cè)試。 通過(guò)使用當(dāng)前時(shí)間作為隨機(jī)種子, 在整個(gè)過(guò)程中的每次運(yùn)行測(cè)試命令時(shí)都將探索新的隨機(jī)數(shù)據(jù)。 如果你使用的是定期運(yùn)行的自動(dòng)化測(cè)試集成系統(tǒng), 隨機(jī)測(cè)試將特別有價(jià)值
2. 測(cè)試一個(gè)命令
對(duì)于測(cè)試包 go test 是一個(gè)的有用的工具, 但是稍加努力我們也可以用它來(lái)測(cè)試可執(zhí)行程序。 如果一個(gè)包的名字是 main, 那么在構(gòu)建時(shí)會(huì)生成一個(gè)可執(zhí)行程序, 不過(guò)main包可以作為一個(gè)包被測(cè)試器代碼導(dǎo)入。 讓我們?yōu)?.3.2節(jié)的echo程序編寫一個(gè)測(cè)試。 我們先將程序拆分為兩個(gè)函數(shù): echo函數(shù)完成真正的工作, main函數(shù)用于處理命令行輸入?yún)?shù)和echo可能返回的錯(cuò)誤。 gopl.io/ch11/echo 在測(cè)試中我們可以用各種參數(shù)和標(biāo)標(biāo)志調(diào)用echo函數(shù), 然后檢測(cè)它的輸出是否正確, 我們通過(guò)增加參數(shù)來(lái)減少echo函數(shù)對(duì)全局變量的依賴。 我們還增加了一個(gè)全局名為out的變量來(lái)替代直接使用os.Stdout, 這樣測(cè)試代碼可以根據(jù)需要將out修改為不同的對(duì)象以便于檢查。 下面就是echo_test.go文件中的測(cè)試代碼: 要注意的是測(cè)試代碼和產(chǎn)品代碼在同一個(gè)包。 雖然是main包, 也有對(duì)應(yīng)的main入口函數(shù), 但是在測(cè)試的時(shí)候main包只是TestEcho測(cè)試函數(shù)導(dǎo)入的一個(gè)普通包, 里面main函數(shù)并沒(méi)有被導(dǎo)出, 而是被忽略的。 通過(guò)將測(cè)試放到表格中, 我們很容易添加新的測(cè)試用例。 讓我通過(guò)增加下面的測(cè)試用例來(lái)看看失敗的情況是怎么樣的: go test 輸出如下: 錯(cuò)誤信息描述了嘗試的操作( 使用Go類似語(yǔ)法) , 實(shí)際的結(jié)果和期望的結(jié)果。 通過(guò)這樣的錯(cuò)誤信息, 你可以在檢視代碼之前就很容易定位錯(cuò)誤的原因。 要注意的是在測(cè)試代碼中并沒(méi)有調(diào)用log.Fatal或os.Exit, 因?yàn)檎{(diào)用這類函數(shù)會(huì)導(dǎo)致程序提前退出; 調(diào)用這些函數(shù)的特權(quán)應(yīng)該放在main函數(shù)中。 如果真的有意外的事情導(dǎo)致函數(shù)發(fā)生panic異常, 測(cè)試驅(qū)動(dòng)應(yīng)該嘗試用recover捕獲異常, 然后將當(dāng)前測(cè)試當(dāng)作失敗處理。 如果是可預(yù)期的錯(cuò)誤, 例如非法的用戶輸入、 找不到文件或配置文件不當(dāng)?shù)葢?yīng)該通過(guò)返回一個(gè)非空的error的方式處理。 幸運(yùn)的是( 上面的意外只是一個(gè)插曲) , 我們的echo示例是比較簡(jiǎn)單的也沒(méi)有需要返回非空error的情況。
3. 白盒測(cè)試
一種測(cè)試分類的方法是基于測(cè)試者是否需要了解被測(cè)試對(duì)象的內(nèi)部工作原理。 黑盒測(cè)試只需要測(cè)試包公開(kāi)的文檔和API行為, 內(nèi)部實(shí)現(xiàn)對(duì)測(cè)試代碼是透明的。 相反, 白盒測(cè)試有訪問(wèn)包內(nèi)部函數(shù)和數(shù)據(jù)結(jié)構(gòu)的權(quán)限, 因此可以做到一下普通客戶端無(wú)法實(shí)現(xiàn)的測(cè)試。 例如, 一個(gè)白盒測(cè)試可以在每個(gè)操作之后檢測(cè)不變量的數(shù)據(jù)類型。 ( 白盒測(cè)試只是一個(gè)傳統(tǒng)的名稱, 其實(shí)稱為clear box測(cè)試會(huì)更準(zhǔn)確。 ) 黑盒和白盒這兩種測(cè)試方法是互補(bǔ)的。 黑盒測(cè)試一般更健壯, 隨著軟件實(shí)現(xiàn)的完善測(cè)試代碼很少需要更新。 它們可以幫助測(cè)試者了解真是客戶的需求, 也可以幫助發(fā)現(xiàn)API設(shè)計(jì)的一些不足之處。 相反, 白盒測(cè)試則可以對(duì)內(nèi)部一些棘手的實(shí)現(xiàn)提供更多的測(cè)試覆蓋。 我們已經(jīng)看到兩種測(cè)試的例子。 TestIsPalindrome測(cè)試僅僅使用導(dǎo)出的IsPalindrome函數(shù), 因此這是一個(gè)黑盒測(cè)試。 TestEcho測(cè)試則調(diào)用了內(nèi)部的echo函數(shù), 并且更新了內(nèi)部的out包級(jí)變量, 這兩個(gè)都是未導(dǎo)出的, 因此這是白盒測(cè)試。 當(dāng)我們準(zhǔn)備TestEcho測(cè)試的時(shí)候, 我們修改了echo函數(shù)使用包級(jí)的out變量作為輸出對(duì)象, 因此測(cè)試代碼可以用另一個(gè)實(shí)現(xiàn)代替標(biāo)準(zhǔn)輸出, 這樣可以方便對(duì)比echo輸出的數(shù)據(jù)。 使用類似的技術(shù), 我們可以將產(chǎn)品代碼的其他部分也替換為一個(gè)容易測(cè)試的偽對(duì)象。 使用偽對(duì)象的好處是我們可以方便配置, 容易預(yù)測(cè), 更可靠, 也更容易觀察。 同時(shí)也可以避免一些不良的副作用, 例如更新生產(chǎn)數(shù)據(jù)庫(kù)或信用卡消費(fèi)行為。 下面的代碼演示了為用戶提供網(wǎng)絡(luò)存儲(chǔ)的web服務(wù)中的配額檢測(cè)邏輯。 當(dāng)用戶使用了超過(guò)90%的存儲(chǔ)配額之后將發(fā)送提醒郵件。 gopl.io/ch11/storage1 我們想測(cè)試這個(gè)代碼, 但是我們并不希望發(fā)送真實(shí)的郵件。 因此我們將郵件處理邏輯放到一個(gè)私有的notifyUser函數(shù)中。 gopl.io/ch11/storage2 現(xiàn)在我們可以在測(cè)試中用偽郵件發(fā)送函數(shù)替代真實(shí)的郵件發(fā)送函數(shù)。 它只是簡(jiǎn)單記錄要通知的用戶和郵件的內(nèi)容。 這里有一個(gè)問(wèn)題: 當(dāng)測(cè)試函數(shù)返回后, CheckQuota將不能正常工作, 因?yàn)閚otifyUsers依然使用的是測(cè)試函數(shù)的偽發(fā)送郵件函數(shù)( 當(dāng)更新全局對(duì)象的時(shí)候總會(huì)有這種風(fēng)險(xiǎn)) 。 我們必須修改測(cè)試代碼恢復(fù)notifyUsers原先的狀態(tài)以便后續(xù)其他的測(cè)試沒(méi)有影響, 要確保所有的執(zhí)行路徑后都能恢復(fù), 包括測(cè)試失敗或panic異常的情形。 在這種情況下, 我們建議使用defer語(yǔ)句來(lái)延后執(zhí)行處理恢復(fù)的代碼。 這種處理模式可以用來(lái)暫時(shí)保存和恢復(fù)所有的全局變量, 包括命令行標(biāo)志參數(shù)、 調(diào)試選項(xiàng)和優(yōu)化參數(shù); 安裝和移除導(dǎo)致生產(chǎn)代碼產(chǎn)生一些調(diào)試信息的鉤子函數(shù); 還有有些誘導(dǎo)生產(chǎn)代碼進(jìn)入某些重要狀態(tài)的改變, 比如超時(shí)、 錯(cuò)誤, 甚至是一些刻意制造的并發(fā)行為等因素。 以這種方式使用全局變量是安全的, 因?yàn)間o test命令并不會(huì)同時(shí)并發(fā)地執(zhí)行多個(gè)測(cè)試。
待續(xù)…
總結(jié)
以上是生活随笔 為你收集整理的《Go语言圣经》学习笔记 第十一章 测试 的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
如果覺(jué)得生活随笔 網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔 推薦給好友。