1. 为什么需要 Mock
在做单元测试时,被测试函数有时并不是一个可执行的独立单元。被测试函数依赖于一些外部资源,比如另外一个函数的返回值、数据库中某一条数据值等。
为了屏蔽外部依赖的干扰,我们会采用 Mock 技术。通过模拟测试资源的方式,满足依赖条件。
从设计模式的角度看,对于满足单一职责原则的函数、类,使用 Mock 的方式忽略外部依赖进行测试也是合理的。因为,外部依赖的测试,应该在其内部完成,而不应转移到调用方。
反推,如果单元测试不好写,那么很有可能是软件没有遵循一定的设计模式进行实践。这种情况下,最重要的是让软件符合设计模式的规范,而不要急于做单元测试。
2. Stub、Fake、Mock 区别
实际上,Mock 并非模拟测试资源的唯一方式,类似的还有 Stub 和 Fake。
Stub 为测试对象提供了一套方法接口,模拟真实的测试资源。当测试对象调用 Stub 方法时,Stub 响应预定的结果,也可能产生预定的错误或异常。Stub 可以跟踪和测试对象的交互,但不处理出入的数据。
Fake 也提供了一套方法接口,用于跟踪和测试对象的交互,但与 Stub 不同,Fake 处理了输入的数据,并以此产生结果。
使用 Stub 和 Fake ,都可以对测试对象进行状态验证。但是如果,想知道测试对象是否按照正确顺序调用了方法,则需要进行行为验证。
Mock 可以用于测试对象的行为验证。
3. Python 中的 Mock
在 Python 3.3 版本之前,使用 Mock 需要先从第三方安装:
|
|
自 Python 3.3 开始,Mock 被引入标准库中,命名为 unittest.mock。
先来看一个简单的例子(下面均以 Python 2.7 为例):
|
|
使用 Mock 做测试的思路:
- 找到被测对象的外部依赖。可能是一个函数、对象、类等。
- 实例化 Mock 类,得到 Mock 对象,设置 Mock 对象的行为与依赖一致。比如,返回值、对象值等。
- 使用 Mock 对象替换掉外部依赖。
- 写测试代码和判断是否符合预期的断言。
4. Mock、MagicMock、patch 使用
4.1 Mock
Mock 类定义:
|
|
重要参数:
- return_value,表示当 mock 对象被调用,side_effect 函数返回的是 DEFAULT 时,返回值。
- side_effect,这个参数指向一个可调用或可迭代的对象。当 mock 对象被调用时,如果该函数返回值不是 DEFAULT 时,那么以该函数的返回值作为 mock 对象调用的返回值
side_effect 有三种用法:
1,按序列返回值
|
|
2,调用函数处理返回值
|
|
3,主动抛出异常
|
|
4.2 MagicMock
MagicMock 类定义:
|
|
MagicMock 是 Mock 类的一个子类,实现了很多 magic 方法和属性。
看一个例子:
|
|
创建 Mock 对象时,默认没有实现 __iter__
函数,所以会报错。但是 ,MagicMock 对象中增加了这个 magic method。
通常情况下,除非是对迭代对象的 Mock,我们感受不到 Mock 和 MagicMock 的区别。
4.3 patch
patch 是 Mock 提供的一个用于替换某些函数、属性的方法。
patch 定义:
|
|
重要参数:
- target,被替换的函数字符串名,需要输入完整路径
- new,替换成的 Mock 实例,默认为 MagicMock
通常有两种写法:
- 使用 with 控制上下文范围,进行替换
|
|
- 使用装饰器,替换指定的属性或函数
|
|
patch 还有三个非常有用的拓展:
- 替换字典用的 patch.dict
|
|
- 多次替换用的 patch.multiple
|
|
- 替换实例用的 patch.object
|
|
5. 断言
断言,用于判断测试是否符合预期。Mock 相关的断言:
- assert_not_called,没调用过
- assert_called_with,调用过
- assert_called_once_with,仅调用过一次
- aseert_has_calls,按指定顺序调用过
- assert_any_calls,是否全局调用过
- reset_mock,重置调用记录
使用示例:
|
|