【问题标题】:Avoid repetition in tests when using Python's unittest subTest?使用 Python 的 unittest subTest 时避免重复测试?
【发布时间】:2019-03-06 18:42:55
【问题描述】:

我很难为这种情况想出一个优雅的解决方案。

假设我在 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 方法?没有线索)并将这些值传递给每个测试方法,然后再产生下一个.但我不知道如何实际做到这一点,特别是因为它涉及上下文管理器......有没有人有任何其他解决方案,或者根本没有解决方法?

【问题讨论】:

  • 欢迎 jfaccioni。您说您测试了可迭代的元素-但是,单元测试是测试代码以查看代码中是否存在任何错误。也许这是一个误解。如果您正在尝试查找代码中的错误,能否请您显示该代码?否则,我们可能不是在这里讨论单元测试。
  • 感谢您的评论。我想做的是测试我制作的课程。在我的实际代码中,这个类的实例是通过从一个长文件(显微镜检测数据)中读取输入值来构建的,每行一个实例。对于测试,我创建了一个模拟数据文件,我在 setUpClass 方法中实例化了该类的模拟实例。但是现在我写了这个,我意识到这可能根本不是单元测试......因为我正在测试类的不同实例,而不是代码本身。我不习惯单元测试,所以我可能以错误的心态解决了这个问题。

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


【解决方案1】:

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

from functools import wraps
from unittest import TestCase


def for_each_number():
    def decorator(test_func):
        @wraps(test_func)
        def wrapper(self: TestCase):
            for n in self.numbers:
                with self.subTest(current_number=n):
                    test_func(self, n)

        return wrapper
    return decorator


class NumberTest(TestCase):
    # ...

    @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 装饰器实现。不过我从来没有单独使用过这个装饰器。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-01-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多