unittest.mock - mock object library

翻译自unittest.mock — mock object library

unittest.mock是一个在Python中做测试的库,它允许你使用mock对象在测试中替换系统的的某些部分,并且假定它们是怎样使用的。

unittest.mock提供了一个核心Mock类来在测试中移除创建众多基础构件的需要。在使用之后,你可以假定哪些方法属性被使用以及调用的时候使用哪些参数。你也可以指定返回值并且以普通的方式设置需要的属性。

另外,mock提供了patch()装饰器用来在测试中对模块级和类级属性进行补丁,协同使用sentinel来创建一个独一无二的对象。

Mock非常容易使用并且是设计与unittest一起使用的。Mock是基于action -> assertion模式而不是很多mocking测试框架使用的record -> replay

Quick Guide

MockMagicMock对象创建了所有的属性和方法,这样你可以获取它们并且储存关于它们如何被使用的细节。你可以配置它们,去指定返回值或者限制哪些属性是可用的,然后做出关于它们如何被使用的假定。

basic

1
2
3
4
5
from unittest.mock import MagicMock
thing = ProductionClass()
thing.method = MagicMock(return_value=3)
thing.method(3, 4, 5, key='value') # 3
thing.method.assert_called_with(3, 4, 5, key='value')

side_effect

side_effect允许你配置side effects,包括当mock被调用的时候引发一个异常

1
2
3
4
5
6
7
8
9
mock = Mock(side_effect=KeyError('foo'))
mock() # raise KeyError Exception
values = {'a': 1, 'b': 2, 'c': 3}
def side_effect(arg):
return values[arg]
mock.side_effect = side_effect
mock('a'), mock('b'), mock('c') # (1,2,3)
mock.side_effect = [5, 4, 3, 2, 1]
mock(), mock(), mock() # (5, 4, 3)

当mock调用的时候side_effect可以是一个函数也可以是一个可迭代对象或者一个异常

Mock有其他多种方式来配置并且控制其行为,比如spec参数配置mock通过其它对象去设定其指定值。尝试获取在spec中不存在的属性和方法会导致失败并且引发AttributeError

如果你传递进去一个函数,它将会和mock同样的参数调用。除非函数返回了DEFAULT单例,mock调用将会返回这个函数返回的值。如果这个函数返回了DEFAULT,mock就会返回其正常值,return_value。返回None应该也是返回正常值。

DEFAULT是一个提前创建的值,它被side_effect用来指示应该使用正常的返回值

如果传递了一个可迭代对象,它将会被用来恢复一个迭代器,然后每次调用的时候产生一个值。这个值可以是一个异常实例,或者从调用mock中返回的一个值。

一个mock使用示例来产生一个异常

1
2
3
mock = Mock()
mock.side_effect = Exception('Boom!')
mock()

使用side_effect来返回一个value的序列

1
2
3
mock = Mock()
mock.side_effect = [3, 2, 1]
mock(), mock(), mock()

使用一个可调用对象

1
2
3
4
5
mock = Mock(return_value=3)
def side_effect(*args, **kwargs):
return DEFAULT
mock.side_effect = side_effect
mock()

side_effect可以在构造器中使用

1
2
3
4
side_effect = lambda value: value + 1
mock = Mock(side_effect=side_effect)
mock(3)
mock(-8)

side_effect置为None来清除它

1
2
3
4
m = Mock(side_effect=KeyError, return_value=3)
m() # raise Exception
m.side_effect = None
m()

patch

patch()装饰器/上下文管理器使得mock测试中某个模块的类或者对象非常容易。你指定的对象将被在测试中的mock对象替换,然后在测试结束时候重新载入原来的对象。

1
2
3
4
5
6
7
8
9
10
11
from unittest.mock import patch
@patch('module.ClassName2')
@patch('module.ClassName1')
def test(MockClass1, MockClass2):
module.ClassName1()
module.ClassName2()
assert MockClass1 is module.ClassName1
assert MockClass2 is module.ClassName2
assert MockClass1.called
assert MockClass2.called
test()

装饰器的顺序就是传入的参数的顺序,装饰器顺序是从下往上,参数就是从左往右。

patch()关键的是要patch的对象查找的命名空间,通常是非常直接的。

同样patch()可以被用作成为一个上下文管理器

1
2
3
4
5
with patch.object(ProductionClass, 'method', return_value=None) as mock_method:
thing = ProductionClass()
# 返回值是None
thing.method(1, 2, 3)
mock_method.assert_called_once_with(1, 2, 3)

patch.dict()可以用来在字典中在某个作用域中设置值,并且在测试完成后重新载入之前的字典

1
2
3
4
5
foo = {'key': 'value'}
original = foo.copy()
with patch.dict(foo, {'newkey': 'newvalue'}, clear=True):
assert foo == {'newkey': 'newvalue'}
assert foo == original

magic method

Mock支持模拟Python魔法方法,最简单的使用魔法方法是使用MagicMock类,它允许你这样:

1
2
3
4
mock = MagicMock()
mock.__str__.return_value = 'foobarbaz'
str(mock)
mock.__str__.assert_called_with()

Mock允许你将函数赋值给魔法方法,然后它们就会被调用。MagicMock类只是一个Mock变体,拥有所有的预先创建的魔法方法。

下面是使用普通的Mock类来使用魔法方法

1
2
3
mock = Mock()
mock.__str__ = Mock(return_value='wheeeeee')
str(mock)

auto-speccing

为了确保在你的测试中的mock对象有和要替换的对象相同的api,你可以使用auto-speccing,auto-speccing可以通过autospec参数改变完成,或者create_autospec()函数。auto-speccing创建了和要替换的对象相同的属性和方法的mock对象,任意的函数和方法都将有和真实对象一样的签名。

如果使用错误,将会产生和生产代码一样的错误方式。

1
2
3
4
5
6
7
from unittest.mock import create_autospec
def function(a, b, c):
pass
mock_function = create_autospec(function, return_value='fishy')
mock_function(1, 2, 3) # 'fishy'
mock_function.assert_called_once_with(1, 2, 3)
mock_function('wrong arguments') # 异常TypeError

create_autospec也可以使用在类中,它会赋值__init__方法的签名,在可调用对象中将会复制__call__方法的签名。