2

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?

2
  • Welcome jfaccioni. You say you test the elements of an iterable - unit-testing, however, is to test code to see if there are any bugs in the code. Maybe this is a misunderstanding. If you are trying to find bugs in code, could you please show that code? Otherwise, we are probably not talking about unit-testing here. Commented Mar 7, 2019 at 20:35
  • Thanks for the comment. What I want to do is to test a class I've made. In my actual code, instances of this class are built by reading input values from a long file (microscopy detection data), one instance per line. For the tests I've created a mock data file from which I instantiate mock instances of the class in the setUpClass method. But now that I wrote this, I realize that this may not be unit-testing at all... since I'm testing different instances of the class, not the code itself. I'm not used to unit-tests so I may have approached the problem with the wrong mindset. Commented Mar 10, 2019 at 3:41

1 Answer 1

8

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.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.