
在编写单元测试时,我们经常需要模拟(mock)外部依赖项,以隔离被测试代码并确保测试的独立性。然而,当被测试的类方法包含条件逻辑(如if/else),并且在某个分支中调用了另一个函数时,如何正确地模拟这个内部调用的函数,同时又确保该类方法本身的逻辑被执行,是一个常见的挑战。
考虑以下一个dataclass的示例,其中cal_sync_column方法根据feature_flag()的返回值,决定是直接返回一个硬编码的字符串,还是调用get_sync_column()函数:
from dataclasses import dataclass, ClassVar
from unittest.mock import patch, MagicMock
# 假设这些是外部模块中的函数
def feature_flag():
# 模拟一个外部特性开关
return False
def get_sync_column():
# 模拟一个返回同步列名的函数
return "default_sync_column"
@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() # 这个函数是我们想要测试其被调用的情况我们的目标是测试当feature_flag()返回True时,get_sync_column()是否被正确调用。
为了测试else分支,我们可能会尝试以下测试代码:
from unittest.mock import patch, MagicMock
from my_module import RMTable, feature_flag, get_sync_column # 假设这些在my_module中
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" # 为mock的方法设置返回值
result = rm_table_mock.cal_sync_column() # 调用mock的cal_sync_column
assert result == "FLAG_1"
mock_sync_column.assert_called_once() # 断言get_sync_column被调用运行上述测试代码,将会得到一个AssertionError:
立即学习“Python免费学习笔记(深入)”;
AssertionError: Expected 'get_sync_column' to have been called once. Called 0 times.
这个错误表明get_sync_column函数从未被调用。
问题出在这一行:rm_table_mock = MagicMock(spec=RMTable)。
当我们创建一个MagicMock实例并为其指定spec=RMTable时,rm_table_mock会模拟RMTable类的行为,并确保它拥有RMTable中声明的所有属性和方法。然而,这并不会让rm_table_mock成为一个真正的RMTable实例。
当您执行rm_table_mock.cal_sync_column()时,您调用的是MagicMock对象上的一个模拟方法,而不是RMTable类中定义的真实cal_sync_column方法。由于您已经为rm_table_mock.cal_sync_column设置了return_value = "FLAG_1",这个模拟方法会直接返回"FLAG_1",而不会执行其内部的任何逻辑,包括对feature_flag()的检查和对get_sync_column()的调用。因此,get_sync_column()自然也就不会被调用。
spec参数的作用是确保模拟对象拥有与原始对象相同的接口,防止调用不存在的方法或属性,但它不会让模拟对象执行原始对象的内部实现。
要正确测试cal_sync_column方法在else分支中调用get_sync_column()的行为,我们需要让cal_sync_column方法本身以真实的方式执行。这意味着我们应该创建一个RMTable的真实实例,而不是模拟整个实例。我们只需要模拟cal_sync_column方法所依赖的外部函数,即feature_flag和get_sync_column。
修改后的测试代码如下:
from unittest.mock import patch, MagicMock
from my_module import RMTable, feature_flag, get_sync_column # 确保导入了真实的RMTable
def test_sync_column_correct_approach():
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 = RMTable()
# 为被cal_sync_column内部调用的mock函数设置返回值
mock_sync_column.return_value = "FLAG_1"
# 调用RMTable真实实例上的cal_sync_column方法
result = rm_table.cal_sync_column()
assert result == "FLAG_1"
mock_sync_column.assert_called_once() # 断言get_sync_column被调用
print("Test passed: get_sync_column was called once and returned 'FLAG_1'")
# 示例运行(如果 my_module 存在并包含上述定义)
if __name__ == '__main__':
# 为了让这个示例在没有真实my_module文件的情况下运行,我们重新定义RMTable和相关函数
# 在实际项目中,你只需从my_module导入即可
def feature_flag():
return False
def get_sync_column():
return "default_sync_column"
@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()
# 将函数和类放入一个临时的“模块”命名空间中,以便patch能找到它们
import sys
sys.modules['my_module'] = sys.modules[__name__] # 模拟当前文件是my_module
test_sync_column_correct_approach()实例化真实类:
为内部调用的函数设置返回值:
调用真实实例的方法:
通过以上调整,我们能够正确地测试RMTable类中cal_sync_column方法在else条件分支下对get_sync_column()的调用,从而验证代码的预期行为。
以上就是Python单元测试:正确Mock类方法中条件分支的内部函数调用的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号