單元測試 筆記

簡單事例

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 是單元測試中最核心最重要的一環。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 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

至於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 掉依賴項,測試相應的邏輯或算法的準確性。

高質量單元測試的關鍵

Test Coverage

咱們能夠用 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)
相關文章
相關標籤/搜索