【发布时间】:2012-09-24 07:23:03
【问题描述】:
单元测试 run 函数的最简单方法是什么?这里我对 fun* 函数的作用不感兴趣,但前提是它们的调用顺序正确。
from mock import Mock
(many, arguments, four, fun) = ('many', 'arguments', 'four', 'fun')
class TemplateMethod(object):
def fun1(self, many, parameters, go, here):
raise NotImplementedError()
@classmethod
def fun2(cls, many, parameters, go, here):
pass
@staticmethod
def fun3(many, parameters, go, here):
pass
def run(self):
result1 = self.fun1(many, arguments, four, fun)
result1.do_something()
self.fun2(many, arguments, four, fun)
self.fun3(many, arguments, four, fun)
解决方案的唯一要求是对被测类的非侵入性。
解决方案:
实际上,这有点像草稿,这个简单的类可以通过多种方式进行修复和扩展,我什至都不想考虑它。关键是您现在可以简单地在模板方法中记录所有方法的所有调用。您还可以指定不应被该类模拟的对象列表(即您正在为其记录调用的函数)。
特别感谢@Damian Schenkelman,他给了我一些重要的建议。
class MockingInvocationRecorder(object):
FUNCTION_TYPES = ('function', 'classmethod', 'staticmethod')
def __init__(self, obj, dont_mock_list):
self._invocation_list = []
name_list = [exc.__name__ for exc in dont_mock_list]
self._wrap_memfuns(obj, name_list)
@property
def invocations(self):
return tuple(self._invocation_list)
def _wrap_single(self, memfun, exclude_list):
def wrapper(*args, **kwargs):
self._invocation_list.append(memfun.__name__)
if memfun.__name__ in exclude_list:
return memfun(*args, **kwargs)
else:
return Mock()
return wrapper
def _wrap_memfuns(self, obj, exclude_list):
for (mem_name, mem) in type(obj).__dict__.iteritems():
if type(mem).__name__ in self.FUNCTION_TYPES:
wrapper = self._wrap_single(getattr(obj, mem_name), exclude_list)
setattr(obj, mem_name, wrapper)
您现在可以通过以下方式测试调用顺序:
tm = TemplateMethod()
ir = MockingInvocationRecorder(tm, [tm.run])
tm.run()
print ir.invocations => ('run', 'fun1', 'fun2', 'fun3')
您可以通过类名引用成员:
ir = MockingInvocationRecorder(tm, [TemplateMethod.run])
请注意您列出了所有不应被嘲笑的方法。
【问题讨论】:
-
这让我觉得这是一个可疑的设计。为什么需要检查函数的顺序是否正确?如果以后对程序进行某些更改需要以不同的顺序执行函数怎么办?您必须基本上将更改复制到单元测试。作为测试的一部分,按照函数调用的顺序进行硬编码似乎违反了不要重复自己原则。必须有一种更好、更抽象的方法来测试
run的输出,而不用关心它是如何获得输出的,不是吗? -
关键是要确保模板方法中的算法步骤实际上是按特定顺序执行的。在我的实际代码中,我首先执行身份验证和授权。至于设计,这只是一个 POC。我不喜欢拥有 call_appender 类的想法,但由于我是 Python 的新手,我看不到任何更简单的解决方案。我可以通过反射扩展当前的解决方案,但我有点希望有人会提出更好的设计。 (我不是要求完整的解决方案,只是想法。)
标签: python unit-testing template-method-pattern