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

爲何須要Mock

假設如今系統有兩個模型A和B,其中A依賴B(例如A,B都是函數,A函數體內調用了B函數),可是B還沒完成,或者根本就不在控制以內;html

這時候又須要對A的功能進行單獨測試,就須要使用mock對象,模擬出一個假的fake_B模塊,雖然這個fake_B模塊是假的,可是咱們能夠經過對它的行爲進行定製來使他可以看起來「像」B模塊的功能,使A依賴fake_B,來對A的功能進行測試。python

同時,因爲fake_B是徹底可控的,除了能夠定製B的屬性,返回值以外,還能夠對B模塊的使用狀況進行測試。ide

輸入圖片說明

而在Python中可使用mock和unittest.mock來產生這樣的mock對象。函數

用法簡介

首先須要建立一個mock對象:測試

>>>from mock import MagicMock
>>>fake_obj = MagicMock()

而後能夠經過return_value設定它的返回值,當你調用這個mock對象時返回,返回值能夠是任何類型,變量,函數,類對象均可以。code

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

也能夠經過side_effect指定它的反作用,這個反作用就是當你調用這個mock對象是會調用的函數,也能夠選擇拋出一個異常,來對程序的錯誤狀態進行測試。orm

>>>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參數指定,前面的返回值和反作用也能夠在建立時進行指定。htm

>>>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對象自己提供了豐富的斷言方法,官方文檔中提供了詳盡的描述。blog

>>>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

參考資料

官方文檔 https://docs.python.org/3/library/unittest.mock.html

相關文章
相關標籤/搜索