對于程序開發新手來說,一個最常見的困惑是測試的主題。他們隐約覺得“單元測試”是很好的,而且他們也應該做單元測試。但他們卻不懂這個詞的真正含義。如果這聽起來像是在說你,不要怕!在這篇文章中,我将介紹什麼是單元測試,為什麼它有用,以及如何對Python的代碼進行單元測試。
什麼是測試?
在讨論為什麼測試很有用、怎樣進行測試之前,讓我們先花幾分鐘來定義一下“單元測試”究竟是什麼。在一般的編程術語中,“測試”指的是通過編寫可以調用的代碼(獨立于你實際應用程序的代碼)來幫助你确定程序中是否有錯誤。這并不能證明你的代碼是正确的(在非常有限的情況下這是唯一的可能)。它隻是報告了測試者認為的那種情況是否被正确處理了。
注:當我使用“測試”一次時,我指的是“自動化測試”,即這些測試是在機器上運行的。“手動測試”則是一個人運行程序,并與它進行交互,從而發現漏洞,這是個獨立的概念。
測試可以檢查出什麼樣的情況呢?語法錯誤是語言的意外誤用,如
你有兩個函數,is_prime和print_next_prime。如果你想測試print_next_prime,我們就需要确定is_prime是正确的,因為print_next_prime中調用了這個函數。在這種情況下,print_next_prime函數是一個單元,is_prime函數是另一個單元。由于單元測試每次隻測試一個單元,因此我們需要仔細考慮怎樣才能準确的測試print_next_prime?(更多的是關于之後怎樣實現這些測試)。
因此,測試代碼應該長什麼樣呢?如果上一個例子存在一個叫primes.py的文件中,我們可以把測試代碼寫在一個叫test_primes.py的文件中。下面是test_primes.py 中的最基本内容,比如下面這個測試樣例:
這個文件通過一個test case:?test_is_five_prime. 創建了一個單元測試。通過Python内嵌的一個測試框架unittest。當unittest.main()被調用時,任何一個以test開頭命名的成員函數将被運行,他們是unittest.TestCase的一個派生類,并且是斷言檢查的。如果我們通過輸入python test_primes.py來運行測試,我們能夠看到unittest框架在控制台上 的輸出:
單獨的“E”表示的是我們單元測試的結果(如果它成功了,會打印出一個“.”)。我們可以看到我們的測試失敗了,以及導緻失敗的那行代碼,還有任何引發的異常信息。
為什麼要測試?
在我們繼續那個例子之前,要問個很重要的問題:“為什麼測試對我來說有價值”?這是個公平的問題,也是那些對于代碼測試不熟悉的人常問的問題。畢竟,測試需要一定的時間,而我們完全可以用這些時間去編代碼,為什麼要測試而不是去做那些最有生産效率的事?
有很多答案可以有效的回答這個問題,我列出了以下幾點:
●測試可以保證你的代碼在一系列給定條件下正常工作
測試确保了一系列條件下的正确性。語法錯誤基本上一定通過測試被查出來,一個代碼單元的基本的邏輯也可以通過測試被檢測出來,以确保一定條件下的正确性。再次,它不是要證明代碼是在任何條件下都正确的。我們隻是簡單的瞄準了一套比較完整的可能的條件(例如,你可以寫一個測試來監測當你調用my_addition_function(3, 'refrigerator), 的時候,但你不必為每個參數檢測所有可能的字符串)
●測試允許人們确保對代碼的改動不會破壞現有的功能
重構代碼時,這一點特别有用。如果沒有測試到位,你就沒法保證你的代碼的改變沒有破壞之前工作正常的東西。如果你希望更改或重寫你的代碼,并希望不會破壞任何東西,适當的單元測試是很必要的。
●測試迫使人們在不尋常條件的情況下思考代碼,這可能會揭示出邏輯錯誤
編寫測試強迫你去思考在非正常條件下你的代碼可能遇到的問題。在上面的例子中,my_addition_function函數可以将兩個數字相加。測試基本正确性的簡單測試将調用my_addition_function(2,2),并斷言說結果是4。然而,進一步的測試可能會通過調用my_addition_function(2.0,2.0)來測試該功能是否能正确進行浮點數的運算。防禦性的編碼原則表明你的代碼應該能夠在非法輸入的情況下正常失效,因此測試時,當字符串類型被作為參數傳遞到函數中時應當抛出一個異常。
●良好的測試要求模塊化,解耦代碼,這是一個良好的系統設計的标志
單元測試的整體做法是通過代碼的松散耦合使其變得更容易。如果你的應用程序代碼直接調用數據庫,例如,測試你應用程序的邏輯依賴于一個有效的數據庫連接,并且測試數據要存在于數據庫中。另一方面,隔離了外部資源的代碼在測試過程中更容易被模拟對象所替代。出于必要,(人們)設計的有測試能力的應用程序最終采用了模塊化和松散耦合。
單元測試的剖析
通過繼續之前的例子,我們将看到如何編寫并組織單元測試。回想一下,primes.py包含以下代碼:
同時,文件test_primes.py包含如下代碼:
做出斷言
unittest是Python标準庫中的一部分,并且也是我們開始“單元測試之旅”的一個好的起點。一個單元測試中包括一個或多個斷言(一些聲明被測試代碼的一些屬性為真的語句)。會想你上學的時候“斷言”這個詞的字面意思就是“陳述事實”。在單元測試中,斷言也是同樣的作用。
self.assertTrue 更像是自我解釋。它能聲明傳遞過去的參數的計算結果為真。unittest.TestCase類包含了許多斷言方法,所以一定要檢查列表并選擇合适的方法進行測試。如果在每個測試中都用到assertTrue的話,則應該考慮一個反模式,因為它增加了測試中讀者的認知負擔。正确使用斷言的方法應當是使測試能夠明确說明究竟是什麼在被斷言(例如,很明顯?,隻需掃一眼assertIsInstance 的方法名,就知道它要說明的是其參數)。
每個測試應該測試一個單獨、有具體特性的代碼,并且應該被賦予相關的命名。就單元測試發現機制的研究表明(主要在Python2.7 和3.2 版本中),測試方法應該以test_為前綴命名。(這是可配置的,但是其目的是鑒别測試方法和非測試的實用方法)。如果我們把test_is_five_prime 的命名改為is_five_prime的話,運行python中的test_primes.py時會輸出如下信息:
不要被上面信息中的“OK”所糊弄了,隻有當什麼測試都沒真正運行的時候才會顯示出“OK”!我認為一個測試也沒跑其實應該顯示個報錯的,但是個人感覺放在一邊,這是一個你應該注意是行為,尤其是當通過程序運行來檢查測試結果的時候(例如,一個持續的集成工具,像TracisCI)。
異常
讓我們回到test_primes.py的實際内容中去,回憶一下運行python test_primes.py指令後的輸出結果:
這些輸出告訴我們,我們一個測試的結果失敗并不是因為一個斷言失敗了,而是因為出現了一個未捕獲的異常。事實上,由于抛出了一個異常,unittest框架并沒有能夠運行我們的測試就返回了。
這裡的問題很明确:我們使用的求模運算的計算範圍中包括了0,因此執行了一個除以0的操作。為了解決這個問題,我們可以很簡單的将起始值由0變為2,并指出對0求模是錯誤的,而對1求模則一直是真(并且一個素數隻能被自身和1整除,因此我們無需檢查1)。
解決問題
一次失敗的測試使我們修改了代碼。一旦我們改好了這個錯誤(将s_prime中的一行改為for element in range(2, number):),我們就得到了如下輸出:
現在錯誤已經改了,這是不是意味着我們應該删掉test_is_five_prime這個測試方法(因為很明顯,它将不會一直能通過測試)?不應該删。由于通過測試是最終目标的話單元測試應該盡量少的被删除。我們已經測試過is_prime的語法是有效的,并且,至少在一種情況下,它返回正确的結果。我們的目标是要建立一套能全部通過的(單元測試的邏輯分組)測試,雖然有些一開始可能會失敗。
請注意,這時我們給assert調用添加了可選的msg參數。如果該測試失敗了,我們的信息将被打印到控制台,并給運行測試的人提供額外的信息。
未完待續。。。
End.
更多熱門文章盡在51testing軟件測試網!
,更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!