unittest测试用例中的重复代码

时间:2012-09-11 02:40:39

标签: python unit-testing python-3.x refactoring

我有一个看起来像这样的测试用例:

def MyTestCase(unittest.Testcase):
  def test_input01(self):
    input = read_from_disk('input01')
    output = run(input)
    validated_output = read_from_disk('output01')
    self.assertEquals(output, validated_output)
  def test_input02(self):
    input = read_from_disk('input02')
    # ...
  # and so on, for 30 inputs, from input01 to input30

现在,我知道测试代码可能有点重复,因为简单性比简洁性更重要。但是这变得非常容易出错,因为当我决定更改这里使用的某些功能的签名时,我不得不在所有30个地方进行更改。

我可以将它重构为已知输入的循环,但我确实希望每个输入都保持单独的测试,所以我认为我应该使用test_inputxx方法。

我做错了什么?

4 个答案:

答案 0 :(得分:12)

编写辅助函数以从测试用例中删除重复:

def MyTestCase(unittest.Testcase):
  def run_input_output(self, suffix):
    input = read_from_disk('input'+suffix)
    output = run(input)
    validated_output = read_from_disk('output'+suffix)
    self.assertEquals(output, validated_output)

  def test_input01(self): self.run_input_output('01')
  def test_input02(self): self.run_input_output('02')
  def test_input03(self): self.run_input_output('03')

答案 1 :(得分:2)

我喜欢Ned Batchelder的解决方案。但是对于子孙后代,如果您经常更改输入数量,则可以执行以下操作:

def MyTestCase(unittest.Testcase):
    def __init__(self, *args, **kwargs):
        for i in range(1,31):
            def test(self, suffix=i):
                input = read_from_disk('input%02d' % suffix)
                output = run(input)
                validated_output = read_from_disk('output%02d' % suffix)
                self.assertEquals(output, validated_output)
            setattr(self, 'test_input%02d' % i) = test
        super(MyTestCase, self).__init__(*args, **kwargs)

答案 2 :(得分:1)

这样的事情如何,以便报告输入失败。

def MyTestCase(unittest.Testcase):
  def test_input01(self):
    for i in range(1,30):
      input = read_from_disk('input%.2d' %i)
      output = run(input)
      validated_output = read_from_disk('output%.2d' %i)
      self.assertEquals(output, validated_output, 'failed on test case %.2d' %i)

答案 3 :(得分:1)

我最喜欢的这种测试工具是参数化测试用例,如下所示:

from nose_parameterized import parameterized

class MyTestCase(unittest.TestCase):
    @parameterized.expand([(1,), (2,), (3,)])
    def test_read_from_disk(self, file_number):
        input = read_from_disk('input%02d' % file_number)
        expected = read_from_disk('output%02d' % file_number)

        actual = run(input)
        self.assertEquals(expected, actual)

编写测试用例以获取所需的任何参数,将参数化函数包装在@parameterized.expand装饰器中,并在expand()调用中提供输入参数集。然后,测试运行器为每组参数运行一个单独的测试!

在这种情况下,只有一个参数,因此expand()调用具有不幸的额外嵌套级别,但是当您的用例稍微复杂并且您使用时,该模式会变得特别好param对象为你的测试函数提供args和kwargs:

from nose_parameterized import parameterized, param

class MyTestCase(unittest.TestCase):
    @parameterized.expand([
        param(english='father', spanish='padre'),
        param(english='taco', spanish='taco'),
        ('earth', 'tierra'), # A regular tuple still works too, but is less readable
        ...
    ])
    def test_translate_to_spanish(self, english, spanish):
        self.assertEqual(translator(english), spanish)

该模式允许您轻松,清晰地指定多组输入参数,并且只编写一次测试逻辑。

我使用nose进行测试,因此我的示例使用nose-parameterized,但还有unittest-compatible version