模拟(Mocking)与桩(Stubbing)是在单元测试中重要的技术,帮助隔离正在测试的功能,通过替换真实的对象或方法为受控的替代品。在本章中我们将详细了解模拟(Mocking)与桩(Stubbing)。
Python 模拟(Mocking)
模拟是一种测试技术,其中创建模拟对象来模拟真实对象的行为。
这对于测试与复杂、不可预测或缓慢的组件(如数据库、Web 服务或硬件设备)交互的代码非常有用。
模拟的主要目的是隔离待测代码,并确保其行为独立于其依赖关系进行评估。
模拟的关键特征
以下是 Python 中模拟的关键特征:
-
行为模拟:模拟对象可以被编程为返回特定的值、引发异常或在各种条件下模仿真实对象的行为。
-
交互验证:模拟对象可以通过允许测试者验证特定的方法是否以预期的参数被调用,记录它们是如何被使用的。
-
测试隔离:通过用模拟对象替换真实对象,测试可以专注于待测代码的逻辑而不必担心外部依赖关系的复杂性或可用性。
Python 模拟示例
下面是 database.get_user
方法的一个示例,该方法被模拟为返回一个预定义的用户字典。测试可以验证该方法是否使用正确的参数被调用。
from unittest.mock import Mock
database = Mock()
database.get_user.return_value = {"name": "Prasad", "age": 30}
user = database.get_user("prasad_id")
print(user)
database.get_user.assert_called_with("prasad_id")
输出
{'name': 'Prasad', 'age': 30}
Python 桩(Stubbing)
桩(Stubbing)是一种相关的测试技术,其中某些方法或函数被替换为返回固定、预定响应的“桩”。
桩比模拟简单,因为它通常不涉及记录或验证交互。相反,桩通过确保一致性和可重复的结果,关注于通过提供受控输入来测试代码。
桩的关键特征
以下是 Python 中桩的关键特征:
-
固定响应:桩无论怎样被调用都会返回特定的、预定义的值或响应。
-
简化依赖关系:通过用桩替换复杂的方法,测试可以避免设置或管理复杂的依赖关系。
-
聚焦输入:桩强调通过提供已知输入给待测代码,允许测试者专注于测试代码的逻辑和输出。
Python 桩示例
下面是一个 get_user_from_db
函数的示例,该函数被桩为总是返回一个预定义的用户字典。测试不需要与真实的数据库交互,简化了设置并保证了一致的结果。
from unittest.mock import patch
def get_user_from_db(user_id):
pass
with patch('__main__.get_user_from_db', return_value={"name": "Prasad", "age": 25}):
user = get_user_from_db("prasad_id")
print(user)
输出
{'name': 'Prasad', 'age': 25}
Python 模拟 vs. 桩
模拟与桩的关键特征、目的和使用案例的比较有助于澄清何时使用每种方法。通过探索这些区别,开发人员可以创建更有效和可维护的测试,最终导致更高品质的软件。
下表展示了根据不同的标准模拟与桩之间的关键差异:
标准 |
模拟 |
桩 |
目的 |
模拟真实对象的行为 |
提供固定、预定的响应 |
交互验证 |
可验证方法调用和参数 |
通常不验证交互 |
复杂度 |
更复杂;可以模拟各种行为 |
较简单;专注于提供受控输入 |
使用场景 |
隔离并测试具有复杂依赖关系的代码 |
通过提供已知响应来简化测试 |
记录行为 |
记录方法是如何被调用的 |
不记录交互 |
状态管理 |
跨调用可以保持状态 |
通常无状态;返回固定输出 |
框架支持 |
主要使用 unittest.mock 与 Mock 和 MagicMock 特性 |
使用 unittest.mock 的 patch 进行简单的替换 |
灵活性 |
高度灵活;可以模拟异常和副作用 |
有限灵活性;专注于返回值 |