Python unit test with base and sub class
Question:
I currently have a few unit tests which share a common set of tests. Here’s an example:
import unittest
class BaseTest(unittest.TestCase):
def testCommon(self):
print 'Calling BaseTest:testCommon'
value = 5
self.assertEquals(value, 5)
class SubTest1(BaseTest):
def testSub1(self):
print 'Calling SubTest1:testSub1'
sub = 3
self.assertEquals(sub, 3)
class SubTest2(BaseTest):
def testSub2(self):
print 'Calling SubTest2:testSub2'
sub = 4
self.assertEquals(sub, 4)
if __name__ == '__main__':
unittest.main()
The output of the above is:
Calling BaseTest:testCommon
.Calling BaseTest:testCommon
.Calling SubTest1:testSub1
.Calling BaseTest:testCommon
.Calling SubTest2:testSub2
.
----------------------------------------------------------------------
Ran 5 tests in 0.000s
OK
Is there a way to rewrite the above so that the very first testCommon
is not called?
EDIT:
Instead of running 5 tests above, I want it to run only 4 tests, 2 from the SubTest1 and another 2 from SubTest2. It seems that Python unittest is running the original BaseTest on its own and I need a mechanism to prevent that from happening.
Answers:
Change the BaseTest method name to setUp:
class BaseTest(unittest.TestCase):
def setUp(self):
print 'Calling BaseTest:testCommon'
value = 5
self.assertEquals(value, 5)
class SubTest1(BaseTest):
def testSub1(self):
print 'Calling SubTest1:testSub1'
sub = 3
self.assertEquals(sub, 3)
class SubTest2(BaseTest):
def testSub2(self):
print 'Calling SubTest2:testSub2'
sub = 4
self.assertEquals(sub, 4)
Output:
Ran 2 tests in 0.000s
Calling BaseTest:testCommon Calling
SubTest1:testSub1 Calling
BaseTest:testCommon Calling
SubTest2:testSub2
From the documentation:
TestCase.setUp()
Method called to
prepare the test fixture. This is
called immediately before calling the
test method; any exception raised by
this method will be considered an
error rather than a test failure. The
default implementation does nothing.
What are you trying to achieve? If you have common test code (assertions, template tests, etc), then place them in methods which aren’t prefixed with test
so unittest
won’t load them.
import unittest
class CommonTests(unittest.TestCase):
def common_assertion(self, foo, bar, baz):
# whatever common code
self.assertEqual(foo(bar), baz)
class BaseTest(CommonTests):
def testCommon(self):
print 'Calling BaseTest:testCommon'
value = 5
self.assertEquals(value, 5)
class SubTest1(CommonTests):
def testSub1(self):
print 'Calling SubTest1:testSub1'
sub = 3
self.assertEquals(sub, 3)
class SubTest2(CommonTests):
def testSub2(self):
print 'Calling SubTest2:testSub2'
sub = 4
self.assertEquals(sub, 4)
if __name__ == '__main__':
unittest.main()
Use multiple inheritance, so your class with common tests doesn’t itself inherit from TestCase.
import unittest
class CommonTests(object):
def testCommon(self):
print 'Calling BaseTest:testCommon'
value = 5
self.assertEquals(value, 5)
class SubTest1(unittest.TestCase, CommonTests):
def testSub1(self):
print 'Calling SubTest1:testSub1'
sub = 3
self.assertEquals(sub, 3)
class SubTest2(unittest.TestCase, CommonTests):
def testSub2(self):
print 'Calling SubTest2:testSub2'
sub = 4
self.assertEquals(sub, 4)
if __name__ == '__main__':
unittest.main()
Matthew’s answer is the one I needed to use since I’m on 2.5 still.
But as of 2.7 you can use the @unittest.skip() decorator on any test methods you want to skip.
http://docs.python.org/library/unittest.html#skipping-tests-and-expected-failures
You’ll need to implement your own skipping decorator to check for the base type. Haven’t used this feature before, but off the top of my head you could use BaseTest as a marker type to condition the skip:
def skipBaseTest(obj):
if type(obj) is BaseTest:
return unittest.skip("BaseTest tests skipped")
return lambda func: func
Matthew Marshall’s answer is great, but it requires that you inherit from two classes in each of your test cases, which is error-prone. Instead, I use this (python>=2.7):
class BaseTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
if cls is BaseTest:
raise unittest.SkipTest("Skip BaseTest tests, it's a base class")
super(BaseTest, cls).setUpClass()
You can solve this problem with a single command:
del(BaseTest)
So the code would look like this:
import unittest
class BaseTest(unittest.TestCase):
def testCommon(self):
print 'Calling BaseTest:testCommon'
value = 5
self.assertEquals(value, 5)
class SubTest1(BaseTest):
def testSub1(self):
print 'Calling SubTest1:testSub1'
sub = 3
self.assertEquals(sub, 3)
class SubTest2(BaseTest):
def testSub2(self):
print 'Calling SubTest2:testSub2'
sub = 4
self.assertEquals(sub, 4)
del(BaseTest)
if __name__ == '__main__':
unittest.main()
A way I’ve thought of solving this is by hiding the test methods if the base class is used. This way the tests aren’t skipped, so the test results can be green instead of yellow in many test reporting tools.
Compared to the mixin method, ide’s like PyCharm won’t complain that unit test methods are missing from the base class.
If a base class inherits from this class, it will need to override the setUpClass
and tearDownClass
methods.
class BaseTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls._test_methods = []
if cls is BaseTest:
for name in dir(cls):
if name.startswith('test') and callable(getattr(cls, name)):
cls._test_methods.append((name, getattr(cls, name)))
setattr(cls, name, lambda self: None)
@classmethod
def tearDownClass(cls):
if cls is BaseTest:
for name, method in cls._test_methods:
setattr(cls, name, method)
cls._test_methods = []
Do not use multiple inheritance, it will bite you later.
Instead you can just move your base class into the separate module or wrap it with the blank class:
class BaseTestCases:
class BaseTest(unittest.TestCase):
def testCommon(self):
print('Calling BaseTest:testCommon')
value = 5
self.assertEqual(value, 5)
class SubTest1(BaseTestCases.BaseTest):
def testSub1(self):
print('Calling SubTest1:testSub1')
sub = 3
self.assertEqual(sub, 3)
class SubTest2(BaseTestCases.BaseTest):
def testSub2(self):
print('Calling SubTest2:testSub2')
sub = 4
self.assertEqual(sub, 4)
if __name__ == '__main__':
unittest.main()
The output:
Calling BaseTest:testCommon
.Calling SubTest1:testSub1
.Calling BaseTest:testCommon
.Calling SubTest2:testSub2
.
----------------------------------------------------------------------
Ran 4 tests in 0.001s
OK
Another option is not to execute
unittest.main()
Instead of that you can use
suite = unittest.TestLoader().loadTestsFromTestCase(TestClass)
unittest.TextTestRunner(verbosity=2).run(suite)
So you only execute the tests in the class TestClass
Just rename the testCommon method to something else. Unittest (usually) skips anything that doesn’t have ‘test’ in it.
Quick and simple
import unittest
class BaseTest(unittest.TestCase):
def methodCommon(self):
print 'Calling BaseTest:testCommon'
value = 5
self.assertEquals(value, 5)
class SubTest1(BaseTest):
def testSub1(self):
print 'Calling SubTest1:testSub1'
sub = 3
self.assertEquals(sub, 3)
class SubTest2(BaseTest):
def testSub2(self):
print 'Calling SubTest2:testSub2'
sub = 4
self.assertEquals(sub, 4)
if __name__ == '__main__':
unittest.main()`
I made about the same than @Vladim P. (https://stackoverflow.com/a/25695512/2451329) but slightly modified:
import unittest2
from some_module import func1, func2
def make_base_class(func):
class Base(unittest2.TestCase):
def test_common1(self):
print("in test_common1")
self.assertTrue(func())
def test_common2(self):
print("in test_common1")
self.assertFalse(func(42))
return Base
class A(make_base_class(func1)):
pass
class B(make_base_class(func2)):
def test_func2_with_no_arg_return_bar(self):
self.assertEqual("bar", func2())
and there we go.
So this is kind of an old thread but I came across this problem today and thought of my own hack for it. It uses a decorator that makes the values of the functions None when acessed through the base class. Don’t need to worry about setup and setupclass because if the baseclass has no tests they won’t run.
import types
import unittest
class FunctionValueOverride(object):
def __init__(self, cls, default, override=None):
self.cls = cls
self.default = default
self.override = override
def __get__(self, obj, klass):
if klass == self.cls:
return self.override
else:
if obj:
return types.MethodType(self.default, obj)
else:
return self.default
def fixture(cls):
for t in vars(cls):
if not callable(getattr(cls, t)) or t[:4] != "test":
continue
setattr(cls, t, FunctionValueOverride(cls, getattr(cls, t)))
return cls
@fixture
class BaseTest(unittest.TestCase):
def testCommon(self):
print('Calling BaseTest:testCommon')
value = 5
self.assertEqual(value, 5)
class SubTest1(BaseTest):
def testSub1(self):
print('Calling SubTest1:testSub1')
sub = 3
self.assertEqual(sub, 3)
class SubTest2(BaseTest):
def testSub2(self):
print('Calling SubTest2:testSub2')
sub = 4
self.assertEqual(sub, 4)
if __name__ == '__main__':
unittest.main()
You can add __test__ = False
in BaseTest class, but if you add it, be aware that you must add __test__ = True
in derived classes to be able to run tests.
import unittest
class BaseTest(unittest.TestCase):
__test__ = False
def testCommon(self):
print 'Calling BaseTest:testCommon'
value = 5
self.assertEquals(value, 5)
class SubTest1(BaseTest):
__test__ = True
def testSub1(self):
print 'Calling SubTest1:testSub1'
sub = 3
self.assertEquals(sub, 3)
class SubTest2(BaseTest):
__test__ = True
def testSub2(self):
print 'Calling SubTest2:testSub2'
sub = 4
self.assertEquals(sub, 4)
if __name__ == '__main__':
unittest.main()
As of Python 3.2, you can add a test_loader function to a module to control which tests (if any) are found by the test discovery mechanism.
For example, the following will only load the original poster’s SubTest1
and SubTest2
Test Cases, ignoring Base
:
def load_tests(loader, standard_tests, pattern):
suite = TestSuite()
suite.addTests([SubTest1, SubTest2])
return suite
It ought to be possible to iterate over standard_tests
(a TestSuite
containing the tests the default loader found) and copy all but Base
to suite
instead, but the nested nature of TestSuite.__iter__
makes that a lot more complicated.
Here is a solution that uses only documented unittest features, and that avoids having a "skip" status in your test results:
class BaseTest(unittest.TestCase):
def __init__(self, methodName='runTest'):
if self.__class__ is BaseTest:
# don't run these tests in the abstract base implementation
methodName = 'runNoTestsInBaseClass'
super().__init__(methodName)
def runNoTestsInBaseClass(self):
pass
def testCommon(self):
# everything else as in the original question
How it works: per the unittest.TestCase
documentation, "Each instance of TestCase will run a single base method: the method named methodName." The default "runTests" runs all the test* methods on the class—that’s how TestCase instances normally work. But when running in the abstract base class itself, you can simply override that behavior with a method that does nothing.
A side effect is your test count will increase by one: the runNoTestsInBaseClass "test" gets counted as a successful test when it’s run on BaseClass.
(This also works in Python 2.7, if you’re still on that. Just change super()
to super(BaseTest, self)
.)
I currently have a few unit tests which share a common set of tests. Here’s an example:
import unittest
class BaseTest(unittest.TestCase):
def testCommon(self):
print 'Calling BaseTest:testCommon'
value = 5
self.assertEquals(value, 5)
class SubTest1(BaseTest):
def testSub1(self):
print 'Calling SubTest1:testSub1'
sub = 3
self.assertEquals(sub, 3)
class SubTest2(BaseTest):
def testSub2(self):
print 'Calling SubTest2:testSub2'
sub = 4
self.assertEquals(sub, 4)
if __name__ == '__main__':
unittest.main()
The output of the above is:
Calling BaseTest:testCommon
.Calling BaseTest:testCommon
.Calling SubTest1:testSub1
.Calling BaseTest:testCommon
.Calling SubTest2:testSub2
.
----------------------------------------------------------------------
Ran 5 tests in 0.000s
OK
Is there a way to rewrite the above so that the very first testCommon
is not called?
EDIT:
Instead of running 5 tests above, I want it to run only 4 tests, 2 from the SubTest1 and another 2 from SubTest2. It seems that Python unittest is running the original BaseTest on its own and I need a mechanism to prevent that from happening.
Change the BaseTest method name to setUp:
class BaseTest(unittest.TestCase):
def setUp(self):
print 'Calling BaseTest:testCommon'
value = 5
self.assertEquals(value, 5)
class SubTest1(BaseTest):
def testSub1(self):
print 'Calling SubTest1:testSub1'
sub = 3
self.assertEquals(sub, 3)
class SubTest2(BaseTest):
def testSub2(self):
print 'Calling SubTest2:testSub2'
sub = 4
self.assertEquals(sub, 4)
Output:
Ran 2 tests in 0.000s
Calling BaseTest:testCommon Calling
SubTest1:testSub1 Calling
BaseTest:testCommon Calling
SubTest2:testSub2
From the documentation:
TestCase.setUp()
Method called to
prepare the test fixture. This is
called immediately before calling the
test method; any exception raised by
this method will be considered an
error rather than a test failure. The
default implementation does nothing.
What are you trying to achieve? If you have common test code (assertions, template tests, etc), then place them in methods which aren’t prefixed with test
so unittest
won’t load them.
import unittest
class CommonTests(unittest.TestCase):
def common_assertion(self, foo, bar, baz):
# whatever common code
self.assertEqual(foo(bar), baz)
class BaseTest(CommonTests):
def testCommon(self):
print 'Calling BaseTest:testCommon'
value = 5
self.assertEquals(value, 5)
class SubTest1(CommonTests):
def testSub1(self):
print 'Calling SubTest1:testSub1'
sub = 3
self.assertEquals(sub, 3)
class SubTest2(CommonTests):
def testSub2(self):
print 'Calling SubTest2:testSub2'
sub = 4
self.assertEquals(sub, 4)
if __name__ == '__main__':
unittest.main()
Use multiple inheritance, so your class with common tests doesn’t itself inherit from TestCase.
import unittest
class CommonTests(object):
def testCommon(self):
print 'Calling BaseTest:testCommon'
value = 5
self.assertEquals(value, 5)
class SubTest1(unittest.TestCase, CommonTests):
def testSub1(self):
print 'Calling SubTest1:testSub1'
sub = 3
self.assertEquals(sub, 3)
class SubTest2(unittest.TestCase, CommonTests):
def testSub2(self):
print 'Calling SubTest2:testSub2'
sub = 4
self.assertEquals(sub, 4)
if __name__ == '__main__':
unittest.main()
Matthew’s answer is the one I needed to use since I’m on 2.5 still.
But as of 2.7 you can use the @unittest.skip() decorator on any test methods you want to skip.
http://docs.python.org/library/unittest.html#skipping-tests-and-expected-failures
You’ll need to implement your own skipping decorator to check for the base type. Haven’t used this feature before, but off the top of my head you could use BaseTest as a marker type to condition the skip:
def skipBaseTest(obj):
if type(obj) is BaseTest:
return unittest.skip("BaseTest tests skipped")
return lambda func: func
Matthew Marshall’s answer is great, but it requires that you inherit from two classes in each of your test cases, which is error-prone. Instead, I use this (python>=2.7):
class BaseTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
if cls is BaseTest:
raise unittest.SkipTest("Skip BaseTest tests, it's a base class")
super(BaseTest, cls).setUpClass()
You can solve this problem with a single command:
del(BaseTest)
So the code would look like this:
import unittest
class BaseTest(unittest.TestCase):
def testCommon(self):
print 'Calling BaseTest:testCommon'
value = 5
self.assertEquals(value, 5)
class SubTest1(BaseTest):
def testSub1(self):
print 'Calling SubTest1:testSub1'
sub = 3
self.assertEquals(sub, 3)
class SubTest2(BaseTest):
def testSub2(self):
print 'Calling SubTest2:testSub2'
sub = 4
self.assertEquals(sub, 4)
del(BaseTest)
if __name__ == '__main__':
unittest.main()
A way I’ve thought of solving this is by hiding the test methods if the base class is used. This way the tests aren’t skipped, so the test results can be green instead of yellow in many test reporting tools.
Compared to the mixin method, ide’s like PyCharm won’t complain that unit test methods are missing from the base class.
If a base class inherits from this class, it will need to override the setUpClass
and tearDownClass
methods.
class BaseTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls._test_methods = []
if cls is BaseTest:
for name in dir(cls):
if name.startswith('test') and callable(getattr(cls, name)):
cls._test_methods.append((name, getattr(cls, name)))
setattr(cls, name, lambda self: None)
@classmethod
def tearDownClass(cls):
if cls is BaseTest:
for name, method in cls._test_methods:
setattr(cls, name, method)
cls._test_methods = []
Do not use multiple inheritance, it will bite you later.
Instead you can just move your base class into the separate module or wrap it with the blank class:
class BaseTestCases:
class BaseTest(unittest.TestCase):
def testCommon(self):
print('Calling BaseTest:testCommon')
value = 5
self.assertEqual(value, 5)
class SubTest1(BaseTestCases.BaseTest):
def testSub1(self):
print('Calling SubTest1:testSub1')
sub = 3
self.assertEqual(sub, 3)
class SubTest2(BaseTestCases.BaseTest):
def testSub2(self):
print('Calling SubTest2:testSub2')
sub = 4
self.assertEqual(sub, 4)
if __name__ == '__main__':
unittest.main()
The output:
Calling BaseTest:testCommon
.Calling SubTest1:testSub1
.Calling BaseTest:testCommon
.Calling SubTest2:testSub2
.
----------------------------------------------------------------------
Ran 4 tests in 0.001s
OK
Another option is not to execute
unittest.main()
Instead of that you can use
suite = unittest.TestLoader().loadTestsFromTestCase(TestClass)
unittest.TextTestRunner(verbosity=2).run(suite)
So you only execute the tests in the class TestClass
Just rename the testCommon method to something else. Unittest (usually) skips anything that doesn’t have ‘test’ in it.
Quick and simple
import unittest
class BaseTest(unittest.TestCase):
def methodCommon(self):
print 'Calling BaseTest:testCommon'
value = 5
self.assertEquals(value, 5)
class SubTest1(BaseTest):
def testSub1(self):
print 'Calling SubTest1:testSub1'
sub = 3
self.assertEquals(sub, 3)
class SubTest2(BaseTest):
def testSub2(self):
print 'Calling SubTest2:testSub2'
sub = 4
self.assertEquals(sub, 4)
if __name__ == '__main__':
unittest.main()`
I made about the same than @Vladim P. (https://stackoverflow.com/a/25695512/2451329) but slightly modified:
import unittest2
from some_module import func1, func2
def make_base_class(func):
class Base(unittest2.TestCase):
def test_common1(self):
print("in test_common1")
self.assertTrue(func())
def test_common2(self):
print("in test_common1")
self.assertFalse(func(42))
return Base
class A(make_base_class(func1)):
pass
class B(make_base_class(func2)):
def test_func2_with_no_arg_return_bar(self):
self.assertEqual("bar", func2())
and there we go.
So this is kind of an old thread but I came across this problem today and thought of my own hack for it. It uses a decorator that makes the values of the functions None when acessed through the base class. Don’t need to worry about setup and setupclass because if the baseclass has no tests they won’t run.
import types
import unittest
class FunctionValueOverride(object):
def __init__(self, cls, default, override=None):
self.cls = cls
self.default = default
self.override = override
def __get__(self, obj, klass):
if klass == self.cls:
return self.override
else:
if obj:
return types.MethodType(self.default, obj)
else:
return self.default
def fixture(cls):
for t in vars(cls):
if not callable(getattr(cls, t)) or t[:4] != "test":
continue
setattr(cls, t, FunctionValueOverride(cls, getattr(cls, t)))
return cls
@fixture
class BaseTest(unittest.TestCase):
def testCommon(self):
print('Calling BaseTest:testCommon')
value = 5
self.assertEqual(value, 5)
class SubTest1(BaseTest):
def testSub1(self):
print('Calling SubTest1:testSub1')
sub = 3
self.assertEqual(sub, 3)
class SubTest2(BaseTest):
def testSub2(self):
print('Calling SubTest2:testSub2')
sub = 4
self.assertEqual(sub, 4)
if __name__ == '__main__':
unittest.main()
You can add __test__ = False
in BaseTest class, but if you add it, be aware that you must add __test__ = True
in derived classes to be able to run tests.
import unittest
class BaseTest(unittest.TestCase):
__test__ = False
def testCommon(self):
print 'Calling BaseTest:testCommon'
value = 5
self.assertEquals(value, 5)
class SubTest1(BaseTest):
__test__ = True
def testSub1(self):
print 'Calling SubTest1:testSub1'
sub = 3
self.assertEquals(sub, 3)
class SubTest2(BaseTest):
__test__ = True
def testSub2(self):
print 'Calling SubTest2:testSub2'
sub = 4
self.assertEquals(sub, 4)
if __name__ == '__main__':
unittest.main()
As of Python 3.2, you can add a test_loader function to a module to control which tests (if any) are found by the test discovery mechanism.
For example, the following will only load the original poster’s SubTest1
and SubTest2
Test Cases, ignoring Base
:
def load_tests(loader, standard_tests, pattern):
suite = TestSuite()
suite.addTests([SubTest1, SubTest2])
return suite
It ought to be possible to iterate over standard_tests
(a TestSuite
containing the tests the default loader found) and copy all but Base
to suite
instead, but the nested nature of TestSuite.__iter__
makes that a lot more complicated.
Here is a solution that uses only documented unittest features, and that avoids having a "skip" status in your test results:
class BaseTest(unittest.TestCase):
def __init__(self, methodName='runTest'):
if self.__class__ is BaseTest:
# don't run these tests in the abstract base implementation
methodName = 'runNoTestsInBaseClass'
super().__init__(methodName)
def runNoTestsInBaseClass(self):
pass
def testCommon(self):
# everything else as in the original question
How it works: per the unittest.TestCase
documentation, "Each instance of TestCase will run a single base method: the method named methodName." The default "runTests" runs all the test* methods on the class—that’s how TestCase instances normally work. But when running in the abstract base class itself, you can simply override that behavior with a method that does nothing.
A side effect is your test count will increase by one: the runNoTestsInBaseClass "test" gets counted as a successful test when it’s run on BaseClass.
(This also works in Python 2.7, if you’re still on that. Just change super()
to super(BaseTest, self)
.)