
在编写单元测试时,我们经常需要测试一个类的方法,而这个方法内部可能根据某些条件调用其他的函数或方法。一个常见的挑战是,当尝试模拟整个类实例时,如何确保内部被调用的函数也能被正确模拟和验证。
考虑以下Python类 RMTable 及其方法 cal_sync_column:
from dataclasses import dataclass, ClassVar
from unittest.mock import patch, MagicMock, Mock
# 假设这些是定义在my_module中的函数
def feature_flag():
# 模拟一个特性开关函数
return False
def get_sync_column():
# 模拟一个返回同步列名的函数
return "default_sync_col"
@dataclass(frozen=True)
class RMTable():
sync_column: ClassVar[str] = None
def __post_init__(self) -> None:
if self.sync_column is None:
object.__setattr__(self, "sync_column", self.cal_sync_column())
def cal_sync_column(self) -> str:
if not feature_flag():
return "_synced"
else:
return get_sync_column() # 这个函数是我们想要测试其调用的cal_sync_column 方法根据 feature_flag() 的返回值,决定是返回硬编码的 _synced 还是调用 get_sync_column()。我们的目标是测试当 feature_flag() 返回 True 时,get_sync_column() 是否被正确调用了一次。
最初的测试尝试可能如下所示:
# 假设以下代码在 my_module_test.py 中,并从 my_module 导入 RMTable, feature_flag, get_sync_column
# from my_module import RMTable, feature_flag, get_sync_column
def test_sync_column_initial_attempt():
with patch("my_module.feature_flag") as feature_flag_mock:
with patch("my_module.get_sync_column") as mock_sync_column:
feature_flag_mock.return_value = True # 确保进入 else 分支
# 错误的方法:模拟了整个 RMTable 实例
rm_table_mock = MagicMock(spec=RMTable)
rm_table_mock.cal_sync_column.return_value = "FLAG_1" # 设置模拟方法的返回值
result = rm_table_mock.cal_sync_column() # 调用模拟对象的模拟方法
assert result == "FLAG_1"
mock_sync_column.assert_called_once() # 期望 get_sync_column 被调用一次运行上述测试时,会得到 AssertionError: Expected 'get_sync_column' to have been called once. Called 0 times. 错误。这表明 get_sync_column 根本没有被调用。
立即学习“Python免费学习笔记(深入)”;
错误发生的原因在于对 MagicMock(spec=RMTable) 的使用。
当执行 rm_table_mock = MagicMock(spec=RMTable) 时,我们创建了一个 RMTable 类的模拟对象。spec=RMTable 的作用是确保 rm_table_mock 具有 RMTable 定义的所有属性和方法,并在访问不存在的属性时抛出错误,从而提高测试的健壮性。
然而,MagicMock 对象的方法(例如 rm_table_mock.cal_sync_column)本身也是模拟对象。当您设置 rm_table_mock.cal_sync_column.return_value = "FLAG_1" 并调用 rm_table_mock.cal_sync_column() 时,实际上是调用了 cal_sync_column 这个模拟方法,并直接返回了您设置的 return_value。它并没有执行 RMTable 类中定义的真实 cal_sync_column 方法的任何逻辑,因此,真实方法内部对 feature_flag() 或 get_sync_column() 的调用自然也就不会发生。
简而言之,MagicMock(spec=Class) 模拟的是类的接口,而不是类的内部实现逻辑。如果你想测试类方法的内部逻辑(包括条件分支和对其他函数的调用),你就需要让这个类方法真实地执行。
要正确测试类方法 cal_sync_column 内部对 get_sync_column 的条件调用,关键在于以下两点:
下面是修正后的测试代码:
from unittest.mock import patch, MagicMock, Mock
# 导入 my_module 中的真实类和函数
from my_module import RMTable, feature_flag, get_sync_column
def test_sync_column_corrected():
# 模拟 my_module.feature_flag 函数
with patch("my_module.feature_flag") as feature_flag_mock:
# 模拟 my_module.get_sync_column 函数
with patch("my_module.get_sync_column") as mock_sync_column:
# 1. 设置 feature_flag 的返回值,以确保进入 else 分支
feature_flag_mock.return_value = True
# 2. 创建 RMTable 的真实实例
rm_table = RMTable()
# 3. 设置 get_sync_column 模拟的返回值
# 因为现在调用的是真实方法,它会去调用我们模拟的 get_sync_column
mock_sync_column.return_value = "FLAG_1"
# 4. 调用真实 RMTable 实例的 cal_sync_column 方法
result = rm_table.cal_sync_column()
# 5. 验证 cal_sync_column 的返回值
assert result == "FLAG_1"
# 6. 验证 get_sync_column 是否被调用了一次
mock_sync_column.assert_called_once()
# 可选:在文件直接运行时执行测试
if __name__ == '__main__':
test_sync_column_corrected()在这个修正后的测试中:
在Python单元测试中,测试类方法内部基于条件逻辑调用的函数时,关键在于理解何时需要真实实例的执行,以及何时需要对依赖进行模拟。通过创建类的真实实例,并精准地模拟其内部调用的外部函数或模块,我们能够有效地验证复杂方法中的条件分支和内部函数调用,从而编写出更健壮、更可靠的单元测试。
以上就是Python单元测试:正确模拟类方法内部条件调用的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号