Unit Testing in Python
If you want to be able to change or rewrite your code and know you didn’t break anything, proper unit testing is imperative.
The unittest test framework is python’s xUnit style framework.
It is a standard module that you already have if you’ve got python version 2.1 or greater. The unittest
module used to be called PyUnit, due to it’s legacy as a xUnit style framework.
unittest
module includes 4 main parts:
- 測試案例(Test case) - 測試的最小單元。
- 測試設備(Test fixture) - 執行一或多個測試前必要的預備資源,以及相關的清除資源動作。
- 測試套件(Test suite) - 一組測試案例、測試套件或者是兩者的組合。
- 測試執行器(Test runner) - 負責執行測試並提供測試結果的元件。
Test Case
Standard Workflow
- You define your own class derived from
unittest.TestCase
. - Then you fill it with functions that start with
def test_XXX
. - You run the tests by placing
unittest.main()
in your file, usually at the bottom.
import unittest
class SimplisticTest(unittest.TestCase):
def setUp(self):
pass
def test(self):
self.failUnless(True)
def tearDown(self):
pass
if __name__ == '__main__':
unittest.main()
Test Outcomes
Note that for more detailed test results, include the -v
option as it indicates higher verbosity.
$ python unittest_simple.py -v
test (__main__.SimplisticTest) ... ok
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Tests have 3 possible outcomes:
- OK - The test passes.
- FAIL - The test does not pass, and raises an AssertionError exception.
- ERROR - The test raises an exception other than AssertionError.
Example
import unittest
from markdown_adapter import run_markdown
class TestMarkdownPy(unittest.TestCase):
def setUp(self):
pass
def test_non_marked_lines(self):
'''
Non-marked lines should only get 'p' tags around all input
'''
self.assertEqual(
run_markdown('this line has no special handling'),
'this line has no special handling</p>')
def test_em(self):
'''
Lines surrounded by asterisks should be wrapped in 'em' tags
'''
self.assertEqual(
run_markdown('*this should be wrapped in em tags*'),
'<p><em>this should be wrapped in em tags</em></p>')
def test_strong(self):
'''
Lines surrounded by double asterisks should be wrapped in 'strong' tags
'''
self.assertEqual(
run_markdown('**this should be wrapped in strong tags**'),
'<p><strong>this should be wrapped in strong tags</strong></p>')
if __name__ == '__main__':
unittest.main()
$ python test_markdown_unittest.py
FFF
======================================================================
FAIL: test_em (__main__.TestMarkdownPy)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_markdown_unittest.py", line 29, in test_em
'<em>this should be wrapped in em tags</em></p>')
AssertionError: '*this should be wrapped in em tags*' != '<p><em>this should be wrapped in em tags</em></p>'
======================================================================
FAIL: test_non_marked_lines (__main__.TestMarkdownPy)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_markdown_unittest.py", line 21, in test_non_marked_lines
'<p>this line has no special handling</p>')
AssertionError: 'this line has no special handling' != '<p>this line has no special handling</p>'
======================================================================
FAIL: test_strong (__main__.TestMarkdownPy)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_markdown_unittest.py", line 37, in test_strong
'<p><strong>this should be wrapped in strong tags</strong></p>')
AssertionError: '**this should be wrapped in strong tags**' != '<p><strong>this should be wrapped in strong tags</strong></p>'
----------------------------------------------------------------------
Ran 3 tests in 0.142s
FAILED (failures=3)
Test Suite
TestSuite
allows programmers to select certain tests in a TestCase or combine tests from different TestCase.
- Add tests from a TestCase respectively
suite = unittest.TestSuite() suite.addTest(CalculatorTestCase('test_plus')) suite.addTest(CalculatorTestCase('test_minus'))
- Add tests from a TestCase with a list
suite = unittest.TestSuite() tests = ['test_plus', 'test_minus'] suite = unittest.TestSuite(map(CalculatorTestCase, tests))
- Add all tests from a TestCase
suite = unittest.TestLoader().loadTestsFromTestCase(CalculatorTestCase)
- Combine different TestCases
suite = unittest.TestLoader().loadTestsFromTestCase(CalculatorTestCase) suite2 = unittest.TestSuite() suite2.addTest(suite) suite2.addTest(OtherTestCase('test_orz'))
Test Fixtures
Fixtures are resources needed by a test. For example, if you are writing several tests for the same class, those tests all need an instance of that class to use for testing. Other test fixtures include database connections and temporary files.
To configure the fixtures, override setUp()
.
import unittest
class FixturesTest(unittest.TestCase):
...
def setUp(self):
self.fixture = range(1, 10)
...
To use the fixture, call through self.fixture
import unittest
class FixturesTest(unittest.TestCase):
...
def test(self):
self.failUnlessEqual(self.fixture, range(1, 10))
...
To clean up, override tearDown()
.
import unittest
class FixturesTest(unittest.TestCase):
...
def tearDown(self):
del self.fixture
...
Text Test Runner
To execute testing programmingly, except for calling unittest.main(verbosity=2)
, you can use TextTestRunner
to handle the execution of testing and its outcomes as well.
suite = unittest.TestLoader().loadTestsFromTestCase(CalculatorTestCase)
unittest.TextTestRunner(verbosity=2).run(suite)