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?

asked Mar 6, 2019 at 18:42
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.

Dan Yishai
7813 silver badges14 bronze badges
answered Sep 20, 2020 at 14:00
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.