如何编写单元测试,使每个测试用例有不同输入但执行相同?

6 投票
5 回答
4468 浏览
提问于 2025-04-15 18:52

我需要为一个Python类创建单元测试。我有一个输入和预期结果的数据库,这些结果应该是被测试的类根据这些输入生成的。

以下是我想要做的伪代码:

for i=1 to NUM_TEST_CASES:
    Load input for test case i
    execute UUT on the input and save output of run
    Load expected result for test case i
    Compare output of run with the expected result

我可以使用unittest这个包来实现吗?还是说有更好的测试包可以用来做这个?

5 个回答

1

在我看来,pytest正好是你需要的工具。

你可以通过参数化测试,让同一个测试根据不同的输入运行多次,这只需要一个装饰器(不需要用到循环等复杂的东西)。

这里有个简单的例子:

import pytest
@pytest.mark.parametrize("test_input,expected", [
    ("3+5", 8),
    ("2+4", 6),
    ("6*9", 42),
])
def test_eval(test_input, expected):
    assert eval(test_input) == expected

在这个例子中,参数化函数接收两个参数 - 一个是参数的名字(以字符串形式),另一个是这些参数的值(以可迭代的形式)。

这样,test_eval就会针对列表中的每一个元素被调用一次。

3

我们这样做是为了在 unittest 框架中运行实际上是集成(回归)测试(其实是我们内部定制的版本,这样做给我们带来了很多好处,比如可以在一组机器上并行运行测试等等——这项定制的巨大附加价值就是我们如此热衷于使用 unittest 框架的原因)。

每个测试都在一个文件中表示(文件里包含测试所需的参数,以及预期的结果)。我们的集成测试会从一个目录中读取所有这些文件,解析每个文件,然后调用:

def addtestmethod(testcase, uut, testname, parameters, expresults):
  def testmethod(self):
    results = uut(parameters)
    self.assertEqual(expresults, results)
  testmethod.__name__ = testname
  setattr(testcase, testname, testmethod)

我们从一个空的测试用例类开始:

class IntegrationTest(unittest.TestCase): pass

然后在一个循环中调用 addtestmethod(IntegrationTest, ...,在这个循环里我们读取所有相关的文件并解析它们,以获取测试名称、参数和预期结果。

最后,我们调用我们内部专门的测试运行器,它负责处理繁重的工作(比如在集群中的可用机器上分配测试、收集结果等等)。我们不想重新发明这个高价值的轮子,所以我们创建了一个尽可能接近典型“手动编码”测试的测试用例,以“欺骗”测试运行器,让它为我们正常工作;-)。

除非你有特定的理由(比如好的测试运行器等)去使用 unittest 的方法来进行你的(集成?)测试,否则你可能会发现用其他方法会让你的生活更简单。不过,这种方法是可行的,我们对它的结果非常满意(大多数情况下是快速运行大量的集成/回归测试!-)。

4

你描述的测试方式和一般的单元测试有点不太一样。通常,单元测试不会从外部文件加载测试数据或预期结果。一般来说,这些数据都是直接写死在单元测试里的。

这并不是说你的计划行不通,只是说这种做法比较少见。

你有两个选择。

  1. (我们的方法)写一个小脚本,做“加载测试用例 i 的输入”和“加载测试用例 i 的预期结果”。用这个脚本生成所需的单元测试代码。(我们使用Jinja2 模板从源文件写 Python 代码。)

    然后删除源文件。没错,删除它们。它们只会让你困惑。

    这样你就剩下了符合“典型”格式的单元测试文件,里面有测试用例的静态数据和预期结果。

  2. 写你的setUp方法来做“加载测试用例 i 的输入”和“加载测试用例 i 的预期结果”。然后写你的test方法来测试被测单元。

它可能看起来像这样。

class OurTest( unittest.TestCase ):
    def setUp( self ):
        self.load_data()
        self.load_results()
        self.uut = ... UUT ...
    def runTest( self ):
        ... exercise UUT with source data ...
        ... check results, using self.assertXXX methods ...

想要多次运行这个吗?一种方法是这样做。

class Test1( OurTest ):
    source_file = 'this'
    result_file = 'that'

class Test2( OutTest ):
    source_file= 'foo'
    result_file= 'bar'

这将允许单元测试的主程序找到并运行你的测试。

撰写回答