pytest中monkey-patch外部API函数报错AttributeError的问题排查
我正在使用pytest为以下函数编写测试用例:
def function_to_testcase(conn, event, customer): if customer["userStatus"] is None: raise IncompleteSignup("Customer marked as Incomplete Signup") logger.info("Forgot password flow started") send_update_customer_details_message( db_id=customer["cid"], zu_id=customer["AccountId"], email=customer["UserName"], encrypted_password=customer["Password"], skip_targets=True, )
需要对外部API调用函数send_update_customer_details_message进行monkey-patch,编写的测试用例如下:
import pytest import unittest from customers_migration.flows.legacy import ( run_legacy_forgot_password_flow, ) class TestLegacy(unittest.TestCase): monkeypatch = pytest.MonkeyPatch() MSG = "Forgot password flow for Legacy started" event_trigger_other = { "request": {"password": "test_pwd"}, "triggerSource": "Other", } customer_status = { "userStatus": 2, "Password": "Test1ngpa$", "cid": 1, "AccountId": "12312", "UserName": "test@test.com", } def test_run_forgot_password_flow_logger(self): with self.assertLogs() as captured: self.monkeypatch.setattr( run_legacy_forgot_password_flow, "send_update_customer_details_message", None, ) run_ipv_legacy_forgot_password_flow( "", self.event_trigger_other, self.customer_status ) self.assertEqual(len(captured.records), 1) self.assertEqual( self.MSG, captured.records[0].getMessage(), )
运行时报错:
AttributeError: <function run_legacy_forgot_password_flow at 0x7fc75f780280> has no attribute 'send_update_customer_details_message'
核心错误原因
你错误地尝试给函数对象run_legacy_forgot_password_flow添加属性send_update_customer_details_message,但该函数本身并不具备这个属性。Monkey-patch的正确目标应该是send_update_customer_details_message被导入的模块,而非调用它的函数。
具体修正步骤
修正Monkey-patch的目标
找到send_update_customer_details_message所在的模块(假设它在customers_migration.flows.legacy中,或被导入到该模块),将setattr的第一个参数改为对应模块:# 先导入目标模块 from customers_migration.flows import legacy # 测试中修改patch目标 self.monkeypatch.setattr( legacy, # 目标模块,而非函数 "send_update_customer_details_message", lambda *args, **kwargs: None # 用空lambda替代原函数,避免执行实际API调用 )注意:如果
send_update_customer_details_message是从其他模块导入到legacy的,仍然需要patchlegacy模块中的这个引用,而非原模块。修正函数调用笔误
测试中调用的run_ipv_legacy_forgot_password_flow与导入的run_legacy_forgot_password_flow名称不符,需改为正确的函数名:run_legacy_forgot_password_flow( "", self.event_trigger_other, self.customer_status )统一日志断言内容
被测试函数输出的日志是"Forgot password flow started",但测试中定义的MSG是"Forgot password flow for Legacy started",两者不匹配会导致断言失败,需统一日志字符串。优化Monkey-patch使用方式
直接在类中定义monkeypatch = pytest.MonkeyPatch()不是最佳实践,建议在测试方法内创建实例并手动撤销,或使用pytest的monkeypatchfixture:def test_run_forgot_password_flow_logger(self): monkeypatch = pytest.MonkeyPatch() try: monkeypatch.setattr(legacy, "send_update_customer_details_message", lambda *args, **kwargs: None) with self.assertLogs() as captured: run_legacy_forgot_password_flow("", self.event_trigger_other, self.customer_status) # 执行断言逻辑 finally: monkeypatch.undo() # 确保patch被撤销,不影响其他测试
内容的提问来源于stack exchange,提问作者Prinz Piuz

