Issue
I'm having trouble trying to come up with a elegant solution for this situation.
Let's say I have a unittest in Python that will test several elements from an iterable. Since this iterable is costly to build in memory, I want to build it only once trough the 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. This is all fine.
The following code is an example of a mock test case doing exactly what I've described:
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()
What bugs me here is that the lines for n in self.numbers: with self.subTest(current_number=n): . . .
are repeated for each test method. Is there any way to avoid this? I know I could simply clump all self.assert
statements together in a single method, but this defeats the purpose of having different tests for different aspects in the first place.
In my mind the "ideal" solution would be to, somehow, yield
the values from the iterable (maybe through the setUp
method? No clue) and pass these values to each test method before yielding the next one. but I have no idea how to actually do this, especially since it involves a context manager... Has anyone got any other solutions, or are there simply no workarounds for this?
Solution
One easy solution is to outsource for
and with
statements to a decorator. It still needs a line per test case though:
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)
# ...
Of course this does not run just once over your iterable as your ideal solution would do. For this you would need to decorate your whole class. This seems possible with the @parameterized_class
decorator from the parameterized library. Never used this decorator on my own though.
Answered By - CodeNStuff
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.