Python Testing

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:

  1. Create a python module

  2. Ceate “Test Classes” that inherit from unittest.TestCase.

  3. Within the classes, define methods that start with test.

  4. Optional: Override the setUp and tearDown fixture methods.

  5. You run the tests by placing unittest.main() in your file.

  6. 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.