我希望在我的测试用例中添加多个测试,而不是执行以下操作:
class TestRestart(unittest.TestCase):
...
def test_modes(self):
for mode in modes:
machine.set_mode(mode)
self.assertTrue(machine.restart())
在我的许多测试中,我希望在自己的函数中测试每个模式。我现在不能使用像鼻子这样的库。我以为我可以使用下面的add_tests
方法来帮助我这样做:
import unittest
def build_name_1(a):
return "test " + str(a[0]) + "_" + str(a[1])
def build_name_2(a):
return "test " + str(a[0]) + "_" + str(a[1]) + "_" + str(a[2])
def add_tests(cls, args, name_builder):
for a in args:
def tb(a):
return lambda self: self.test_body(*a)
setattr(cls, name_builder(a), tb(a))
class TestEqual(unittest.TestCase):
def test_body(self, i, j):
self.assertNotEquals(0, i-j)
args = ((0,0),(1,1))
add_tests(TestEqual, args, build_name_1)
class TestBetween(unittest.TestCase):
def test_body(self, i, j, k):
self.assertTrue(i<j<k)
args = ((2,1,2),(2,2,3))
add_tests(TestBetween, args, build_name_2)
if __name__ == '__main__':
unittest.main()
是否可以将add_tests
调用移到TestEqual和TestBetween类中,而不是像上面那样将它们放在类之外?这不是更好吗?
我可以对add_tests
做出哪些改进?我怎么能让它来处理命名参数?
答案 0 :(得分:1)
import unittest
def outer(i, j):
"""Saves i and j in the inner scope, will be passed an
instance of Foo on call to inner.
"""
def inner(inst):
inst.assertNotEqual(i, j)
return inner
class Meta(type):
"""Builds test functions before unittest does its discovery.
"""
def __init__(self, *args, **kwargs):
for i, j in self.tests:
setattr(self, self.namebuilder(i, j), outer(i, j))
super().__init__(*args, **kwargs)
class Foo(unittest.TestCase, metaclass=Meta):
def namebuilder(*args):
return "test_{}_{}".format(*args)
tests = [(i, j) for i in range(10) for j in range(i+1, 10)]
class Bar(unittest.TestCase, metaclass=Meta):
def namebuilder(*args):
return "test_labeled_differently_{}_{}".format(*args)
tests= [(i, i) for i in range(10)]
if __name__ == '__main__':
unittest.main()
以下是使用元类进行此操作的一种方法。 unittest
在您的类实例化之前发现测试函数。将类视为实例构建器,将元类视为类构建器。元类将创建所有测试函数,因此当loader.py https://hg.python.org/cpython/file/322ee2f2e922/Lib/unittest/loader.py中的默认unittest加载器调用函数getTestCaseNames时,它将找到动态创建的测试。我已经在loader.py中包含了这个函数,因此您可以自己查看它是如何工作的。
def getTestCaseNames(self, testCaseClass):
"""Return a sorted sequence of method names found within testCaseClass
"""
def isTestMethod(attrname, testCaseClass=testCaseClass,
prefix=self.testMethodPrefix):
return attrname.startswith(prefix) and \
callable(getattr(testCaseClass, attrname))
testFnNames = list(filter(isTestMethod, dir(testCaseClass)))
if self.sortTestMethodsUsing:
testFnNames.sort(key=functools.cmp_to_key(self.sortTestMethodsUsing))
return testFnNames
答案 1 :(得分:1)
你可以使你的add_tests
函数成为类装饰器,这样它就可以在类创建时调用,而不是单独调用。如果你不介意深层嵌套的函数,你也可以传入各种参数。
这是一个与你现在所做的相同的版本,只是装饰工厂形式:
def add_tests(args, name_builder):
def decorator(cls):
for a in args:
def tb(a):
return lambda self: self.test_body(*a)
setattr(cls, name_builder(a), tb(a))
return cls
你打电话给:
@add_tests(args, build_name1)
class TestEquals(unittest.TestCase):
...
除了kwargs
元组的序列之外,您还可以通过采用args
序列序列来处理关键字参数。然后你只需将它们传递给测试函数,该函数可以作为可调用函数传递,或者如果你正在按名称处理方法(因为在类绑定到它的名字之前调用类装饰器)。试试这个(但请注意name_builder
函数会传递一个额外的参数,因此需要更新它以便它也可以处理kwargs!):
def add_tests(args_sequence, kwargs_sequence, name_builder, test_func=None):
def decorator(cls):
if test_func is None:
test_func = cls.test_body # fall back on your current behavior
elif isinstance(test_func, str):
test_func = getattr(cls, test_func)
for args, kwargs in zip(args_sequence, kwargs_sequence):
def tb(a, kw):
return lambda self: test_func(self, *a, **kw)
setattr(cls, name_builder(args, kwargs), tb(args, kwargs))
return cls
然后你可以用:
做最后的例子@add_tests([(mode.SILENT,), (mode.NOISY,)], [{}, {}], build_name_mode, "test_mode_flow")
@add_tests([(1,), ()], [{"speed":33}, {"speed":22, "gear":4}], # can mix args and kwargs
build_gear_mode, "test_gear_flow")
class TestRestart(unittest.TestCase):
...