Python mock 对象的基本思想是:用一个假对象 (mock_obj
) 代替参与测试过程的某个
真实对象 tested_module.myobj
,在执行测试 (test_function()
) 前,
可以调整假对象属性的值 (myobj.prop1 = 'val1'
) 或者方法的返回值
(myobj.method1.return_value = 'val2'
),
然后对测试目标或者假对象在测试过程中的行为进行验证:
from tested_module import myobj
@mock.patch('myobj')
def test_target(self, mock_obj):
myobj.prop1 = 'val1'
tested_function()
self.assertFalse(mock_obj.method1.called, "Failed to avoid running.")
上面的例子演示了:当假对象的属性 prop1
被设为 val1
时,
测试过程中它的 method1
方法不应该被调用。
下面按照作用域从大到小的顺序分别说明不同级别对象的 mock 方法。
mock 第三方库
通过mock Python 标准库避免副作用:
# mymodule.py:
import os
def rm(filename):
if os.path.isfile(filename):
os.remove(filename)
# mytests.py:
from mymodule import rm
from unittest import TestCase, mock
class RmTestCase(TestCase):
@mock.patch('mymodule.os')
def test_rm(self, mock_os):
rm("any path")
mock_os.remove.assert_called_with("any path")
真实的 os.remove()
没有执行,但我们可以验证这个方法被调用过了。
mock 类实例
如果 mock 目标不是模块级对象,而是函数参数:
# mymodule.py:
class MyOS:
def __init__(self, path):
self.path = path
def remove(self, target):
return 'remove file %s, and %s' % (self.path, target)
def rm(myos, filepath):
return myos.remove(filepath)
# mytests.py:
class RmTestCase(TestCase):
def test_myos(self):
mock_os = mock.create_autospec(MyOS)
mock_os.remove.return_value = "this is mocked"
print(rm(mock_os, 'mno'))
这里我们创建了真实 MyOS
类的替代品,然后定义了它的方法的返回值:
mock_os.remove.return_value = ...
,然后验证被测试函数在此情况下的执行结果。
mock 函数
如果进一步将上面的 myos
从函数参数变成内部对象,可以通过 mock 对象方法实现替代:
# mymodule.py:
class MyOS:
def __init__(self, path):
self.path = path
def remove(self, target):
return 'remove file %s, and %s' % (self.path, target)
def rm(filepath):
myos = MyOS('linux')
return myos.remove(filepath)
# mytests.py:
class RmTest(TestCase):
def test_rm(self):
print(rm('aabbc'))
@mock.patch('mymodule.MyOS.remove')
def test_mock_rm(self, mock_remove):
mock_remove.return_value = 'this is mocked'
print(rm('xyz'))
第一个测试用例是真实函数的运行结果,第2个测试用例是 mock 之后函数的运行结果。
多个 patch 的顺序
patch多个mock对象时,patch顺序和参数顺序要相反:
@mock.patch('mymodule.sys')
@mock.patch('mymodule.os')
@mock.patch('mymodule.os.path')
def test_something(self, mock_os_path, mock_os, mock_sys):
pass
这是因为 Python 的 decorator 是按顺序包装函数的:
patch_sys(patch_os(patch_os_path(test_something)))
,
导致最外层的 patch_sys
最后执行,所以其参数要放到最后。
Ref: