Preface
The Python unit testing framework, sometimes referred to as “PyUnit,” is a Python language version of JUnit, by Kent Beck and Erich Gamma. JUnit is, in turn, a Java version of Kent’s Smalltalk testing framework. Each is the de facto standard unit testing framework for its respective language.
Basic example
The unittest module provides a rich set of tools for constructing and running tests. This section demonstrates that a small subset of the tools suffice to meet the needs of most users. Here is a short script to test three functions from the random module:
- import random
- import unittest
- class TestSequenceFunctions(unittest.TestCase):
- def setUp(self):
- self.seq = range(10)
- def test_shuffle(self):
- # make sure the shuffled sequence does not lose any elements
- random.shuffle(self.seq)
- self.seq.sort()
- self.assertEqual(self.seq, range(10))
- # should raise an exception for an immutable sequence
- self.assertRaises(TypeError, random.shuffle, (1,2,3))
- def test_choice(self):
- element = random.choice(self.seq)
- self.assertTrue(element in self.seq)
- def test_sample(self):
- self.assertRaises(ValueError, random.sample, self.seq, 20)
- for element in random.sample(self.seq, 5):
- self.assertTrue(element in self.seq)
- if __name__ == '__main__':
- unittest.main()
The crux of each test is a call to assertEqual() to check for an expected result; assertTrue() to verify a condition; or assertRaises() to verify that an expected exception gets raised. These methods are used instead of the assert statement so the test runner can accumulate all test results and produce a report.
When a setUp() method is defined, the test runner will run that method prior to each test. Likewise, if a tearDown() method is defined, the test runner will invoke that method after each test. In the example, setUp() was used to create a fresh sequence for each test.
The final block shows a simple way to run the tests. unittest.main() provides a command-line interface to the test script. When run from the command line, the above script produces an output that looks like this:
Instead of unittest.main(), there are other ways to run the tests with a finer level of control, less terse output, and no requirement to be run from the command line. For example, the last two lines may be replaced with:
- suite = unittest.TestLoader().loadTestsFromTestCase(TestSequenceFunctions)
- unittest.TextTestRunner(verbosity=2).run(suite)
The above examples show the most commonly used unittest features which are sufficient to meet many everyday testing needs. The remainder of the documentation explores the full feature set from first principles.
Command-Line Interface
The unittest module can be used from the command line to run tests from modules, classes or even individual test methods:
You can pass in a list with any combination of module names, and fully qualified class or method names.
You can run tests with more detail (higher verbosity) by passing in the -v flag:
For a list of all the command-line options:
Test Discovery
Unittest supports simple test discovery. In order to be compatible with test discovery, all of the test files must be modules or packages importable from the top-level directory of the project (this means that their filenames must be valid identifiers).
Test discovery is implemented in TestLoader.discover(), but can also be used from the command line. The basic command-line usage is:
The discover sub-command has the following options:
-v, --verbose
-s, --start-directory directory
-p, --pattern pattern
-t, --top-level-directory directory
The -s, -p, and -t options can be passed in as positional arguments in that order. The following two command lines are equivalent:
As well as being a path it is possible to pass a package name, for example myproject.subpackage.test, as the start directory. The package name you supply will then be imported and its location on the filesystem will be used as the start directory.
Caution:
Organizing test code
The basic building blocks of unit testing are test cases — single scenarios that must be set up and checked for correctness. In unittest, test cases are represented by instances of unittest‘s TestCase class. To make your own test cases you must write subclasses of TestCase, or use FunctionTestCase.
An instance of a TestCase-derived class is an object that can completely run a single test method, together with optional set-up and tidy-up code. The testing code of a TestCase instance should be entirely self contained, such that it can be run either in isolation or in arbitrary combination with any number of other test cases. The simplest TestCase subclass will simply override the runTest() method in order to perform specific testing code:
- import unittest
- class Widget:
- def __init__(self, name):
- self.name = name
- self.width = 50
- self.height = 50
- def size(self):
- return (self.width, self.height)
- class DefaultWidgetSizeTestCase(unittest.TestCase):
- def runTest(self):
- widget = Widget('The widget')
- self.assertEqual(widget.size(), (50, 50), 'incorrect default size')
- if __name__ == '__main__':
- unittest.main()
The way to run a test case will be described later. For now, note that to construct an instance of such a test case, we call its constructor without arguments:
- testCase = DefaultWidgetSizeTestCase()
- import unittest
- class Widget:
- def __init__(self, name):
- self.name = name
- self.width = 50
- self.height = 50
- def size(self):
- return (self.width, self.height)
- def resize(self, width, height):
- self.width=width
- self.height=height
- class SimpleWidgetTestCase(unittest.TestCase):
- def setUp(self):
- self.widget = Widget('The widget')
- class DefaultWidgetSizeTestCase(SimpleWidgetTestCase):
- def runTest(self):
- self.assertEqual(self.widget.size(), (50,50),
- 'incorrect default size')
- class WidgetResizeTestCase(SimpleWidgetTestCase):
- def runTest(self):
- self.widget.resize(100,150)
- self.assertEqual(self.widget.size(), (100,150),
- 'wrong size after resize')
- if __name__ == '__main__':
- unittest.main()
- ...
- class SimpleWidgetTestCase(unittest.TestCase):
- def setUp(self):
- self.widget = Widget('The widget')
- def tearDown(self):
- self.widget.dispose()
- self.widget = None
- ...
Often, many small test cases will use the same fixture. In this case, we would end up subclassing SimpleWidgetTestCase into many small one-method classes such as DefaultWidgetSizeTestCase. This is time-consuming and discouraging, so in the same vein as JUnit, unittest provides a simpler mechanism:
- import unittest
- class WidgetTestCase(unittest.TestCase):
- def setUp(self):
- self.widget = Widget('The widget')
- def tearDown(self):
- self.widget.dispose()
- self.widget = None
- def test_default_size(self):
- self.assertEqual(self.widget.size(), (50,50),
- 'incorrect default size')
- def test_resize(self):
- self.widget.resize(100,150)
- self.assertEqual(self.widget.size(), (100,150),
- 'wrong size after resize')
- defaultSizeTestCase = WidgetTestCase('test_default_size')
- resizeTestCase = WidgetTestCase('test_resize')
- widgetTestSuite = unittest.TestSuite()
- widgetTestSuite.addTest(WidgetTestCase('test_default_size'))
- widgetTestSuite.addTest(WidgetTestCase('test_resize'))
- def suite():
- suite = unittest.TestSuite()
- suite.addTest(WidgetTestCase('test_default_size'))
- suite.addTest(WidgetTestCase('test_resize'))
- return suite
- def suite():
- tests = ['test_default_size', 'test_resize']
- return unittest.TestSuite(map(WidgetTestCase, tests))
- suite = unittest.TestLoader().loadTestsFromTestCase(WidgetTestCase)
Often it is desirable to group suites of test cases together, so as to run tests for the whole system at once. This is easy, since TestSuite instances can be added to a TestSuite just as TestCase instances can be added to a TestSuite:
- suite1 = module1.TheTestSuite()
- suite2 = module2.TheTestSuite()
- alltests = unittest.TestSuite([suite1, suite2])
Re-using old test code
Some users will find that they have existing test code that they would like to run from unittest, without converting every old test function to a TestCase subclass. For this reason, unittest provides a FunctionTestCase class. This subclass of TestCase can be used to wrap an existing test function. Set-up and tear-down functions can also be provided.
Given the following test function:
- class Something:
- def __init__(self, name):
- self.name = "test"
- def makeSomething():
- return Something("test")
- def testSomething():
- something = makeSomething()
- assert something.name is not None
- testcase = unittest.FunctionTestCase(testSomething)
- testcase = unittest.FunctionTestCase(testSomething,
- setUp=makeSomethingDB,
- tearDown=deleteSomethingDB)
Note.
Skipping tests and expected failures
Unittest supports skipping individual test methods and even whole classes of tests. In addition, it supports marking a test as a “expected failure,” a test that is broken and will fail, but shouldn’t be counted as a failure on a TestResult.
Skipping a test is simply a matter of using the skip() decorator or one of its conditional variants.
Basic skipping looks like this:
- class MyTestCase(unittest.TestCase):
- @unittest.skip("demonstrating skipping")
- def test_nothing(self):
- self.fail("shouldn't happen")
- @unittest.skipIf(mylib.__version__ < (1, 3),
- "not supported in this library version")
- def test_format(self):
- # Tests that work for only a certain version of the library.
- pass
- @unittest.skipUnless(sys.platform.startswith("win"), "requires Windows")
- def test_windows_support(self):
- # windows specific testing code
- pass
Classes can be skipped just like methods:
- @unittest.skip("showing class skipping")
- class MySkippedTestCase(unittest.TestCase):
- def test_not_run(self):
- pass
Expected failures use the expectedFailure() decorator.
- class ExpectedFailureTestCase(unittest.TestCase):
- @unittest.expectedFailure
- def test_fail(self):
- self.assertEqual(1, 0, "broken")
- def skipUnlessHasattr(obj, attr):
- if hasattr(obj, attr):
- return lambda func: func
- return unittest.skip("{!r} doesn't have {!r}".format(obj, attr))
* unittest.skip(reason)
* unittest.skipIf(condition, reason)
* unittest.skipUnless(condition, reason)
* unittest.expectedFailure()
* exception unittest.SkipTest(reason)
Skipped tests will not have setUp() or tearDown() run around them. Skipped classes will not have setUpClass() or tearDownClass() run.
Supplement
* unittest - Classes and functions
沒有留言:
張貼留言