使用Python的unittest subTest时,避免在测试中重复吗?

时间:2019-03-06 18:42:55

标签: python python-3.x unit-testing python-unittest

在解决这种情况时,我遇到了麻烦。

假设我在Python中有一个单元测试,它将测试可迭代对象中的多个元素。由于在内存中构建此可迭代方法非常昂贵,因此我只想通过setUpClass方法构建它。然后,在每个测试中,我想依次将可迭代对象中的每个元素传递给测试,我可以使用context manager and the subTest method。很好。

以下代码是一个模拟测试案例的示例,完全按照我的描述进行操作:

import unittest

class NumberTest(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        cls.numbers = []
        for x in range(1000):
            cls.numbers.append(x)

    @classmethod
    def tearDownClass(cls):
        del cls.numbers

    def test_number_is_even(self):
        for n in self.numbers:
            with self.subTest(current_number=n):
                self.assertEqual(n % 2, 0)

    def test_number_is_odd(self):
        for n in self.numbers:
            with self.subTest(current_number=n):
                self.assertEqual(n % 2, 1)

    def test_number_is_positive(self):
        for n in self.numbers:
            with self.subTest(current_number=n):
                self.assertTrue(n > 0)

    def test_number_is_negative(self):
        for n in self.numbers:
            with self.subTest(current_number=n):
                self.assertTrue(n < 0)


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

给我带来麻烦的是,每种测试方法都重复行for n in self.numbers: with self.subTest(current_number=n): . . .。有什么办法可以避免这种情况?我知道我可以将所有self.assert语句简单地合并为一个方法,但是这违背了首先针对不同方面进行不同测试的目的。

在我看来,“理想”的解决方案是以某种方式yield迭代中的值(也许通过setUp方法?不知道),然后将这些值传递给每个测试方法产生下一个。但我不知道如何实际执行此操作,尤其是因为它涉及到上下文管理器...是否有人有其他解决方案,或者根本没有解决方法?

1 个答案:

答案 0 :(得分:1)

一个简单的解决方案是将forwith语句外包给装饰器。不过,每个测试用例仍然需要一行:

import functools
import unittest


class NumberTest(unittest.TestCase):
    # ...
    def for_each_number(test_func):
        @functools.wraps(test_func)
        def decorated(self):
            for n in self.numbers:
                with self.subTest(current_number=n):
                    test_func(self, n)
        return decorated

    @for_each_number
    def test_number_is_even(self, n):
        self.assertEqual(n % 2, 0)

    @for_each_number
    def test_number_is_odd(self, n):
        self.assertEqual(n % 2, 1)
    # ...

当然,这不会像理想解决方案那样在迭代过程中运行一次。为此,您需要装饰整个课程。使用parameterized库中的@parameterized_class装饰器似乎可以做到这一点。不过,我从来没有独自使用过这个装饰器。