Python單元測試和Mock測試
本博客采用創作共用版權協議, 要求署名、非商業用途和保持一致. 轉載本博客文章必須也遵循 署名-非商業用途-保持一致 的創作共用協議.
單元測試
- 測試可以保證你的代碼在一系列給定條件下正常工作
- 測試允許人們確保對代碼的改動不會破壞現有的功能
- 測試迫使人們在不尋常條件的情況下思考代碼,這可能會揭示出邏輯錯誤
- 良好的測試要求模塊化,解耦代碼,這是一個良好的系統設計的標志
范例
#!/usr/bin/env python # -*- coding: utf-8 -*- importos, sys importtime, datetime importunittest fromunittestimportTestCase classTestSequenece(TestCase): defsetUp(self): self.lst = range(10) print"setUp starting ..." deftest_eq(self): print"test_eq starting..." self.assertEqual(self.lst, range(10)) deftest_in(self): print"test_in starting..." self.assertIn(1, self.lst) self.assertNotIn(10, self.lst) deftest_instance(self): print"test_instance starting..." self.assertIsInstance(self.lst, list) deftearDown(self): print"tearDown starting..." if__name__ =='__main__': unittest.main()
然后我們看一下執行結果再分析:
setUp starting ... test_eq starting... tearDown starting... .setUp starting ... test_in starting... tearDown starting... .setUp starting ... test_instance starting... tearDown starting... . ---------------------------------------------------------------------- Ran 3testsin0.000s OK
共運行三個測試, 每次測試成功通過都會輸出一個.號
- TestCase直譯就是測試用例, 一個測試用例可以包含多個測試
- test_xxxx就是測試項, 根據實際的功能代碼邏輯來編寫對應的測試項, 運行時會自動查找所有以test開發的成員函數
- assertXXXX斷言語句, 用來判斷測試結果是否符合測試預期結果.
- setUp是執行每個測試項前的準備工作, 比如:可以做一些初始化工作
- tearDown是執行在每個測試項后的收尾工作,銷毀測試過程中產生的垃圾, 恢復現場等
Mock測試
Mock測試是什么鬼? 我們常常遇到這樣一種場景, 我們測試一些函數, 而這些函數內部調用另外帶有副作用的操作, 這可能導致我們在測試過程中對數據造成未知的副作用, 而這并不是我們希望在測試中看到的.
Mock測試可以替換到指定的Python對象或者方法, 并自定義指定對象或者方法的返回值, 從來模擬對象或者方法, 消除副作用.
Mock在Python3.3時加入到標準庫中, 2.X版本可以通過pip安裝
$ pip install mock
首先任意寫一個函數
# -*- coding: utf-8 -*- #!/usr/bin/env python importos, sys, time deffoo(): lst = [1] lst = give_me_five(lst) returnlst defgive_me_five(lst): returnlst *5
我們希望通過單元測試來測試這個函數的邏輯正確性(請注意, 這只是一個演習!).
# -*- coding: utf-8 -*- #!/usr/bin/env python importos, sys, time # sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) importunittest fromunittestimportTestCase importmock importmodule classFoo(object): pass classTestMock(TestCase): # 1 deftest_method(self): obj = Foo() obj.method = mock.MagicMock(return_value=3) printobj.method self.assertEqual(obj.method(4),3) # 2 @mock.patch('module.foo') deftest_decorator(self, foo): # res = module.foo() foo.return_value = [1,2,3] self.assertEqual(foo(), [1,2,3]) # 3 deftest_with(self): withmock.patch('module.give_me_five')asgive_me_five: give_me_five.return_value = "I'm Mock" self.assertEqual(module.foo(), "I'm Mock") # 4 deftest_module(self): module.give_me_five = mock.Mock(return_value=[1] *5) module.give_me_five([1])# 此時已經變成了一個Mock對象, 并嘗試調用 module.give_me_five.assert_called_with([1])# 對mock的參數進行斷言 self.assertEqual(module.foo(), [1] *5) if__name__ =='__main__': unittest.main()
- 我們首先集成TestCase創建了一個單元測試
- # 1位置, 我們通過mock提供的函數給obj的method方法設置返回值(可以看到類中并不包含method方法). 最后通過斷言來判斷返回值等于我們通過MagicMock設置的返回值
- # 2位置, 我們通過mock提供的裝飾器,patch()可以作為函數做裝飾, 類裝飾器, 上下文管理器將module中的foo函數給mock掉,并且并mock的函數生成的Mock對象作為類成員函數參數傳入, 指定了foo函數的返回值, 并通過了斷言測試
- # 3位置, 將patch()作為一個上下文管理, 關于上下文管理器可以看我另一篇文章Python奇技淫巧, 用法和作為裝飾器基本類似
- # 4位置, 我們調用module.foo函數, 而我們并不關系foo()調用了那些函數, 我只關心在成功調用module.give_me_five后, foo函數的邏輯正確性. 所以此次我們通過Mock函數給module.give_me_five指定我們希望的返回值. 這樣就能獨立的測試module.foo的邏輯
mock的主要思想: 通過mock對象對某些函數進行替換, 對在測試上下文中, 這些被mock的函數被重定向到指定的mock對象
mock還有一些更高級的應用
- MagicMock是Mock的子類, 并且包含一些如__str__一樣的黑魔法函數, 使用MagicMock甚至可以mock掉黑魔法函數
- 通過patch.object可以mock掉類中指定的成員函數
- 通過patch.dict可以將對象mock為字典
- 通過patch中的start和stop方法可以控制mock的生效范圍, 更加靈活的運行mock測試
參考鏈接
本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!