Python单元测试最佳实践
Are you ready?
↓↓↓
今天的課程為《 Python單元測試》,內容共分為三個部分:單元測試的概念、工具與方法、Coverage 統計單元測試覆蓋率的工具和Mock 簡化單元測試的工具。
單元測試的概念、工具與方法
單元測試的概念
測試具有許多種不同的類型,比如說單元測試、模塊測試、聯調測試、系統測試、交付測試等。在這些測試之中,單元測試是最先要完成的。單元測試通常是由開發者去完成,用來驗證代碼中的函數是否符合預期。因此,它聚焦于函數的邏輯以及核心的算法是否正確。通常而言,一個單元測試用例是用于判斷在某個特定條件或場景下,某個特定函數的行為。
單元測試的意義
單元測試的意義包括兩個方面。
(1)質量
①單元測試主要針對函數,顆粒度小、測試針對性強,bug更容易暴露;
②由于單元測試覆蓋面較窄,無需考慮其它函數或者所依賴的模塊,所以它的場景易構造,核心功能驗證更充分;
③進行單元測試保證整體代碼結構良好,使代碼就具有較高的可測性和可維護性。
(2)效率
單元測試能夠提高開發效率,主要表現在:
①單元測試進行的時間較早,測試場景構建快,可有效減少調試時間。
②由于單元測試只針對修改的代碼展開測試,無需考慮額外內容,所以在較短時間內即可把預期的邏輯測試充分。
③單元測試能夠在項目開發初期發現的bug,bug發現的時間越早,所帶來的收益越大。由于盡早發現bug能夠節省整個項目開發的時間,所以單元測試可加快開發效率,縮短開發周期。
單元測試框架
(1)測試框架選擇
在進行單元測試的時候,我們需要測試框架作為工具。一個良好的測試框架能夠讓測試工作事半功倍,此處我們介紹三個框架。
①Unittest是Python標準庫中原生自帶的,它的好處在于無兼容性問題,是其他框架的基礎;不足之處在于編碼略顯繁瑣,入門門檻稍高。
②Pytest的好處在于編碼簡潔、生態好、插件豐富、使用人數多;不足之處在于他屬于第三方庫,使用前另需提前安裝。
③Nose2能夠兼容Unittest,也屬于第三方庫,需要安裝,但是與Pytest相比,它迭代緩慢,使用人數少。
在這里我們選擇Unittest作為單元測試的框架,原因有二:首先,作為Python標準庫中原生自帶的框架,Unittest無兼容性問題;其次,第三方庫難以保證長期快速迭代,易過時。
(2)Unitest的基礎概念
在做單元測試之前,需要先了解一下Unittest的幾個基礎概念。
①Test(測試用例),針對一個特定場景,特定目的具體測試過程。
比如說一個函數通過一組輸入測試它,就是一個測試用例;如果一個函數通過三組輸入來測試,即為三個測試用例。
②TestCase(測試類),可以包含同一個測試對象的多個測試用例。
如果一個函數通過三組輸入來測試,也就是三個測試用例,這三個測試用例可以合成為一個測試類。
③TestSuite(測試集),可以包含多個測試類的多個測試用例。
④Assertion(斷言),必須使用斷言判斷測試結果。
⑤TestFixture,為測試做統一的準備和清除工作,通常是初始化,連接數據庫,準備數據,斷開數據庫,清除現場等。
擴展來說,TestFixture有四種最常使用的作用范圍,分別為:
→ setUp:在測試類的每個測試用例執行前執行。
→ teardown:在測試類的每個測試用例執行后執行。
→ setUpClass:在測試類的第一個測試用例執行前執行。
→ tearDownClass:在測試類的最后一個測試用例執行后執行。
TestFixture可以讓單元測試代碼更簡單,但并非必須使用,也不要求配對出現。
單元測試的規范
想要做好單元測試,必須遵循一定的規范。下面介紹一下單元測試涉及的規范。
(1)所有的單元測試必須使用斷言(assert)判斷結果,禁止出現無斷言的測試用例;
使用斷言,不但有利于他人理解,而且一旦出現不符合預期的情況,可以立即找出問題。
可以使用assertEqual, assertNotEqual 來判斷相等或不相等,assertTrue,assertFalse 來判斷Boolean, assertRaises 判斷拋出的異常是否符合預期。
(2)測試用例需要具有自表述能力,達到見名知意。
比如命名test_login_with_invalid_password(),通過它的名字便可知它是用一個非法的密碼去測試登錄功能,具有自表述能力;但是如果命名為 test_login_case_(),名字減少了很多信息,難以得知它具體在做什么,不具有自表述能力。
(3)測試用例之間相互獨立,不應相互依賴、相互調用。
(4)一個測試用例只測一個函數。一個測試用例里面可以包含這一個函數的多個場景,但不能包含有多個參數的函數。原因在于,復雜測試用例出現錯誤時,無法定位問題的出處。
單元測試對編碼的要求
單元測試中代碼需保持一致性,盡量不要出現結果不一致的情況。假設有的代碼會帶來不一致性,導致單元測試無法穩定運行。針對這種情況,有兩種解決方案:第一,將帶來不一致性的代碼抽取出來,把它作為一種變量傳入我們需要調用或使用一致性變量的時候;第二,借助第三部分即將講到的一個工具——mock——來解決這種問題。
Coverage 統計 單元測試覆蓋率的工具
單元測試做完之后如何評價我們單元測試的效果。此時需要用到覆蓋率工具,即Coverage。Coverage是一個第三方的工具,需要提前下載安裝。
(1)統計覆蓋率方法
把python替換為coverage run-branch,然后會生成coverage文件,文件里會記錄所有我們需要的覆蓋率信息。
(2)打印覆蓋率信息
執行coverage report-m 命令,讀取當前目錄下.coverage文件,打印覆蓋率信息。輸出Stmts(總行數), Miss(未覆蓋行數), Branch(總分支數), BrPart (未覆蓋分支數), Cover(覆蓋率) , Missing(未覆蓋具體信息)等信息。
(3)覆蓋率中排除某些文件
執行coverage report-m—omit=file 1[,file 2,……] 命令, 在統計并打印覆蓋率時,排除某些文件。若有多個文件用逗號分隔。
(4)生成HTML格式的覆蓋率信息
針對代碼量較大,查找覆蓋率信息難度較大、耗時較長的情況,執行coverage html [–omit=file1[,file2,……]]命令,將覆蓋率信息以html格式顯示。
Mock 簡化單元測試的工具
使用mock工具的原因與其功能
Mock基于實際進行單元測試的場景而產生,以下三類場景非常具有代表性:
①構造模塊。需要測試模塊A,但它要調用的模塊B還未開發,可是測試卻不容推遲、需按時進行,面對這種情況,我們可以使用Mock生成一個還未寫完的代碼,即可進行相應的測試。
②改變函數邏輯。代碼中含有結果不可預知的代碼,例如time.time()(時間), random.random()(隨機數)。Mock可以改變含有結果不可預知代碼的函數的邏輯,強行讓其返回我們想要的返回值,使其結果可預知。
③減少依賴。在所有模塊代碼都已完成,但無法保證代碼穩定性的情況下。針對其他模塊的質量不可靠的情況,可通過Mock工具構造一個相對穩定的模塊,從而規避其他模塊的問題。
Mock使用場景
通過以下10個場景來講述Mock的常見用法。
場景01:通過 return_value,Mock可以強行修改,永遠返回我們想要的返回值,支持的類型包括string,number,Boolean,list,dict等。
場景02:將前一個例子的實例名改為類名,可實現替換類方法的返回值。
場景03:通過 side_effect,根據調用次數返回想要的結果,當超出調用次數時拋StopIteration 異常。
場景04:通過 side_effect可以完全修改函數的邏輯,使用另一個函數來替換它,根據參數返回想要的結果。
場景05:通過 side_effect拋出想要的異常或錯誤。
場景06:針對需要mock在特定要求下生效的情況,通過with.patch.object設定一個作用域以達到限制mock作用域的目的。
場景07:獲取調用信息,如函數是否被調用、函數被調用的次數、函數被調用的形式、函數調用的參數等。
場景08:通過create_autospec在返回值改變的同時,確保api不會因mock而改變。
場景09:針對需要調用的函數、調用的接口完全沒有開發的情況,可以通過Mock從零構造依賴模塊從而完成測試。
場景10 :替換函數調用鏈。比如說用popen去執行一個命令,然后用read函數把它讀取出來,再用split去做切分,這就是一個函數調用鏈(os.popen(cmd).read().split())。
Mock 對編碼的要求
在模塊引入方式上,推薦以import XXX的形式引入,以XXX.func()形式調用,不要from.xxx import *,因為需要一個鏈條指向它,否則無法達到我們的預期。
關于Python單元測試
我們今天就講解到這啦
點擊進入獲得更多技術信息~~
總結
以上是生活随笔為你收集整理的Python单元测试最佳实践的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 百度安全发布PaddleSleeve 面
- 下一篇: 单元测试实践