Python Testing
This post was written after going over this brilliant writeup. I use some of the code samples presented there, as well.
Doctests
The most basic way to test in python is by using doctests. This can be achieved by embedding the tests in the docstring:
def multiply(a, b):
"""
>>> multiply(4, 3)
12
>>> multiply('a', 3)
'aaa'
"""
return a * b
Then, in order to run the tests, we do:
$ python -m doctest -v
CAVEAT: We probably want to avoid this, since it has a lost of unpredictable behavior. Not to mention the fact that the tests must be written in files and classes that might not exist yet!! (Test Driven Development)
Unittest
This is the most basic test framework provided with Python’s standard libraries that can actually be considered a testing framework.
STEPS:
-
Create a python module
-
Ceate “Test Classes” that inherit from
unittest.TestCase
. -
Within the classes, define methods that start with test.
-
Optional: Override the
setUp
andtearDown
fixture methods. -
You run the tests by placing
unittest.main()
in your file. -
OR: You can try to let python discover and run the tests using
python -m unittest discover simple_example
.
EXAMPLE:
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')
if __name__ == '__main__':
unittest.main()
CAVEAT: In python world, what I presented is considered “Too much boiler plate code!”. Other frameworks can be used on top of unittest to simplify the process.
Nose
“Nose extends unittest to make testing easier”. You can get started by creating a file and dumping test functions that start with test
. That’s it. You can use the default assert
statements that come with python for the assertions.
Fixtures: They are a bit weird. The only useful thing to remember is the use of setup_module
and teardown_module
functions, for module level fixtures.
EXAMPLE:
from unnecessary_math import multiply
def setup_module(module):
print ("setup_module before anything in this file")
def teardown_module(module):
print ("teardown_module after everything in this file")
def test_numbers_3_4():
print 'test_numbers_3_4'
assert multiply(3,4) == 12
def test_strings_a_3():
print 'test_strings_a_3'
assert multiply('a',3) == 'aaa'
Running nose:
$ nosetests -v test_um_unittest.py
CAVEAT: The biggest caveat, IMHO, is the lack of ‘memorable structure’. I can’t remember half the syntax nose imposes! Not to mention that it is an external library that I had to download.
Py.Test
If you ever want to be structured and portable, just go with unittest
. If that’s not the case, which is more often than not, then Py.Test
FTW.
It is almost identical to nose
, minus the unecessary clutter. Hence, presenting some examples shall suffice.
RUNNING PY.TEST:
$ py.test test_um_pytest.py
EXAMPLES:
from unnecessary_math import multiply
# module functions
def setup_module(module):
print ("setup_module")
def teardown_module(module):
print ("teardown_module")
# Before/After each function
def setup_function(function):
print ("setup_function")
def teardown_function(function):
print ("teardown_function")
# Sample test functions
def test_numbers_3_4():
print 'actual test code'
assert multiply(3,4) == 12
def test_strings_a_3():
print 'actual test code'
assert multiply('a',3) == 'aaa'
# Very simple class
class TestUM:
# Class fixtures
def setup_class(cls):
print ("setup_class")
def teardown_class(cls):
print ("teardown_class")
# Method fixtures
def setup_method(self, method):
print ("setup_method")
def teardown_method(self, method):
print ("teardown_method")
# Actual test methods
def test_numbers_5_6(self):
print 'actual test code'
assert multiply(5,6) == 30
def test_strings_b_2(self):
print 'actual test code'
assert multiply('b',2) == 'bb'
Conclusion:
I have been convinced by this exercise to use TDD as much as possible, and probably in all future projects! Well, honestly, I still think using it for an iOS app taking it a bit too far, but hey! We can definitely use it for backend development, which is what matters the most now.