动态地将测试添加到我的测试用例

时间:2014-09-15 21:01:06

标签: python python-unittest

我希望在我的测试用例中添加多个测试,而不是执行以下操作:

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做出哪些改进?我怎么能让它来处理命名参数?

2 个答案:

答案 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):
    ...