8

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

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

4 回答 4

13

编写一个辅助函数来删除测试用例中的重复:

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')
于 2012-09-11T02:52:47.417 回答
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)
于 2012-09-11T03:01:58.237 回答
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)
于 2012-09-11T02:49:18.057 回答
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)

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

I use nose for testing, so my example uses nose-parameterized, but there's also a unittest-compatible version.

于 2014-09-08T22:03:26.907 回答