Table of Contents
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.
In this post, I’ll cover the basics of how to create and run a simple test using unittest.
Then I’ll show how I’m using it to test markdown.py.
Overview of unittest
The unittest module used to be called PyUnit, due to it’s legacy as a xUnit style framework.
It works much the same as the other styles of xUnit, and if you’re familiar with unit testing in other languages, this framework (or derived versions), may be the most comfortable for you.
The standard workflow is:
- You define your own class derived from unittest.TestCase.
- Then you fill it with functions that start with ’test_'.
- You run the tests with
python -m unittest
orpython -m unittest discover
.
One of the many benifits of unittest, that you’ll use when your tests get bigger than the toy examples I’m showing on this blog, is the use of ‘setUp’ and ’tearDown’ functions to get your system ready for the tests.
Like the doctest introduction, I’ll run through a simple example first, then show how I’m using unittest for testing markdown.py.
unittest example
Using the same unnecessary_math.py module that I wrote in the
doctest intro, here’s some example test
code to test my ‘multiply’ function.
test_um_unittest.py:
import unittest
from unnecessary_math import multiply
class TestUM(unittest.TestCase):
def setUp(self):
pass
def test_numbers_3_4(self):
self.assertEqual( multiply(3,4), 12)
def test_strings_a_3(self):
self.assertEqual( multiply('a',3), 'aaa')
In this example, I’ve used assertEqual()
. The unittest framework has a whole bunch of assertBlah()
style functions like assertEqual()
. Once you have a reasonable reference for all of the assert functions bookmarked, working with unnittest is pretty powerful and easy.
Aside from the tests you write, most of what you need to do can be accomplished with the test fixture methods such as setUp, tearDown, setUpClass, tearDownClass, etc.
Running unittests
Run the tests with python -m unittest
.
> python -m unittest test_um_unittest.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
## Verbose with `-v`
The output of unittest is pretty terse.
If you'd like to see the test names, use `-v`.
$ python -m unittest -v test_um_unittest.py test_numbers_3_4 (test_um_unittest.TestUM.test_numbers_3_4) … ok test_strings_a_3 (test_um_unittest.TestUM.test_strings_a_3) … ok
Ran 2 tests in 0.000s
OK
## Putting a `__name__ == '__main__'` section in the file.
There's an older style of putting something like this at the end of a test file.
```python
if __name__ == '__main__':
unittest.main()
This allows us to run all of the test code just by running the file.
So instead of python -m unittest test_um_unittest.py
, you can just say python test_um_unittest.py
.
However, I don’t usually do this when using unittest anymore.
Test discovery
Let’s say that you’ve got a bunch of test files. It would be annoying to have to run each test file separately. That’s where test discovery comes in handy.
In our case, all of my test code (one file for now) is in ‘simple_example’.
To run all of the unittests in there, use python -m unittest discover simple_example
, with or without the ‘-v’, like this:
> python -m unittest discover simple_example
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
> python -m unittest discover -v simple_example
test_numbers_3_4 (test_um_unittest.TestUM.test_numbers_3_4) ... ok
test_strings_a_3 (test_um_unittest.TestUM.test_strings_a_3) ... ok
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
unittest example with markdown.py
Now, I’ll throw unittest at my markdown.py project.
This is going to be pretty straightforward, as the tests are quite similar to the doctest versions, just formatted with all of the unittest boilerplate stuff, especially since I don’t need to make use of startUp or tearDown fixtures.
test_markdown_unittest.py:
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>')
Testing markdown.py
And now we can see that everything is failing (as expected).
> python -m unittest 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)
One interesting thing to note as compared to doctest. Only actual tests are counted.
I have 3 tests. And unittest gets that right.
Doctest lists 4 tests, with one of them passing. What’s the 4th? It’s the import statement.
Every statement is counted in doctest, so the counts are quite a bit wacky, if you ask me. The counts are way more meaningful in unittest.
More unittest info
The python.org page on unittest is a great source for information on unittest.
If you’ve got another favorite tutorial or reference for unittest, please leave a comment.
Also, the code shown here is available on github.com/okken/markdown.py
Next
Now that the basics of doctest and unittest are done, I’ll get into some of the real fun by exploring nose, then pytest. Then, before getting onto some other fun topics, I probably should get my markdown.py script to do something. In the process of doing that, I’ll probably have at least one post talking about my use of regular expressions in python.
Updates June 27, 2025
- noted that the
__name__ == '__main__'
thing is optional - changed the discovery section a bit
- I may still have discovery not quite right, as I don’t really use unittest that much