模拟方法

使用 Mock 的常见场景包括:

通过 Mock 可以替换对象的方法,检查系统的其它部分是否使用正确的参数调用了方法:

一旦 Mock 对象被使用(本例中的 real.method),可以通过它的方法和属性断言它是被如何使用的。

一旦 Mock 对象被调用,它的 called 属性会被设置为 True。更为重要的是我们可以使用 assert_called_with()assert_called_once_with() 方法检查是否使用正确的参数调用过 Mock 对象。

下面的例子测试了调用 ProductionClass().method 导致 something 方法被调用:


模拟对象上的方法调用

在上面的例子中,我们直接模拟了对象上的一个方法,检查它是否被正确的调用。另外一个常见情形是把 Mock 对象传递给一个方法,然后检查它是否被正确地使用。

下面的 ProductionClass 有一个 closer 方法,当使用一个对象调用它时,它会调用对象的 close 方法:

因此为了测试它,我们需要传进去一个带 close 方法的对象,然后检查它是否被正确地调用:

为了在 Mock 对象上提供 ‘close’ 方法,我们无需做任何事情。访问 close 时会自动创建它。因此,如果 ‘close’ 还没被调用过,在测试中访问它将会创建它,但是 assert_called_with() 方法会抛出失败异常。


模拟类

Mock 的一种常见的使用情形是模拟在测试中被实例化的类。在模拟类时,使用 Mock 对象替代被模拟的类。通过调用被模拟的类来创建实例。即可以通过被模拟的类的返回值来访问 “mock 实例”。

在下面的例子中,函数 some_function 实例化 Foo,并调用它的一个方法。patch() 使用 Mock 对象替换类 FooFoo 实例是调用 Mock 对象的返回值,因此通过修改 Mock 对象的返回值来配置它:


给 Mock 对象指定名称

给 Mock 对象指定名字很有用。Mock 对象的名称会被展示在它的 repr 中,并且当 Mock 对象出现在测试失败消息中时,名称很有用。Mock 对象的名称也会传播给它的属性和方法:


追踪所有调用

mock_calls 属性会记录所有对 Mock 对象的子属性以及子属性的子属性的调用:

使用 call 对象可以构造用于与 mock_calls 比较的列表:

然而,传递给返回 Mock 对象的调用的参数不会被记录:


设置返回值和属性

在 Mock 对象上设置返回值非常容易:

可以使用同样的方式给 Mock 对象上的方法设置返回值:

也可以在构造器中设置返回值:

如果需要在 Mock 对象上设置属性,可以这样做:

通过 Mock 可以模拟更复杂的情形,比如 mock.connection.cursor().execute("SELECT 1")。如果我们想让这个调用返回一个列表,那么我们必须配置这个嵌套调用的返回结果。

可以使用 call 对象以“链式调用”的形式构造调用集:

.call_list() 的调用会将 call 对象转换成代表“链式调用”的调用列表。


模拟抛出异常

side_effect 是 Mock 的一个非常有用的属性。如果将其设置为一个异常类或实例,当 Mock 对象被调用时,该异常将被抛出:


Side Effect 函数和可调用对象

也可以将 side_effect 设置为函数或可迭代对象。对于 Mock 对象将要被调用多次,并且每次调用需要返回不同的值的情形,可以将 side_effect 指定为一个可迭代对象。当把 side_effect 设置为可迭代对象时,每次调用 Mock 对象将返回可迭代对象的下一个值:

对于更复杂的使用场景,比如根据调用 Mock 对象时传递的参数动态地更改返回值,可以将 side_effect 设置为函数。该函数将被使用与 Mock 对象相同的参数调用。该函数的返回值就是调用 Mock 对象的返回值:


从已有对象创建 Mock

使用 spec 参数可以把一个对象设置为 Mock 对象的规格。访问规格对象上不存在的属性或方法时,将立即抛出属性错误:

使用规格对象还可以更智能地匹配对 Mock 对象的调用,而不必关注参数的传递方式:


Patch 装饰器

在测试中,一种常见的需求是模拟类属性或模块属性。如果模块和类是全局的,那么在测试之后,必须把它们复原。如果模拟对象在其它测试中持续存在,那么会导致难以诊断的问题。

为此,Mock 提供了三个便捷的装饰器:patch()patch.object()patch.dict()patch 接受一个字符串作为参数,该参数的形式是 package.module.Class.attribute,用于指定要模拟的属性。它也可以接受一个可选的值作为参数,该值用于替换被模拟的属性。

patch.object 带三个参数:

使用 patch() 可以模拟模块(包括 builtins):

模块名可以带“.”,形式是 package.module

比较优雅的使用方式是装饰测试方法本身:

当使用只带一个参数的 patch() 或者带两个参数的 patch.object() 时,Mock 对象会被自动创建,并被传进测试函数 / 方法中:

可以叠加多个 patch 装饰器:

当嵌套 patch 装饰器时,会将 Mock 对象按照它们被应用的顺序传进函数。即按照从低向上的顺序将 Mock 对象传进函数。

patch.dict() 用于在一个作用域中设置字典的值,当测试结束时,字典会被恢复到原始状态:

patch()patch.object()patch.dict() 支持上下文管理协议。

当使用 patch() 创建 Mock 对象时,可以通过 with 语句的 as 形式获取 Mock 对象的引用:

另外,patch()patch.object()patch.dict() 也可以用作类装饰器,这种情况相当于给每个名称以 “test” 开头的方法单独应用装饰器。


原创声明

本文翻译自官方文档:https://docs.python.org/3/library/unittest.mock-examples.html。未完待续。


说明

本文在 Python 3.8.2 测试通过