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

如何在Python项目单元测试中禁用指定装饰器?

如何在Python单元测试中禁用指定装饰器

运行单元测试时想禁用选定的装饰器,但当前代码里的disable_decorator_for未实现正确逻辑,导致测试无法捕获预期的ValueError,测试失败。

问题原因

被@handle_value_error装饰后的func_that_raise_value_error实际是装饰器返回的wrapper函数,它会捕获ValueError并打印日志,不会向外抛出异常,所以测试里的assertRaises断言自然会失败。

解决方案

利用functools.wraps给wrapper函数自动添加的__wrapped__属性(该属性指向被装饰的原函数),我们可以临时把被装饰的函数替换回原函数,测试完成后再恢复,避免影响其他用例。

方案1:实现disable_decorator_for并手动恢复

先修正disable_decorator_for的实现,同时注意测试后的恢复逻辑:

import unittest
import functools
import sys


def handle_value_error(func):
    @functools.wraps(func)
    def wrapper_handle_value_error():
        try:
            return func()
        except ValueError as e:
            print(f"During calling {func.__name__!r} an error was raised: {str(e)}")
    return wrapper_handle_value_error


@handle_value_error
def func_that_raise_value_error():
    raise ValueError


def disable_decorator_for(func, decorator):
    # 获取函数所在的模块
    module = sys.modules[func.__module__]
    # 遍历包装链,找到最原始的函数并替换
    current_func = func
    while hasattr(current_func, '__wrapped__'):
        original_func = current_func.__wrapped__
        setattr(module, func.__name__, original_func)
        current_func = original_func


class TestFunc(unittest.TestCase):
    def test_func_that_raise_value_error(self):
        # 保存原装饰后的函数,用于测试后恢复
        original_wrapped_func = func_that_raise_value_error
        try:
            disable_decorator_for(func=func_that_raise_value_error, decorator=handle_value_error)
            with self.assertRaises(ValueError):
                func_that_raise_value_error()
        finally:
            # 恢复原函数,避免影响其他测试用例
            module = sys.modules[func_that_raise_value_error.__module__]
            setattr(module, func_that_raise_value_error.__name__, original_wrapped_func)

方案2:用unittest.mock.patch临时替换(更简洁)

不需要额外写disable_decorator_for,直接用patch临时替换函数为原函数,测试结束后自动恢复:

import unittest
import functools
from unittest.mock import patch


def handle_value_error(func):
    @functools.wraps(func)
    def wrapper_handle_value_error():
        try:
            return func()
        except ValueError as e:
            print(f"During calling {func.__name__!r} an error was raised: {str(e)}")
    return wrapper_handle_value_error


@handle_value_error
def func_that_raise_value_error():
    raise ValueError


class TestFunc(unittest.TestCase):
    def test_func_that_raise_value_error(self):
        # 获取被装饰的原函数
        original_func = func_that_raise_value_error.__wrapped__
        # 临时替换模块中的函数为原函数
        with patch(f'{__name__}.func_that_raise_value_error', original_func):
            with self.assertRaises(ValueError):
                func_that_raise_value_error()

注意点

  • 原代码中的测试方法里有笔误:func_thar_raise_value_error应该改为func_that_raise_value_error,否则会报未定义错误。
  • 测试后一定要恢复原函数,不然其他测试用例调用该函数时会使用未装饰的版本,导致逻辑错误。

内容的提问来源于stack exchange,提问作者flow_7777

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 02:54:24