【问题标题】:How to write a unit-test where each test case has different input but does the same?如何编写一个单元测试,其中每个测试用例都有不同的输入但做的相同?
【发布时间】:2010-02-03 23:44:39
【问题描述】:

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

这是我想做的伪代码:

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 包来实现这一点,还是有一些更好的测试包用于此目的?

【问题讨论】:

标签: python unit-testing


【解决方案1】:

您描述测试的方式与一般的单元测试有点奇怪。单元测试通常不会从外部文件加载测试数据或剩余结果。通常,它只是在单元测试中硬编码。

这并不是说您的计划行不通。只能说不典型。

你有两个选择。

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

    然后删除源文件。是的,删除它们。它们只会让你感到困惑。

    您剩下的是“典型”形式的适当 Unittest 文件,其中包含测试用例和预期结果的静态数据。

  2. 编写setUp 方法来执行“为测试用例 i 加载输入”和“为测试用例 i 加载预期结果”。编写您的 test 方法来练习 UUT。

它可能看起来像这样。

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'

这将允许 unittest 主程序找到并运行您的测试。

【讨论】:

  • 谢谢。我想我会选择选项 1。Jinja2 看起来很有趣。他们使用什么许可证?
  • @zr:“他们使用什么许可证?”为什么问我? PyPi 页面是这样写的:pypi.python.org/pypi/Jinja2.
【解决方案2】:

我们这样做是为了在unittest 框架内运行真正的集成(回归)测试(实际上是内部定制,它给我们带来了巨大的好处,例如运行在一组机器上并行测试等等——这种定制的巨大附加值是我们如此热衷于使用unittest 框架的原因。

每个测试都在一个文件中表示(该测试中使用的参数,后跟预期结果)。我们的 integration_test 从一个目录中读取所有这些文件,解析它们,然后调用:

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 的方法进行(集成?)测试,否则您可能会发现使用不同的方法可以让您的生活变得更简单。然而,这个非常可行,我们对它的结果非常满意(其中主要包括快速运行大型集成/回归测试套件!-)。

【讨论】:

  • 我发现自己使用“单元测试”来表示“我编写的使用 unittest 或 nUnit 运行的测试”,即使它们通常根本不是单元测试。
【解决方案3】:

在我看来,pytest 正是您需要的东西。

您可以parametrise tests 以便在您有输入的情况下运行相同的测试多次,并且只需要一个装饰器(没有循环等)。

这是一个简单的例子:

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

这里的 parametrise 有两个参数 - 参数的名称作为一个字符串,这些参数的值作为一个迭代。

test_eval 将为列表的每个元素调用一次。

【讨论】:

    【解决方案4】:

    也许您可以为此使用doctest。了解您的输入和输出(并能够将案例编号映射到函数名称),您应该能够生成如下文本文件:

    >>> from XXX import function_name1
    >>> function_name1(input1)
    output1
    >>> from XXX import function_name2
    >>> function_name2(input2)
    output2
    ...
    

    然后只需使用doctest.testfile('cases.txt')。值得一试。

    【讨论】:

      【解决方案5】:

      您可能还想看看my answerthis question。我再次尝试进行回归测试而不是单元测试本身,但 unittest 框架对两者都有好处。

      在我的例子中,我有大约十几个输入文件,涵盖了相当多的不同用例,并且我有大约六个我想要调用的测试函数。

      我没有编写 72 个不同的测试,其中除了输入参数和结果数据之外大部分是相同的,而是创建了一个结果字典(键是输入参数,值是每个被测函数的结果字典)。然后,我编写了一个 TestCase 类来测试 6 个函数中的每一个,并通过多次将 TestCase 添加到测试套件中来复制 12 个测试文件。

      【讨论】:

        猜你喜欢
        • 2019-09-17
        • 2014-06-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-03-23
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多