【问题标题】:How to test same assertion for large amount of data如何为大量数据测试相同的断言
【发布时间】:2026-02-09 19:40:02
【问题描述】:

我正在使用 python unittest 模块进行一些测试;但是,它非常重复。

我有很多数据想要一遍又一遍地通过相同的测试,检查是否正确。但是,我必须为每个人定义一个测试。

例如,我想做类似的事情。我知道我可以使用生成器来做到这一点(在以前的线程中找到它)。但是是否有替代方案,甚至可以使用不同的测试模块?

任何建议都会很棒。


import unittest

class TestData(unittest.TestCase):
    def testNumbers(self):
        numbers = [0,11,222,33,44,555,6,77,8,9999]
        for i in numbers:
            self.assertEqual(i, 33)

【问题讨论】:

  • 这有什么问题?看起来很棒。
  • 基本上,只要断言为真,它就会停止执行。

标签: python unit-testing python-unittest


【解决方案1】:

从 Python 3.4 开始,您可以使用 unittest.TestCase.subTest(msg=None, **params) 上下文管理器 (documentation)。这将允许您通过仅添加一个语句来实现您想要的。

这是您的示例修改为使用subTest()

import unittest

class TestData(unittest.TestCase):
    def testNumbers(self):
        numbers = [0, 11, 222, 33, 44, 555, 6, 77, 8, 9999]
        for i in numbers:
            with self.subTest(i=i):  # added statement
                self.assertEqual(i, 33)

【讨论】:

  • 这个答案正是 OP(和我)需要最少的重写。
【解决方案2】:

Bill Gribble 建议的解决方案示例代码如下所示:

import unittest

class DataTestCase(unittest.TestCase):
    def __init__(self, number):
        unittest.TestCase.__init__(self, methodName='testOneNumber')
        self.number = number

    def testOneNumber(self):
        self.assertEqual(self.number, 33)

    def shortDescription(self):
        # We need to distinguish between instances of this test case.
        return 'DataTestCase for number %d' % self.number


def get_test_data_suite():
    numbers = [0,11,222,33,44,555,6,77,8,9999]
    return unittest.TestSuite([DataTestCase(n) for n in numbers])

if __name__ == '__main__':
    testRunner = unittest.TextTestRunner()
    testRunner.run(get_test_data_suite())

【讨论】:

    【解决方案3】:

    ddt library 旨在解决您对unittest[*] 的要求。

    例如:

    import ddt
    import unittest
    
    @ddt.ddt
    class EvalTests(unittest.TestCase):
    
        @ddt.data(
                ('1', 1),
                ('1 == 1',  True),
                ('1 == 2',  False),
                ('1 + 2',   4),  ## This will fail
        )
        def test_eval_expressions(self, case):
            expr, exp_value = case
            self.assertEqual(eval(expr), exp_value)
    

    当你运行它时,你会得到 4 个测试用例,而不仅仅是一个:

    $ python -m unittest  -v  test_eval.py
    test_eval_expressions_1___1___1_ (test_eval.EvalTests) ... ok
    test_eval_expressions_2___1__1___True_ (test_eval.EvalTests) ... ok
    test_eval_expressions_3___1__2___False_ (test_eval.EvalTests) ... ok
    test_eval_expressions_4___1_2___4_ (test_eval.EvalTests) ... FAIL
    
    ======================================================================
    FAIL: test_eval_expressions_4___1_2___4_ (test_eval.EvalTests)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "/usr/lib/python/lib/site-packages/ddt.py", line 129, in wrapper
        return func(self, *args, **kwargs)
      File "/Work/test_eval.py", line 15, in test_eval_expressions
        self.assertEqual(eval(expr), exp_value)
    AssertionError: 3 != 4
    
    ----------------------------------------------------------------------
    Ran 4 tests in 0.002s
    
    FAILED (failures=1)
    

    请注意,ddt 会尝试为生成的 TC 提供名称。

    用 pip 安装它:

    pip install ddt
    

    [*] pythonic pytest 框架 (pytest.mark.parametrize) 的相同解决方案已集成到核心工具中,仅此功能就值得切换到 pytest

    【讨论】:

      【解决方案4】:

      在另一篇文章中,我偶然发现 Nose Tests 它更适合数据驱动测试。

      
      class Test_data():
          def testNumbers():
              numbers = [0,11,222,33,44,555,6,77,8,9999]
              for i in numbers:
                  yield checkNumber, num
      
      def checkNumber(num):
          assert num == 33
      

      上面的代码和我的第一篇文章完全一样。 不需要导入,只需要写一个python类。

      您可以通过键入以下内容来执行测试:

      nosetests filename

      【讨论】:

        【解决方案5】:

        您可能需要考虑使用 unittest.TestSuite 类,它允许您动态构建一组 unittest.TestCase 实例,这些实例将单独运行。您的 unittest.TestCase 子类应该只定义一个测试方法,该类接受一个构造参数,传入值以针对该特定实例进行测试。

        【讨论】:

          【解决方案6】:

          在循环中运行断言的问题在于,如果其中一个断言失败,您不知道是哪个值导致它(在您的示例中,它会在 0 上失败,但您不知道直到你调试)。另一方面,重复self.assertEqual(i, 33) 是一个更糟糕的主意,因为它会引入代码重复。

          我在测试中所做的是在测试中创建一个简单的、名称短小的内部函数,并使用不同的参数调用它。所以你的函数看起来像这样:

          import unittest
          
          class TestData(unittest.TestCase):
              def testNumbers(self):
                  def eq(i):
                      self.assertEqual(i, 33)
                  eq(0)
                  eq(11)
                  eq(222)
                  eq(33)
                  eq(44)
                  eq(555)
                  ... 
          

          这样,当0 的断言失败时,您会立即在unittest 模块打印的堆栈跟踪中看到它。

          【讨论】:

          • 我明白你在做什么。这是个好主意。但是第一次达到 33 时,它将停止执行其余代码。
          • 如果您的意思是您必须为每个数字重新创建测试夹具,那么您应该按照 Bill Gribble 的建议继续执行并动态构建一个 TestSuite。
          【解决方案7】:

          this 答案的分拆,这对我来说不太适用。在我不处理 大量 数据的情况下,我确实需要使用不同的输入运行相同的测试。以下测试使用我想要自定义的create_acreate_b 方法。

          要求以相同的自定义运行两个测试。

          class Tests(unittest.TestCase):
          
              def test_a_uses_b(self):
                  a = create_a()
                  b = create_b()
                  a.b = b
                  self.assertIs(b.a, a)
          
              def test_b_uses_a(self):
                  a = create_a()
                  b = create_b()
                  b.a = a
                  self.assertIs(a.b, b)
          

          我自己实例化TestSuiteTestCase,绕过测试加载程序,导致错误,因为它需要一个名为runTest 的方法。

          结果是这样的:

          class Tests(unittest.TestCase):
          
              def __init__(self, create_a, create_b):
                  super().__init__()
                  self.create_b = create_b
                  self.create_a = create_a
          
              def test_a_uses_b(self):
                  a = self.create_a()
                  b = self.create_b()
                  a.b = b
                  self.assertIs(b.a, a)
          
              def test_b_uses_a(self):
                  a = self.create_a()
                  b = self.create_b()
                  b.a = a
                  self.assertIs(a.b, b)
          
          
          class TestPair1(Tests):
              def __init__(self):
                  super().__init__(create_a1, create_b1)
          
          
          class TestPair2(Tests):
              def __init__(self):
                  super().__init__(create_a2, create_b2)
          

          【讨论】: