1. 为什么需要使用mock
unittest.mock是用于在单元测试中模拟和替换指定的对象及行为,以便测试用例更加准确地进行测试运行。例如对于以下代码,想要针对函数func_a写一个简单的单元测试:
import unittest
def func_c(arg1, arg2):
a_dict = {}
# 其他代码
return a_dict
def func_b(arg3, arg4):
b_list = []
a_arg1 = None
a_arg2 = None
# 其他代码
a_dict = func_c(a_arg1, a_arg2)
# 其他代码
return b_list
def func_a():
b_list = func_b('111', '222')
if 'aaa' in b_list:
return False
return True
class FuncTest(unittest.TestCase):
def test_func_a(self):
assert func_a()
但是这样的话,函数func_b和func_c的逻辑都需要一起测试,在单元测试中这明显是不合理的,对于想要测试的函数func_a,里面所使用到的其他函数或接口,我们只需要关心它的返回值即可,保证当前测试的函数按它自己的逻辑运行,所以可以写成下面这样:
import unittest
def mock_func_b(arg3, arg4):
return ['bbb', 'ccc']
def func_a():
# 使用一个模拟的mock_func_b代替真正的函数func_b
# 这个mock_func_b不需要关心具体实现逻辑,只关心返回值
b_list = mock_func_b('111', '222')
if 'aaa' in b_list:
return False
return True
class FuncTest(unittest.TestCase):
def test_func_a(self):
assert func_a()
注意,模拟的mock_func_b并不需要保证func_a中所有的可能分支和逻辑都执行一次,单元测试更多的是验证函数或接口(比如这里的func_a)是否与设计相符、发现代码实现与需求中存在的错误、修改代码时是否引入了新的错误等。但是这里的写法也有很大的问题,一个功能模块中使用的函数或接口通常来讲其实并不少、也没有这里这么简单,如果涉及的接口都要重新写一个mock对象(如mock_func_b),那单元测试的工作将会变得非常繁重和复杂,所以unittest中的mock模块派上了用场,这个模块也正如它的名称一样,可以模拟各种对象。
import unittest
from unittest import mock
def func_a():
# 创建一个mock对象,return_value表示在该对象被执行时返回指定的值
mock_func_b = mock.Mock(return_value=['bbb', 'ccc'])
b_list = mock_func_b('111', '222')
if 'aaa' in b_list:
return False
return True
class FuncTest(unittest.TestCase):
def test_func_a(self):
assert func_a()
2. Mock对象
2.1 快速上手
mock模块中的Mock类最常用的就是Mock和MagicMock,可以用来模拟对象、属性和方法,并且会保存这些被模拟的对象的使用细节,之后再使用断言来判断它们是否按照期待的被使用。
使用Mock类指定其被调用时触发的一些行为(Mock对象也可以用于替换指定的对象或方法)。
>>> from unittest.mock import MagicMock, Mock
>>> mock = Mock(side_effect=KeyError('foo'))
>>> mock() # 直接调用将发生指定的异常
Traceback (most recent call last):
...
KeyError: 'foo'
>>> values = {'a': 1, 'b': 2, 'c': 3}
>>> def side_effect_func(arg):
... return values[arg]
...
>>> mock.side_effect = side_effect_func # 重新指定side_effect
>>> mock('a'), mock('b'), mock('c') # 表示只能传入指定的参数
(1, 2, 3)
>>> mock('a'), mock('b'), mock('c'), mock('d') # 传入未指定的参数则会报错
Traceback (most recent call last):
...
KeyError: 'd'
>>> mock.side_effect = [5, 4, 3, 2, 1] # 重新指定side_effect
>>> mock(), mock(), mock(), mock() # 相当于迭代器,依次返回对应的值,使用完后再次调用就会报错
(5, 4, 3, 2)
>>> mock()
1
>>> mock()
Traceback (most recent call last):
...
StopIteration
使用spec参数指定Mock对象的属性和方法,指定时可以是一个对象,会自动将该对象的属性和方法赋给当前Mock对象,但是注意赋值的属性和方法也是Mock类型的,并不会真正执行对应方法的内容。
from unittest.mock import MagicMock, Mock
class SpecMock:
def test_spec(self):
print('spec running...')
def test_mock_spec():
mock = Mock(spec=SpecMock())
print(mock.test_spec) # 注意打印的内容,返回的是一个Mock类型
print(mock.test_spec()) # 该方法内的内容并没有被执行
mock.func()
if __name__ == '__main__':
test_mock_spec()
'''输出:
<Mock name='mock.test_spec' id='1956426692808'>
<Mock name='mock.test_spec()' id='1956430210952'>
Traceback (most recent call last):
...
AttributeError: Mock object has no attribute 'func'
'''
使用MagicMock创建并替换原有的方法。
from unittest.mock import MagicMock
class TestClass:
def func(self, a, b):
return a + b
tc = TestClass()
# 使用MagicMock创建并替换原来的func方法,并指定其被调用时的返回值
tc.func = MagicMock(return_value='666')
print(tc.func(2, 3))
# 判断func是否按照指定的方式被调用,如果没有,
# 比如这里指定assert_called_with(4, 5),就会抛出异常,
# 因为之前使用的是tc.func(2, 3)来进行调用的
print(tc.func.assert_called_with(2, 3))
'''输出:
666
None
'''
Mock类虽然支持对Python中所有的magic方法进行“mock”,并允许给magic方法赋予其他的函数或者Mock实例,但是如果需要使用到magic方法,最简单的方式是使用MagicMock类,它继承自Mock并实现了所有常用的magic方法。
>>> from unittest.mock import MagicMock, Mock, patch
>>> mock = Mock()
>>> mock.__str__ = Mock(return_value='666')
>>> str(mock)
'666'
>>> m_mock = MagicMock()
>>> m_mock.__str__.return_value = '999'
>>> str(m_mock)
'999'
>>> m_mock.__str__.assert_called_with()
可以使用create_autospec函数来创建所有和原对象一样的api。
>>> from unittest.mock import create_autospec
>>> def func(a, b, c):
... pass
...
>>> mock_func = create_autospec(func, return_value='func autospec...')
>>> func(1, 2, 3)
>>> mock_func(1, 2, 3)
'func autospec...'
>>> mock_func(111)
Traceback (most recent call last):
...
TypeError: missing a required argument: 'b'
2.2 Mock类和MagicMock类
Mock对象可以用来模拟对象、属性和方法,Mock对象也会记录自身被使用的过程,你可以通过相关assert方法来测试验证代码是否被执行过。MagicMock类是Mock类的一个子类,它实现了所有常用的magic方法。
2.2.1 Mock构造函数
构造函数 unittest.mock.Mock(spec=None, side_effect=None, return_value=DEFAULT, wraps=None, name=None, spec_set=None, unsafe=False, **kwargs)
参数解释:
- spec: 可以传入一个字符串列表、类或者实例,如果传入的是类或者实例对象,那么将会使用
dir
方法将该类或实例转化为一个字符串列表(magic属性和方法除外)。访问(get操作)任何不在此列表中的属性和方法时都会抛出AttributeError。如果传入的是一个类或者实例对象,那么__class__方法会返回对应的类,以便在使用isinstance
方法时进行判断。 - spec_set: spec参数的变体,但更加严格,如果试图使用get操作或set操作来操作此参数指定的对象中没有的属性或方法,则会抛出AttributeError。spec参数是可以对spec指定对象中没有的属性进行set操作的。参考
mock_add_spec
方法。 - side_effect: 可以传入一个函数,每次当Mock对象被调用的时候,就会自动调用该函数,可以用于抛出异常或者动态改变mock对象的返回值,此函数使用的参数与mock对象被调用时传入的参数是一样的,并且,除非它的返回值为
unittest.mock.DEFAULT
对象,否则这个函数的返回值将会作为mock对象的返回值。也可以传入一个exception对象或者实例对象,如果传入exception对象,则每次调用mock对象都会抛出该异常。也可以传入一个可迭代对象,每次调用mock对象时就会返回该迭代对象的下一个值。如果不想使用了,可以将它设置为None。具体参见后面mock对象side_effect
属性的使用。 - return_value: 每次调用mock对象时的返回值,默认第一次调用时创建新的Mock对象。
- unsafe: 如果某个属性或方法中会assert一个AttributeError,则可以设置
unsafe=True
来跳过这个异常。(Python3.5更新) - wraps: 包裹Mock对象的对象,当wraps不为None时,会将Mock对象的调用传入wraps对象中,并且可以通过Mock对象访问wraps对象中的属性。但是如果Mock对象指定了明确的return_value那么wraps对象就不会起作用了。
- name: 指定mock对象的名称,可在debug的时候使用,并且可以“传播”到子类中。
- 注: 初始化Mock对象时,还可以传入其他任意的关键字参数,这些参数会被用于设置成Mock对象的属性,具体参见后面的
configure_mock()
。
2.2.2 常用方法
assert_called()
assert:mock对象至少被调用过一次。(Python3.6新增)
assert_called_once()
assert:mock对象只被调用过一次。(Python3.6新增)
assert_called_with(*args, **kwargs)
assert:mock对象最后一次被调用的方式。
>>> from unittest.mock import Mock
>>> mock = Mock()
>>> mock.method(1, 2, 3, test='wow')
<Mock name='mock.method()' id='2956280756552'>
>>> mock.method.assert_called_with(1, 2, 3, test='wow')
assert_called_once_with(*args, **kwargs)
assert:mock对象以指定方式只被调用过一次。
assert_any_call(*args, **kwargs)
assert:mock对象以指定方式被调用过。
assert_has_calls(calls, any_order=False)文章来源:https://www.toymoban.com/news/detail-428524.html
calls是一个 unittest.mock.call
对象列表,any_order默认为False,表示calls中的对象必须按照原来的调用顺序传入,为True则表示可以是任意文章来源地址https://www.toymoban.com/news/detail-428524.html
到了这里,关于测试人必会的Python内置库:unittest.mock(单元测试mock的基础使用)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!