How do I concisely implement multiple similar unit tests in the Python unittest framework?
Here's my favorite approach to the "family of related tests". I like explicit subclasses of a TestCase that expresses the common features.
class MyTestF1( unittest.TestCase ):
theFunction= staticmethod( f1 )
def setUp(self):
self.matrix1 = numpy.ones((5,10))
self.matrix2 = numpy.identity(5)
def testOutputShape( self ):
"""Output of functions be of a certain shape"""
output = self.theFunction(self.matrix1, self.matrix2)
fail_message = "%s produces output of the wrong shape" % (self.theFunction.__name__,)
self.assertEqual(self.matrix1.shape, output.shape, fail_message)
class TestF2( MyTestF1 ):
"""Includes ALL of TestF1 tests, plus a new test."""
theFunction= staticmethod( f2 )
def testUniqueFeature( self ):
# blah blah blah
pass
class TestF3( MyTestF1 ):
"""Includes ALL of TestF1 tests with no additional code."""
theFunction= staticmethod( f3 )
Add a function, add a subclass of MyTestF1
. Each subclass of MyTestF1 includes all of the tests in MyTestF1 with no duplicated code of any kind.
Unique features are handled in an obvious way. New methods are added to the subclass.
It's completely compatible with unittest.main()
You don't have to use meta classes here. A simple loop fits just fine. Take a look at the example below:
import unittest
class TestCase1(unittest.TestCase):
def check_something(self, param1):
self.assertTrue(param1)
def _add_test(name, param1):
def test_method(self):
self.check_something(param1)
setattr(TestCase1, 'test_' + name, test_method)
test_method.__name__ = 'test_' + name
for i in range(0, 3):
_add_test(str(i), False)
Once the for is executed, the TestCase1 has three test methods that are supported by both nose and unittest.
If you're already using nose (and some of your comments suggest you are), just use Test Generators, which are the most straightforward way to implement parametric tests I've come across:
For example:
from binary_search import search1 as search
def test_binary_search():
data = (
(-1, 3, []),
(-1, 3, [1]),
(0, 1, [1]),
(0, 1, [1, 3, 5]),
(1, 3, [1, 3, 5]),
(2, 5, [1, 3, 5]),
(-1, 0, [1, 3, 5]),
(-1, 2, [1, 3, 5]),
(-1, 4, [1, 3, 5]),
(-1, 6, [1, 3, 5]),
(0, 1, [1, 3, 5, 7]),
(1, 3, [1, 3, 5, 7]),
(2, 5, [1, 3, 5, 7]),
(3, 7, [1, 3, 5, 7]),
(-1, 0, [1, 3, 5, 7]),
(-1, 2, [1, 3, 5, 7]),
(-1, 4, [1, 3, 5, 7]),
(-1, 6, [1, 3, 5, 7]),
(-1, 8, [1, 3, 5, 7]),
)
for result, n, ns in data:
yield check_binary_search, result, n, ns
def check_binary_search(expected, n, ns):
actual = search(n, ns)
assert expected == actual
Produces:
$ nosetests -d
...................
----------------------------------------------------------------------
Ran 19 tests in 0.009s
OK