unittest.mock
unittest.mock
mock是python中的测试库,主要用于提供测试桩。
- 单元测试时,如果依赖其他服务,那么如何隔离进行测试呢。
- 此时会建立一个测试桩,模拟所依赖的服务,从而避免依赖服务的干扰,专注测试所需的模块。
- 而mock库就可以帮助我们很方便地建立测试桩,在python2中,mock是独立的库。
- 在python3中,mock就被加入unittest单元测试库中了,足见mock库在单元测试中的不可或缺性。
基础概念
mock库中有两个基础的概念
- 一个是Mock类,
- Mock是一个灵活而且功能强大的类,用于模拟依赖的模块。
- 一个是patch
- patch用于在特定作用域内执行模拟。
Mock类
- 直接创建Mock对象,
- 简单情况下只需要通过
return_value
指定返回值
1
2
3
4
m = Mock(return_value=3)
assert m() == 3
# 创建了Mock对象,这个对象会在执行之后始终返回3
# 在测试中如果有模块需要返回特定值,就可以使用Mock类进行替换
- 有了Mock类作为模拟对象,接着使用patch指定被模拟对象
- 成功使用Mock类模拟了特定的方法,在with作用域内,调用被模拟方法
product_func()
时,实际调用的都是Mock类的方法。
1
2
3
4
5
6
with patch(product_func_path) as mock_method:
mock_method.return_value = 3
assert product_func() == 3
# 将需要模拟的方法`product_func()` 的路径`product_func_path` 作为参数传递给patch方法,并创建Mock对象`mock_method` 进行模拟。
# 可以看到指定`mock_method` 的返回值为3
# 然后执行`product_func()` 方法,最终返回值为3。
Mock 类
Mock类的初始化方法:
1
2
3
4
5
6
class unittest.mock.Mock(spec=None, \
side_effect=None, \
return_value=DEFAULT,\
wraps=None, \
name=None, \
spec_set=None, unsafe=False, **kwargs)
创建参数中最重要的两个参数是side_effect
和 return_value
return_value
- 用于表示此Mock对象的不变的返回值。
- 此返回值在第一次调用Mock对象时生成,后面多次调用对象,都返回此相同的返回值
side_effect
- 用于指定可变的返回值或抛出特定的异常。
- 此参数可以设置为下面三种类型:
- 如果
side_effect
设置为异常类,那么执行Mock对象时就会抛出此异常 - 如果
side_effect
设置为特定的方法,那么执行Mock对象时,就会将Mock对象执行的参数传递给side_effect
指定的方法,并将此方法返回的值作为Mock对象的返回值 - 如果
side_effect
设置为可迭代的对象,那么执行Mock对象就会从这个可迭代的对象中获取一个值进行返回
- 如果
side_effect
参数的使用:
1
2
3
4
5
6
7
8
9
10
11
m = Mock(side_effect=Exception('boom'))
with pytest.raises(Exception):
m()
# Mock类执行时,会抛出Exception,适用于测试会抛出特定异常的场景
m = Mock(side_effect=[3, 4, 5])
assert m() == 3
assert m() == 4
assert m() == 5
# 多次调用Mock会返回不同的值,每次的值是从可迭代对象`[3, 4, 5]` 中返回一个值。适用于方法执行后会返回了一系列特定的值的情况
还有一种情况,可以设置Mock为特定的方法,每次调用Mock对象就是执行此方法。
Mock方法与属性
Mock类提供了一些方法与属性,方便进行测试
assert_called(*args, **kwargs)
- 断言mock至少被调用一次
assert_called_once(*args, **kwargs)
- 断言mock调用仅一次
assert_called_with(*args, **kwargs)
- 断言mock以某种参数至少调用一次
assert_called_once_with(*args, **kwargs)
- 断言mock以某种参数调用仅一次
assert_any_called(*args, **kwargs)
- 断言mock以某种参数曾经被调用过,
- 区别与上面的
assert_called_with()
与assert_called_once_with()
必须是最近的那次调用符合断言
assert_has_calls(calls, any_order=False)
- 断言mock被按照的特定一组调用的方式调用过。
- 如果
any_order
是False,那么必须满足calls中的调用,而且必须是连续的, - 如果
any_order
是True,那么就只需要执行了calls中的调用就可以了
assert_not_called()
- 断言没有被调用
reset_mock(*args, return_value=False, side_effect=False)
- 重置mock对象的所有调用
attach_mock()
- 将mock附加在mock对象的属性上
called
- mock是否被调用的值
call_count
- 调用次数
return_value
- 设置mock的返回值
call_args
- 最后调用的参数
__class__
- 指定mock的类型,支持
isinstance()
判断
- 指定mock的类型,支持
NonCallableMock
- 是不可调用的Mock类,
- 适合模拟模拟不可调用的类对象
NonCallableMagicMock
- 是不可调用的MagicMock类
MagicMock
- 是Mock类的子类,
- 与Mock相比,默认实现了大部分的魔术方法
- Mock and MagicMock objects create all attributes and methods as you access them and store details of how they have been used.
You can configure them, to specify return values or limit what attributes are available, and then make assertions about how they have been used:
1 2 3 4 5 6 7 8
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
- 可以设置为特定的方法,迭代器或者一个异常。
- 设置为None可以取消
side_effect
的影响 - side_effect allows you to perform side effects, including raising an exception when a mock is called:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
mock = Mock(side_effect=KeyError('foo'))
mock()
# Traceback (most recent call last):
# ...
# KeyError: 'foo'
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)
patch
patch用于在特定范围内执行模拟。
- patch有多种使用方法,
- 可以使用装饰器方法,
- 也可以使用with语句,
- 也可以通过
start()
和stop()
方法指定模拟的开始和结束。
首先查看patch的初始化方法:
1
unittest.mock.patch(target, new=DEFAULT, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, **kwargs)
target
- 用于指定被模拟的对象,是一个类似
package.module.className
格式的字符串 - 其次参数
new_callable
可以用于指定最终创建的模拟对象类型, - 默认情况下是MagicMock类型的Mock对象
patch 使用方法
- 装饰器的方式
- 通过patch装饰器,通过target指定参数指定被模拟的对象
- 而创建的模拟对象会通过参数传递给方法
- 代码模拟了
someClass
类, - 创建的模拟对象通过参数
mock_class
传递给方法 - 在方法中可以看到
mock_class
与someClass
类型一致。 - 通过此方式指定模拟的范围是函数内部,当函数执行结束,模拟就不再生效
- 代码模拟了
1
2
3
@patch('__main__.someClass')
def function(normal_arguments, mock_class)
assert mock_class is someClass
- with语句的使用方式
- 通过上下文管理的方式设置模拟范围。
- 使用此方式创建的Mock对象通过
as
语句传递给后面使用- 模拟了
someClass
类,模拟对象为mock_class
- 在方法中,可以看到
mock_class
与someClass
类型一致, - 而被模拟对象
someClass
执行后的结果与mock_class
的返回值,即通过return_value
指定的值一致
- 模拟了
1
2
3
4
with patch('__main__.someClass') as mock_class:
ret = mock_class.return_value
assert mock_class is someClass
assert someClass() is ret
- 手动指定模拟的范围
- 使用
mock_class
模拟了someClass
类 - 最终通过
patch.start()
和patch.stop()
指定模拟的开始和结束
- 使用
1
2
3
4
5
patcher = patch('__main__.someClass')
mock_class = patcher.start()
assert someClass is mock_class
patcher.stop()
patch.object()
- 用于模拟对象的属性,使用Mock对象模拟对象的属性。
- 初始化时使用参数
target
指定对象,使用参数attribute
设置模拟的属性。
patch.dict()
- 用于模拟dict类型的对象,在模拟结束时恢复被模拟对象的数据
patch.multiple()
- 用于同时模拟多个对象
Python mock Patch os.environ and return value
1
2
3
4
5
6
7
8
9
10
11
12
def func():
print os.environ["mytemp"]
def test_func():
k = mock.patch.dict(os.environ, {"mytemp": "mytemp"})
k.start()
func()
k.stop()
test_func()
.
This post is licensed under CC BY 4.0 by the author.
Comments powered by Disqus.