捕获单元测试传递的消息:Django后端测试问题排查与修复
问题1:装饰test方法时出现TypeError的原因及修复
错误原因
你遇到的TypeError本质是装饰器包装测试方法后,破坏了unittest对实例方法的绑定调用逻辑:
- 断言方法(如
assertEqual)是通过实例调用的(self.assertEqual(...)),调用时会主动把self作为第一个参数传入装饰器的wrapper,所以之前的代码能正常工作。 - 而unittest框架调用测试方法(如
test_division)时,是直接调用实例的方法(instance.test_division()),要求方法必须是绑定到实例的方法。但你的装饰器返回的是一个普通函数,而非绑定方法,框架调用时不会自动传入self,导致wrapper缺少必要的self参数报错。
另外,你给测试方法定义msg参数的做法本身不符合unittest规范:unittest要求测试方法只能接受self作为唯一参数,框架不会传递额外参数,即便你设置了默认值,也属于不规范写法,可能引发其他问题。
修复方案
我们的目标是给每个测试方法设置默认失败消息,当断言未指定msg时自动使用该消息。可以通过以下步骤实现:
步骤1:修改装饰器,兼容实例方法绑定逻辑
使用functools.wraps保留原方法的绑定特性,同时在测试方法执行时存储默认消息,断言方法优先使用传入的msg,否则用测试方法的默认消息:
import unittest import functools def save_custom_message(func): @functools.wraps(func) def wrapper(*args, **kwargs): if not args: return func(*args, **kwargs) self = args[0] # 处理测试方法:存储默认消息到实例 if func.__name__.startswith('test'): # 从测试方法的自定义属性获取默认消息,没有则用类的默认值 self._default_test_msg = getattr(func, 'default_msg', CustomTestCase.default_message) # 处理断言方法:替换msg为传入值或测试方法的默认消息 elif func.__name__.startswith('assert'): kwargs['msg'] = kwargs.get('msg', getattr(self, '_default_test_msg', CustomTestCase.default_message)) self.msg = kwargs['msg'] return func(*args, **kwargs) return wrapper
步骤2:修改CustomTestCase的__getattribute__
class CustomTestCase(unittest.TestCase): default_message = "The test case failed" def __getattribute__(self, name): attr = super().__getattribute__(name) if (name.startswith("assert") or name.startswith("test")) and callable(attr): return save_custom_message(attr) return attr
步骤3:给测试方法添加默认消息(通过自定义属性)
不再给测试方法加额外参数,而是通过方法的自定义属性设置默认消息:
class MyTests(CustomTestCase): def test_division(self): self.assertEqual(10 / 2, 5) # 这个断言会用测试方法的默认消息 self.assertEqual(10 / 2, 3) # 这个断言会用自己传入的msg self.assertEqual(10 / 5, 3, msg="Third division assert failed") # 给测试方法设置默认消息 MyTests.test_division.default_msg = "Something is wrong with division"
问题2:assertEqual调用时args只输出3的原因
这是因为你误解了unittest.TestCase.assertEqual的参数结构和装饰器中*args的收集逻辑:
assertEqual的签名是def assertEqual(self, first, second, msg=None):其中first和second是位置参数,msg是可选关键字参数。- 当你调用
self.assertEqual(10 / 2, 3, msg="Second division assert")时,10/2(即5)是第一个位置参数(first),3是第二个位置参数(second),msg是关键字参数。 - 在你的装饰器
wrapper(self, *args, **kwargs)中,args会收集除self外的所有位置参数,也就是(5, 3),kwargs收集关键字参数{'msg': "Second division assert"}。
你看到只输出3,大概率是代码中错误地只打印了args[-1](即第二个位置参数),而非完整的args。如果确实只输出3,检查装饰器的参数定义是否误写为wrapper(self, arg, *args, **kwargs),这种情况下arg会接收5,args会接收(3,)。
内容的提问来源于stack exchange,提问作者AlwaysLearning
相关产品推荐
相关产品推荐

