Introduction to Testing

This introduction is specifically aimed at developers new to testing or new to testing in python. Say we have a python module called test_guide containing a file base.py with a class called Resource in it that has some methods on it.

Code to test

class Resource(object):
    """
    A base class for API resources
    """
    ...
    ...
    def build_param_string(self, params):
        """
        This is a simple helper method to build a parameter string. It joins
        all list elements that evaluate to ``True`` with an ampersand, '&'

        .. code-block:: python

            >>> parameters = Resource().build_param_string([
            ...    'filter[name]=dev', None, 'page=1'
            ...    ])
            ...
            >>> print parameters
            filter[name]=dev&page=1

        :type params: list
        :param params: The parameters to build a string with

        :rtype: str
        :return: The compiled parameter string
        """
        return '&'.join([p for p in params if p])

Setup

We first need a tests directory in our module with a file called base_tests.py in it. Our example structure looks like this:

$  ls -alR tests_guide
total 24
drwxr-xr-x   6 ubuntu  ubuntu   204 Jul 22 10:39 .
drwxr-xr-x  24 ubuntu  ubuntu   816 Jul 22 10:36 ..
-rw-r--r--   1 ubuntu  ubuntu    47 Jul 22 10:05 __init__.py
-rw-r--r--   1 ubuntu  ubuntu  1520 Jul 22 10:39 base.py
drwxr-xr-x   4 ubuntu  ubuntu   136 Jul 22 10:45 tests
-rw-r--r--   1 ubuntu  ubuntu    19 Jul 22 10:05 version.py

./tests:
total 8
drwxr-xr-x  4 ubuntu  ubuntu  136 Jul 22 10:45 .
drwxr-xr-x  6 ubuntu  ubuntu  204 Jul 22 10:39 ..
-rw-r--r--  1 ubuntu  ubuntu    0 Jul 22 10:05 __init__.py
-rw-r--r--  1 ubuntu  ubuntu  125 Jul 22 10:05 base_tests.py

Writing an example test

Now we want to test the build_param_string() method on our Resource class. Since we only want the results that evaluate to True in the method, we need to test it with a bunch of values that evaluate to False.

from datetime import time
import unittest

from tests_guide.base import Resource


class ResourceTests(unittest.TestCase):
    def test_build_param_string(self):
        """
        Tests .build_param_string() returns the correct string
        """
        resource = Resource()

        test_params = [
            False,
            'filter[name]=dev',
            None,
            'page=1',
            0,
            '',
            [],
            {},
            time(0)
        ]

        param_str = resource.build_param_string(test_params)

        self.assertEqual(param_str, 'filter[name]=dev&page=1')

Did you see what happened? We first have a test class to group our tests of the Resource class. Then we have a method test_build_param_string() that actually runs the tests. Tests in python are assertions, and the builtin TestCase has a lot of helpful assertions. For this test, we simply assert that our method’s output is equal to the output we expect by calling assertEqual(). We could just as easily used the built in assert keyword like so:

assert param_str == 'filter[name]=dev&page=1'

If our method’s output had been different than what we expected, our test would have raised an AssertionError and the test would be marked as Failed.

Running the tests

In all of the Ambition python templates a testing framework is built in and there is a Contributing section with instructions on how to run the tests. It may differ slightly depending on if the project is a Django app or a pure Python project.

If the project is pure Python, you can run:

$ python setup.py nosetests

If the project is a Django app or Django project:

$ python setup.py test

Example output:

$ python setup.py test
running test
running egg_info
writing top-level names to ambition_py_tests_guide.egg-info/top_level.txt
writing ambition_py_tests_guide.egg-info/PKG-INFO
writing requirements to ambition_py_tests_guide.egg-info/requires.txt
writing dependency_links to ambition_py_tests_guide.egg-info/dependency_links.txt
reading manifest file 'ambition_py_tests_guide.egg-info/SOURCES.txt'
reading manifest template 'MANIFEST.in'
writing manifest file 'ambition_py_tests_guide.egg-info/SOURCES.txt'
running build_ext
nosetests tests_guide --verbosity=1
Creating test database for alias 'default'...
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK
Destroying test database for alias 'default'...