单元测试方法上的装饰器工厂
Decorator factory on a unittest method
def register_processor2(processor_name='SomeProcessor'):
def decorator(func):
class SomeProcessor(GenericPaymentProcessor, TriggeredProcessorMixin):
name = processor_name
transaction_class = Transaction
@staticmethod
def setup(data=None):
pass
@wraps(func)
def func_wrapper(*args, **kwargs):
PaymentProcessorManager.register(SomeProcessor)
result = func(*args, **kwargs)
PaymentProcessorManager.unregister(SomeProcessor)
return result
return func_wrapper
return decorator
def register_processor(func):
class SomeProcessor(GenericPaymentProcessor, TriggeredProcessorMixin):
name = 'SomeProcessor'
transaction_class = Transaction
@staticmethod
def setup(data=None):
pass
@wraps(func)
def func_wrapper(*args, **kwargs):
PaymentProcessorManager.register(SomeProcessor)
result = func(*args, **kwargs)
PaymentProcessorManager.unregister(SomeProcessor)
return result
return func_wrapper
class TestPaymentMethodEndpoints(APITestCase):
@register_processor
def test_put_detail_cannot_change_processor(self):
self.assertEqual(True, False)
好的,装饰器 register_processor
按预期工作。测试失败了,但我想使内部 class 的名称可自定义,所以我改为使用装饰器工厂实现。
事情是当 运行 用 register_processor2
修饰的测试时,我得到以下信息:
AttributeError: 'TestPaymentMethodEndpoints' object has no attribute '__name__'
这是来自@wraps(func)
,我的问题是为什么func
这里是TestPaymentMethodEndpoints
的实例,而不是绑定方法?
此外,如果我删除 @wraps
装饰器,那么 测试会运行并通过 。
我希望测试不会被发现,因为 func_wrapper
不是以 test_*
开头的,即使它被发现,它也应该失败。
对正在发生的事情以及我将如何着手做这件事有任何见解吗?
编辑
所以我想通了,即使装饰器工厂的参数有默认值,你仍然需要在调用它时放置 ()
。
但仍然希望听到有关在测试通过/首先被发现的情况下发生的情况的解释。
class TestPaymentMethodEndpoints(APITestCase):
@register_processor()
def test_put_detail_cannot_change_processor(self):
self.assertEqual(True, False)
现在想想很有道理 :D,天哪,你每天都能学到新东西!
我想你现在问的是 "how come the unittest
module can find test cases that have been wrapped in functions with names that don't start test
?"
答案是因为 unittest
不使用函数的 名称 来查找 运行 的方法,它使用 测试用例的属性名称 class可以找到它们。
所以请尝试 运行以下代码:
from unittest import TestCase
def apply_fixture(func):
def wrap_with_fixture(self):
print('setting up fixture...')
try:
func(self)
finally:
print('tearing down fixture')
return wrap_with_fixture
class MyTestCase(TestCase):
@apply_fixture
def test_something(self):
print('run test')
print('Attributes of MyTestCase: %s' % dir(MyTestCase))
print('test_something method: %s' % MyTestCase.test_something)
mtc = MyTestCase()
mtc.test_something()
您会看到 dir
的输出包含名称 test_something
:
Attributes of MyTestCase: ['__call__', ...lots of things..., 'test_something']
但是该属性的值是包装函数 wrap_with_fixture
:
test_something method: <function apply_fixture.<locals>.wrap_with_fixture at 0x10d90aea0>
当您考虑到当您创建一个函数时,您同时创建了一个具有所提供名称的函数和一个具有相同名称的局部变量,并且装饰器 @
语法只是语法糖,这是有道理的,因此以下内容同样有效,尽管创建测试用例的方式比较冗长 class:
class MyTestCase(TestCase):
def test_something(self):
print('run test')
# Overwrite existing 'local' (or 'class' variable in this context)
# with a new value. We haven't deleted the test_something function
# which still exists but now is owned by the function we've created.
test_something = apply_fixture(test_something)
def register_processor2(processor_name='SomeProcessor'):
def decorator(func):
class SomeProcessor(GenericPaymentProcessor, TriggeredProcessorMixin):
name = processor_name
transaction_class = Transaction
@staticmethod
def setup(data=None):
pass
@wraps(func)
def func_wrapper(*args, **kwargs):
PaymentProcessorManager.register(SomeProcessor)
result = func(*args, **kwargs)
PaymentProcessorManager.unregister(SomeProcessor)
return result
return func_wrapper
return decorator
def register_processor(func):
class SomeProcessor(GenericPaymentProcessor, TriggeredProcessorMixin):
name = 'SomeProcessor'
transaction_class = Transaction
@staticmethod
def setup(data=None):
pass
@wraps(func)
def func_wrapper(*args, **kwargs):
PaymentProcessorManager.register(SomeProcessor)
result = func(*args, **kwargs)
PaymentProcessorManager.unregister(SomeProcessor)
return result
return func_wrapper
class TestPaymentMethodEndpoints(APITestCase):
@register_processor
def test_put_detail_cannot_change_processor(self):
self.assertEqual(True, False)
好的,装饰器 register_processor
按预期工作。测试失败了,但我想使内部 class 的名称可自定义,所以我改为使用装饰器工厂实现。
事情是当 运行 用 register_processor2
修饰的测试时,我得到以下信息:
AttributeError: 'TestPaymentMethodEndpoints' object has no attribute '__name__'
这是来自@wraps(func)
,我的问题是为什么func
这里是TestPaymentMethodEndpoints
的实例,而不是绑定方法?
此外,如果我删除 @wraps
装饰器,那么 测试会运行并通过 。
我希望测试不会被发现,因为 func_wrapper
不是以 test_*
开头的,即使它被发现,它也应该失败。
对正在发生的事情以及我将如何着手做这件事有任何见解吗?
编辑
所以我想通了,即使装饰器工厂的参数有默认值,你仍然需要在调用它时放置 ()
。
但仍然希望听到有关在测试通过/首先被发现的情况下发生的情况的解释。
class TestPaymentMethodEndpoints(APITestCase):
@register_processor()
def test_put_detail_cannot_change_processor(self):
self.assertEqual(True, False)
现在想想很有道理 :D,天哪,你每天都能学到新东西!
我想你现在问的是 "how come the unittest
module can find test cases that have been wrapped in functions with names that don't start test
?"
答案是因为 unittest
不使用函数的 名称 来查找 运行 的方法,它使用 测试用例的属性名称 class可以找到它们。
所以请尝试 运行以下代码:
from unittest import TestCase
def apply_fixture(func):
def wrap_with_fixture(self):
print('setting up fixture...')
try:
func(self)
finally:
print('tearing down fixture')
return wrap_with_fixture
class MyTestCase(TestCase):
@apply_fixture
def test_something(self):
print('run test')
print('Attributes of MyTestCase: %s' % dir(MyTestCase))
print('test_something method: %s' % MyTestCase.test_something)
mtc = MyTestCase()
mtc.test_something()
您会看到 dir
的输出包含名称 test_something
:
Attributes of MyTestCase: ['__call__', ...lots of things..., 'test_something']
但是该属性的值是包装函数 wrap_with_fixture
:
test_something method: <function apply_fixture.<locals>.wrap_with_fixture at 0x10d90aea0>
当您考虑到当您创建一个函数时,您同时创建了一个具有所提供名称的函数和一个具有相同名称的局部变量,并且装饰器 @
语法只是语法糖,这是有道理的,因此以下内容同样有效,尽管创建测试用例的方式比较冗长 class:
class MyTestCase(TestCase):
def test_something(self):
print('run test')
# Overwrite existing 'local' (or 'class' variable in this context)
# with a new value. We haven't deleted the test_something function
# which still exists but now is owned by the function we've created.
test_something = apply_fixture(test_something)