在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

参考资料

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

猜你喜欢

转载自my.oschina.net/u/3826227/blog/1816937