1. 程式人生 > >在Python中使用mock模組進行單元測試

在Python中使用mock模組進行單元測試

為什麼需要Mock

假設現在系統有兩個模型A和B,其中A依賴B(例如A,B都是函式,A函式體內呼叫了B函式),但是B還沒完成,或者根本就不在控制之內;這時候又需要對A的功能進行單獨測試,就需要使用mock物件,模擬出一個假的fake_B模組,雖然這個fake_B模組是假的,但是我們可以通過對它的行為進行定製來使他能夠看起來“像”B模組的功能,使A依賴fake_B,來對A的功能進行測試。同時,由於fake_B是完全可控的,除了可以定製B的屬性,返回值之外,還可以對B模組的使用情況進行測試。

圖片名稱


而在Python中可以使用mock和unittest.mock來產生這樣的mock物件。

用法簡介

首先需要建立一個mock物件

   >>>from mock import MagicMock

   >>>fake_obj = MagicMock() 

然後可以通過return_value設定它的返回值,當你呼叫這個mock物件時返回,返回值可以是任何型別,變數,函式,類物件都可以。

    >>>fake_obj.return_value = 'This is a mock object'
    >>>fake_obj()
    'This is a mock object'

也可以通過side_effect指定它的副作用,這個副作用就是當你呼叫這個mock物件是會呼叫的函式,也可以選擇丟擲一個異常,來對程式的錯誤狀態進行測試。

    >>>def b():
    ...    print 'This is b'
    ...
    >>>fake_obj.side_effect = b
    >>>fake_obj()
    This is b
    >>>fake_obj.side_effect = KeyError('This is b')
    >>>fake_obj()
    ...
    KeyError: 'This is b'

同時也可以以一個物件base為基礎,拓展mock,使得mock獲得base的所有屬性和方法(但僅是介面相同,沒有實現),當呼叫不屬於base的屬性和方法時,會丟擲AttributeError。這需要在建立mock物件的時候,在spec引數指定,前面的返回值和副作用也可以在建立時進行指定。

    >>>class B:
    ...    def __init__(self):
    ...        self.a = 'a'
    ...
    >>>b = B()
    >>>fake_obj = MagicMock(b)
    <MagicMock spec='B' id='4370614160'>
    >>>fake_obj.a
    'a'
    >>>fake_obj.b
    AttributeError: Mock object has no attribute 'b'

還有一點需要強調的就是,如果要模擬一個物件而不是函式,你可以直接在mock物件上新增屬性和方法,並且每一個新增的屬性都是一個mock物件,也就是說可以對這些屬性進行配置,並且可以一直遞迴的定義下去。

    >>>fake_obj.fake_a.return_value = 'This is fake_obj.fake_a'
    >>>fake_obj.fake_a()
    'This is fake_obj.fake_a'

在對mock物件進行一定的配置之後就是使用mock物件進行測試,這個模組採用的‘action-assertion’的方式進行測試,即先執行,再通過斷言進行測試。例如可以測試mock物件是否被呼叫,呼叫了幾次,如何被呼叫等等,mock物件本身提供了豐富的斷言方法,官方文件中提供了詳盡的描述。

    >>>fake_obj = MagicMock(return_value = 1)
    >>>fake_obj.assert_called()
    AssertionError: Expected 'None' to have been called.
    >>>fake_obj()
    >>>fake_obj.assert_called()
    >>>
    >>>fake_obj(1,2,3,key=1)
    >>>fake_obj.assert_called_with(1,2,3,key=1)
    >>>

同時mock庫提供了patch函式來簡化mock物件對原物件的替換。patch可以作為裝飾器或者上下文管理器使用,這意味著在裝飾的函式和上下文管理器中,對應的類會被替換為mock物件。

    >>>from mock import patch
    >>>@patch('__main__.SomeClass')
    ... def function(normal_argument, mock_class):
    ...     print(mock_class is SomeClass)   
    ...
    >>>function(None)
    True

參考資料