You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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被导入的模块,而非调用它的函数。

具体修正步骤

  1. 修正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模块中的这个引用,而非原模块。

  2. 修正函数调用笔误
    测试中调用的run_ipv_legacy_forgot_password_flow与导入的run_legacy_forgot_password_flow名称不符,需改为正确的函数名:

    run_legacy_forgot_password_flow(
        "", self.event_trigger_other, self.customer_status
    )
    
  3. 统一日志断言内容
    被测试函数输出的日志是"Forgot password flow started",但测试中定义的MSG是"Forgot password flow for Legacy started",两者不匹配会导致断言失败,需统一日志字符串。

  4. 优化Monkey-patch使用方式
    直接在类中定义monkeypatch = pytest.MonkeyPatch()不是最佳实践,建议在测试方法内创建实例并手动撤销,或使用pytest的monkeypatch fixture:

    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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 16:57:38