1 Part 1 Beginning Python
1.9 Special Tasks
1.9.3 Unit tests
For more documentation on the unit test framework, see unittest Unit testing framework http://docs.python.org/2/library/unittest.html#moduleunittest For help and more information do the following at the Python interactive prompt:
>>> import unittest
>>> help(unittest)
And, you can read the source: Lib/unittest.py in the Python standard library.
1.9.3.1 A simple example
Here is a very simple example. You can find more information about this primitive way of structuring unit tests in the library documentation for the unittest module Basic example http://docs.python.org/lib/minimalexample.html
import unittest
class UnitTests02(unittest.TestCase):
def testFoo(self):
self.failUnless(False)
class UnitTests01(unittest.TestCase):
def testBar01(self):
self.failUnless(False) def testBar02(self):
self.failUnless(False) def main():
unittest.main()
if __name__ == '__main__':
main()
Notes:
● The call to unittest.main() runs all tests in all test fixtures in the module. It actually creates an instance of class TestProgram in module
Lib/unittest.py, which automatically runs tests.
● Test fixtures are classes that inherit from unittest.TestCase.
● Within a test fixture (a class), the tests are any methods whose names begin with the prefix "test".
● In any test, we check for success or failure with inherited methods such as failIf(), failUnless(), assertNotEqual(), etc. For more on these
methods, see the library documentation for the unittest module TestCase Objects http://docs.python.org/lib/testcaseobjects.html.
● If you want to change (1) the test method prefix or (2) the function used to sort (the order of) execution of tests within a test fixture, then you can create your own instance of class unittest.TestLoader and customize it. For example:
def main():
my_test_loader = unittest.TestLoader() my_test_loader.testMethodPrefix = 'check'
my_test_loader.sortTestMethodsUsing = my_cmp_func unittest.main(testLoader=my_test_loader)
if __name__ == '__main__':
main()
But, see the notes in section Additional unittest features for instructions on a (possibly) better way to do this.
1.9.3.2 Unit test suites
Here is another, not quite so simple, example:
#!/usr/bin/env python import sys, popen2 import getopt import unittest
class GenTest(unittest.TestCase):
def test_1_generate(self):
cmd = 'python ../generateDS.py f o out2sup.py s out2sub.py people.xsd'
outfile, infile = popen2.popen2(cmd) result = outfile.read()
outfile.close() infile.close()
self.failUnless(len(result) == 0) def test_2_compare_superclasses(self):
cmd = 'diff out1sup.py out2sup.py' outfile, infile = popen2.popen2(cmd) outfile, infile = popen2.popen2(cmd) result = outfile.read()
outfile.close() infile.close()
#print 'len(result):', len(result)
# Ignore the differing lines containing the date/time.
#self.failUnless(len(result) < 130 and result.find('Generated') > 1)
self.failUnless(check_result(result)) def test_3_compare_subclasses(self):
cmd = 'diff out1sub.py out2sub.py' outfile, infile = popen2.popen2(cmd) outfile, infile = popen2.popen2(cmd) result = outfile.read()
outfile.close() infile.close()
# Ignore the differing lines containing the date/time.
#self.failUnless(len(result) < 130 and result.find('Generated') > 1)
self.failUnless(check_result(result))
def check_result(result):
flag1 = 0 flag2 = 0
lines = result.split('\n') len1 = len(lines)
if len1 <= 5:
flag1 = 1
s1 = '\n'.join(lines[:4]) if s1.find('Generated') > 1:
flag2 = 1
return flag1 and flag2
# Make the test suite.
def suite():
# The following is obsolete. See Lib/unittest.py.
#return unittest.makeSuite(GenTest) loader = unittest.TestLoader() # or alternatively
# loader = unittest.defaultTestLoader
testsuite = loader.loadTestsFromTestCase(GenTest) return testsuite
# Make the test suite and run the tests.
def test():
testsuite = suite()
runner = unittest.TextTestRunner(sys.stdout, verbosity=2) runner.run(testsuite)
USAGE_TEXT = """
Usage:
python test.py [options]
Options:
h, help Display this help message.
Example:
python test.py
"""
def usage():
print USAGE_TEXT sys.exit(1)
def main():
args = sys.argv[1:]
try:
opts, args = getopt.getopt(args, 'h', ['help']) except:
usage() relink = 1
for opt, val in opts:
if opt in ('h', 'help'):
usage() if len(args) != 0:
usage() test()
if __name__ == '__main__':
main() #import pdb
#pdb.run('main()')
Notes:
● GenTest is our test suite class. It inherits from unittest.TestCase.
● Each method in GenTest whose name begins with "test" will be run as a test.
● The tests are run in alphabetic order by method name.
● Defaults in class TestLoader for the test name prefix and sort comparison function can be overridden. See 5.3.8 TestLoader Objects
http://docs.python.org/lib/testloaderobjects.html.
● A test case class may also implement methods named setUp() and
tearDown() to be run before and after tests. See 5.3.5 TestCase Objects
http://docs.python.org/lib/testcaseobjects.html. Actually, the first test method in our example should, perhaps, be a setUp() method.
● The tests use calls such as self.failUnless() to report errors. These are inherited from class TestCase. See 5.3.5 TestCase Objects
http://docs.python.org/lib/testcaseobjects.html.
● Function suite() creates an instance of the test suite.
● Function test() runs the tests.
1.9.3.3 Additional unittest features
And, the following example shows several additional features. See the notes that follow
the code:
import unittest
class UnitTests02(unittest.TestCase):
def testFoo(self):
self.failUnless(False) def checkBar01(self):
self.failUnless(False)
class UnitTests01(unittest.TestCase):
# Note 1
def setUp(self):
print 'setting up UnitTests01' def tearDown(self):
print 'tearing down UnitTests01' def testBar01(self):
print 'testing testBar01' self.failUnless(False) def testBar02(self):
print 'testing testBar02' self.failUnless(False) def function_test_1():
name = 'mona'
assert not name.startswith('mo') def compare_names(name1, name2):
if name1 < name2:
return 1
elif name1 > name2:
return 1 else:
return 0 def make_suite():
suite = unittest.TestSuite() # Note 2
suite.addTest(unittest.makeSuite(UnitTests01, sortUsing=compare_names))
# Note 3
suite.addTest(unittest.makeSuite(UnitTests02, prefix='check')) # Note 4
suite.addTest(unittest.FunctionTestCase(function_test_1)) return suite
def main():
suite = make_suite()
runner = unittest.TextTestRunner() runner.run(suite)
if __name__ == '__main__':
main()
Notes:
1. If you run this code, you will notice that the setUp and tearDown methods in class UnitTests01 are run before and after each test in that class.
2. We can control the order in which tests are run by passing a compare function to the makeSuite function. The default is the cmp builtin function.
3. We can control which methods in a test fixture are selected to be run by passing the optional argument prefix to the makeSuite function.
4. If we have an existing function that we want to "wrap" and run as a unit test, we can create a test case from a function with the FunctionTestCase function. If we do that, notice that we use the assert statement to test and possibly cause failure.
1.9.3.4 Guidance on Unit Testing
Why should we use unit tests? Many reasons, including:
● Without unit tests, corner cases may not be checked. This is especially important, since Python does relatively little compile time error checking.
● Unit tests facilitate a frequent and short design and implement and release development cycle. See ONLamp.com Extreme Python
http://www.onlamp.com/pub/a/python/2001/03/28/pythonnews.html and What is XP http://www.xprogramming.com/what_is_xp.htm.
● Designing the tests before writing the code is "a good idea".
Additional notes:
● In a test class, instance methods setUp and tearDown are run automatically before each and after each individual test.
● In a test class, class methods setUpClass and tearDownClass are run automatically once before and after all the tests in a class.
● Module level functions setUpModule and tearDownModule are run before and after any tests in a module.
● In some cases you can also run tests directly from the command line. Do the following for help:
$ python m unittest help