Python單元測試和Mock測試

jopen 9年前發布 | 22K 次閱讀 Python Python開發


本博客采用創作共用版權協議, 要求署名、非商業用途和保持一致. 轉載本博客文章必須也遵循 署名-非商業用途-保持一致 的創作共用協議.

單元測試

  • 測試可以保證你的代碼在一系列給定條件下正常工作
  • 測試允許人們確保對代碼的改動不會破壞現有的功能
  • 測試迫使人們在不尋常條件的情況下思考代碼,這可能會揭示出邏輯錯誤
  • 良好的測試要求模塊化,解耦代碼,這是一個良好的系統設計的標志

范例

#!/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 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!