目錄python
import unittest # 將要被測試的排序函數 def sort(arr): l = len(arr) for i in range(0, l): for j in range(i + 1, l): if arr[i] >= arr[j]: tmp = arr[i] arr[i] = arr[j] arr[j] = tmp # 編寫子類繼承 unittest.TestCase class TestSort(unittest.TestCase): # 以 test 開頭的函數將會被測試 def test_sort(self): arr = [3, 4, 1, 5, 6] sort(arr) # assert self.assertEqual(arr, [1, 3, 4, 5, 6]) if __name__ == '__main__': unittest.main()
首先,咱們須要建立一個類TestSort, 繼承類unittest.TestCase
; 而後,在這個類中定義相應的測試函數test_sort(), 進行測試。
注意,測試函數要以 test 開頭,而測試函數的內部,一般使用assertEqual()、assertTrue()、assertFalse()和assertRaise()等
assert 語句對結果進行驗證。算法
這個例子比較簡單,若是碰到複雜的例子,須要用到單元測試的技巧。數據庫
mock、side_effect和patch。這三者用法不同,但都是一個核心思想,即用虛假的實現,來替換掉被測試函數的一些依賴項,讓咱們能把
更多的精力放在須要被測試的功能上。後端
mock 是單元測試中最核心最重要的一環。mock 是指經過一個虛假對象,來代替被測試函數或模塊須要的對象。數組
舉個例子,好比你要測一個後端API邏輯的功能性,但通常後端API都依賴於數據庫、文件系統、網絡等。這樣,你就須要經過mock,來建立一些
虛假信息的數據庫層、文件系統層、網絡層對象,以即可以簡單地對核心後端邏輯單元進行測試。網絡
Python mock則主要使用 mock 或者 MagicMock 對象,示例以下:ide
import unittest from unittest.mock import MagicMock class A(unittest.TestCase): def m1(self): val = self.m2() self.m3(val) def m2(self): pass def m3(self, val): pass def test_m1(self): a = A() a.m2 = MagicMock(return_value="custom_val") a.m3 = MagicMock() a.m1() self.assertTrue(a.m2.called) # 驗證 m2 被 call 過 a.m3.assert_called_with("custom_val") # 驗證 m3 被指定參數 call 過 if __name__ == '__main__': unittest.main(argv=['first-arg-is-ignored'], exit=False)
這段代碼中,咱們定義了一個類的三個方法m1()、m2()、m3()。咱們須要對 m1() 進行單元測試,可是 m1() 取決於 m2() 和 m3()。若是m2()和m3()
的內部比較複雜,你就不能只是簡單地調用m1()函數來進行測試,可能須要解決不少依賴項的問題。模塊化
可是有了mock就好辦了。咱們能夠把m2()替換爲一個返回具體數值的value, 把m3()替換爲另外一個mock(空函數)。這樣,測試m1()就很容易了,咱們能夠測試
m1()調用m2(),而且用m2()的返回值調用m3()。函數
這樣測試的m1()基本上毫無心義,看起來只是象徵性的測了一下邏輯。真正的工業化代碼,都是不少層模塊相互邏輯調用的一個樹形結構。單元測試須要測的是
某個節點的邏輯功能,mock 掉相關的依賴項是很是重要的。這也是爲何會被叫作單元測試 unit test, 而不是其餘的 integration test, end to end test。post
Mock Side Effect 就是mock的函數,屬性是能夠根據不一樣的輸入,返回不一樣的數值,而不僅是一個return_value。
好比下面這個例子,測試的是輸入參數是否爲負數,輸入小於0則輸出爲1,不然輸出爲2。這就是 Mock Side Effect 的用法。
from unittest.mock import MagicMock def side_effect(arg): if arg < 0: return 1 else: return 2 mock = MagicMock() mock.side_effect = side_effect mock(-1) mock(1)
至於patch,給開發者提供了很是便利的函數 mock 方法。它能夠應用 Python 的 decoration 模式或是 context manager 概念,快速天然地 mock
所須要的函數。它的用法也不難,咱們來看代碼:
from unittest.mock import patch @patch('sort') def test_sort(self, mock_soft): ... ...
在這個 test 裏面,mock_sort 替代 sort 函數自己的存在,因此咱們能夠像開始提到的 mock object 同樣,設置 return_value 和 side_effect。
另外一種 patch 的常見用法,是mock類的成員函數,這個技巧咱們在工做中也常常會用到,好比說一個類的構造函數很是複雜,而測試其中一個成員函數並不
依賴全部初始化的 object。它的用法以下:
with patch.object(A, '__init__', lambda x: None): ...
在 with 語句裏面,咱們經過 patch,將 A 類的構造函數 mock 爲一個 do nothing 的函數,這樣就能夠很方便地避免一些複雜的初始化(initialization)。
綜上,單元測試的核心仍是 mock, mock 掉依賴項,測試相應的邏輯或算法的準確性。
咱們能夠用 Python 的 coverage tool 來衡量 Test Coverage, 而且顯示每一個模塊爲被 coverage 的語句
好比,咱們寫了一個下面這個函數,對一個數組進行處理,並返回新的數組:
def work(arr): # pre process ... ... # sort l = len(arr) for i in range(0, l): for j in range(i + 1, j): if arr[i] >= arr[j]: tmp = arr[i] arr[i] = arr[j] arr[j] = tmp # post process ... ... return arr
這段代碼的大概意思是,現有個預處理,再排序,最後在處理一下而後返回,這時候咱們就能夠用到模塊化:
def preprocess(arr): ... ... return arr def sort(arr): ... ... return arr def postprocess(arr): ... return arr def work(self): arr = preprocess(arr) arr = sort(arr) arr = postprocess(arr) return arr
接着再進行相應的測試,測試三個子函數的功能正確性;而後經過 mock 子函數,調用 work() 函數,來驗證三個子函數被 call 過。
from unittest.mock import patch def test_preprocess(self): ... def test_sort(self): ... def test_postprocess(self): ... @patch('%s.preprocess') @patch('%s.sort') @patch('%s.postprocess') def test_work(self,mock_post_process, mock_sort, mock_preprocess): work() self.assertTrue(mock_post_process.called) self.assertTrue(mock_sort.called) self.assertTrue(mock_preprocess.called)