使用 Python 的单元测试子测试时避免重复测试?
Avoid repetition in tests when using Python's unittest subTest?
我无法为这种情况想出一个优雅的解决方案。
假设我在 Python 中有一个单元测试,它将测试可迭代对象中的几个元素。由于在内存中构建此可迭代对象的成本很高,因此我只想通过 setUpClass
method. Then, in each test, I want to sequentially pass each element in the iterable to the test, which I can to using the 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
语句集中在一个方法中,但这首先违背了对不同方面进行不同测试的目的。
在我看来,"ideal" 解决方案是以某种方式 yield
来自 iterable 的值(也许通过 setUp
方法?没有线索)并将这些值传递给在产生下一个之前的每个测试方法。但我不知道如何实际执行此操作,特别是因为它涉及上下文管理器......有没有人有任何其他解决方案,或者根本没有解决方法?
一个简单的解决方案是将 for
和 with
语句外包给装饰器。但是每个测试用例仍然需要一行:
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)
# ...
当然,这 而不是 运行 只是在您的可迭代对象上执行一次,就像您的理想解决方案所做的那样。为此,您需要装饰整个 class。这似乎可以通过 parameterized 库中的 @parameterized_class
装饰器实现。我自己从来没有使用过这个装饰器。
我无法为这种情况想出一个优雅的解决方案。
假设我在 Python 中有一个单元测试,它将测试可迭代对象中的几个元素。由于在内存中构建此可迭代对象的成本很高,因此我只想通过 setUpClass
method. Then, in each test, I want to sequentially pass each element in the iterable to the test, which I can to using the 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
语句集中在一个方法中,但这首先违背了对不同方面进行不同测试的目的。
在我看来,"ideal" 解决方案是以某种方式 yield
来自 iterable 的值(也许通过 setUp
方法?没有线索)并将这些值传递给在产生下一个之前的每个测试方法。但我不知道如何实际执行此操作,特别是因为它涉及上下文管理器......有没有人有任何其他解决方案,或者根本没有解决方法?
一个简单的解决方案是将 for
和 with
语句外包给装饰器。但是每个测试用例仍然需要一行:
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)
# ...
当然,这 而不是 运行 只是在您的可迭代对象上执行一次,就像您的理想解决方案所做的那样。为此,您需要装饰整个 class。这似乎可以通过 parameterized 库中的 @parameterized_class
装饰器实现。我自己从来没有使用过这个装饰器。