我有一些测试数据,想为每个项目创建一个单元测试。我的第一个想法是这样做的:

import unittest

l = [["foo", "a", "a",], ["bar", "a", "b"], ["lee", "b", "b"]]

class TestSequence(unittest.TestCase):
    def testsample(self):
        for name, a,b in l:
            print "test", name
            self.assertEqual(a,b)

if __name__ == '__main__':
    unittest.main()

这样做的缺点是它在一个测试中处理所有数据。我想在飞行中为每个项目生成一个测试。有什么建议吗?


当前回答

基于元类的答案在Python 3中仍然有效,但必须使用元类参数,而不是__metaclass__属性,例如:

class ExampleTestCase(TestCase,metaclass=DocTestMeta):
    pass

其他回答

从Python 3.4开始,已经为此目的向unittest引入了子测试。详细信息请参见文档。TestCase。subTest是一个上下文管理器,它允许在测试中隔离断言,以便用参数信息报告失败,但它不会停止测试执行。下面是文档中的例子:

class NumbersTest(unittest.TestCase):

def test_even(self):
    """
    Test that numbers between 0 and 5 are all even.
    """
    for i in range(0, 6):
        with self.subTest(i=i):
            self.assertEqual(i % 2, 0)

测试运行的输出是:

======================================================================
FAIL: test_even (__main__.NumbersTest) (i=1)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "subtests.py", line 32, in test_even
    self.assertEqual(i % 2, 0)
AssertionError: 1 != 0

======================================================================
FAIL: test_even (__main__.NumbersTest) (i=3)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "subtests.py", line 32, in test_even
    self.assertEqual(i % 2, 0)
AssertionError: 1 != 0

======================================================================
FAIL: test_even (__main__.NumbersTest) (i=5)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "subtests.py", line 32, in test_even
    self.assertEqual(i % 2, 0)
AssertionError: 1 != 0

这也是unittest2的一部分,因此可用于早期版本的Python。

这被称为“参数化”。

有几个工具支持这种方法。例如:

pytest的装饰 参数化

结果代码如下所示:

from parameterized import parameterized

class TestSequence(unittest.TestCase):
    @parameterized.expand([
        ["foo", "a", "a",],
        ["bar", "a", "b"],
        ["lee", "b", "b"],
    ])
    def test_sequence(self, name, a, b):
        self.assertEqual(a,b)

这将生成测试:

test_sequence_0_foo (__main__.TestSequence) ... ok
test_sequence_1_bar (__main__.TestSequence) ... FAIL
test_sequence_2_lee (__main__.TestSequence) ... ok

======================================================================
FAIL: test_sequence_1_bar (__main__.TestSequence)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/site-packages/parameterized/parameterized.py", line 233, in <lambda>
    standalone_func = lambda *a: func(*(a + p.args), **p.kwargs)
  File "x.py", line 12, in test_sequence
    self.assertEqual(a,b)
AssertionError: 'a' != 'b'

由于历史原因,我将保留大约2008年的原始答案):

我使用的方法是这样的:

import unittest

l = [["foo", "a", "a",], ["bar", "a", "b"], ["lee", "b", "b"]]

class TestSequense(unittest.TestCase):
    pass

def test_generator(a, b):
    def test(self):
        self.assertEqual(a,b)
    return test

if __name__ == '__main__':
    for t in l:
        test_name = 'test_%s' % t[0]
        test = test_generator(t[1], t[2])
        setattr(TestSequense, test_name, test)
    unittest.main()

我有麻烦使这些工作为setUpClass。

下面是Javier回答的一个版本,它允许setUpClass访问动态分配的属性。

import unittest


class GeneralTestCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        print ''
        print cls.p1
        print cls.p2

    def runTest1(self):
        self.assertTrue((self.p2 - self.p1) == 1)

    def runTest2(self):
        self.assertFalse((self.p2 - self.p1) == 2)


def load_tests(loader, tests, pattern):
    test_cases = unittest.TestSuite()
    for p1, p2 in [(1, 2), (3, 4)]:
        clsname = 'TestCase_{}_{}'.format(p1, p2)
        dct = {
            'p1': p1,
            'p2': p2,
        }
        cls = type(clsname, (GeneralTestCase,), dct)
        test_cases.addTest(cls('runTest1'))
        test_cases.addTest(cls('runTest2'))
    return test_cases

输出

1
2
..
3
4
..
----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK

除了使用setattr,我们还可以在Python 3.2及更高版本中使用load_tests。

class Test(unittest.TestCase):
    pass

def _test(self, file_name):
    open(file_name, 'r') as f:
        self.assertEqual('test result',f.read())

def _generate_test(file_name):
    def test(self):
        _test(self, file_name)
    return test

def _generate_tests():
    for file in files:
        file_name = os.path.splitext(os.path.basename(file))[0]
        setattr(Test, 'test_%s' % file_name, _generate_test(file))

test_cases = (Test,)

def load_tests(loader, tests, pattern):
    _generate_tests()
    suite = TestSuite()
    for test_class in test_cases:
        tests = loader.loadTestsFromTestCase(test_class)
        suite.addTests(tests)
    return suite

if __name__ == '__main__':
    _generate_tests()
    unittest.main()

这可以通过使用pytest来完成。只需要编写test_me.py文件的内容:

import pytest

@pytest.mark.parametrize('name, left, right', [['foo', 'a', 'a'],
                                               ['bar', 'a', 'b'],
                                               ['baz', 'b', 'b']])
def test_me(name, left, right):
    assert left == right, name

并使用py命令运行测试。Test——tb=短test_me.py。然后输出如下所示:

=========================== test session starts ============================
platform darwin -- Python 2.7.6 -- py-1.4.23 -- pytest-2.6.1
collected 3 items

test_me.py .F.

================================= FAILURES =================================
_____________________________ test_me[bar-a-b] _____________________________
test_me.py:8: in test_me
    assert left == right, name
E   AssertionError: bar
==================== 1 failed, 2 passed in 0.01 seconds ====================

这很简单!此外,pytest还有更多的功能,如fixture、标记、断言等。