如何编写单元测试,使每个测试用例有不同输入但执行相同?
我需要为一个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 个回答
在我看来,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
就会针对列表中的每一个元素被调用一次。
我们这样做是为了在 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
的方法来进行你的(集成?)测试,否则你可能会发现用其他方法会让你的生活更简单。不过,这种方法是可行的,我们对它的结果非常满意(大多数情况下是快速运行大量的集成/回归测试!-)。
你描述的测试方式和一般的单元测试有点不太一样。通常,单元测试不会从外部文件加载测试数据或预期结果。一般来说,这些数据都是直接写死在单元测试里的。
这并不是说你的计划行不通,只是说这种做法比较少见。
你有两个选择。
(我们的方法)写一个小脚本,做“加载测试用例 i 的输入”和“加载测试用例 i 的预期结果”。用这个脚本生成所需的单元测试代码。(我们使用Jinja2 模板从源文件写 Python 代码。)
然后删除源文件。没错,删除它们。它们只会让你困惑。
这样你就剩下了符合“典型”格式的单元测试文件,里面有测试用例的静态数据和预期结果。
写你的
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'
这将允许单元测试的主程序找到并运行你的测试。