10. Adding unit testing to your project

Introduction

Since we are using python for most of our work, we do have the ability to use the standards for unit testing which are available in it.

Python comes with a powerful framework to support unit testing, which can easily be integrated into the services we create.

Setup a test case

One of the first services we created was the “Hello world” example, now we are going to test it using the python unit testing framework.

To start we create a package named tests in our work directory and make sure there is an empty __init__.py file in it (which makes it a package).

Then we start to create our first test case.

tests/test_helloworld.py
 1import unittest
 2
 3
 4class TestHelloWorld(unittest.TestCase):
 5    def setUp(self):
 6        # import and execute our service
 7        import services.valuea.samples.helloworld
 8        self._service = services.valuea.samples.helloworld.Service()
 9
10    def tearDown(self):
11        # nothing to teardown when we have finished testing "Hello world"
12        pass
13
14    def test_response_message(self):
15        self.assertIn('message', self._service.execute(), 'message expected from service')
16        self.assertEqual(self._service.execute()['message'], 'hello world!')
17        self.assertEquals(len(self._service.execute()), 1, 'there should only be one result item')
18

Services are self contained items, which means don’t have to validate their behavior using either a frontend application or a message queue. This simplifies testing.

Deep dive into the test

Our example test module starts with an import of unittest, which is the standard module for unit testing in python and doesn’t require additional modules.

We first inherit unittest.TestCase to define a new test case, this usually comes with two special methods and one or more test methods.

Most of the time a unit test starts with a setUp() which prepares the environment to run your tests, depending on the type of test this can contain a very simple setup like the one above or can be responsible to prepare a more advanced one which makes sure the needed data is available before executing any tests.

Note

When designing unit tests, always make sure that test cases are restartable, meaning that multiple executions of the same case lead to the same result. When dealing with data, you should make sure that your setUp() prepares all requirements needed to run the case.

Our example only contains an empty tearDown(), since we don’t really have things to cleanup after we’re done testing. When preparing a case with data, you might want to remove the test data once the testing is completed.

Finally we can start adding tests, our example only contains one named test_response_message() with three steps.

Note

The unit testing framework gathers all methods starting with test and executes them in order of appearance.

Every test should contain one or more assert calls, which validates what we expect and raises an error for the unit testing framework in case the assertion fails.

Step by step our example validates three assumptions:

  1. The service response should contain an item named ‘message’

  2. The content of message should be (exactly) ‘hello world!’

  3. We don’t expect anything else in the response then message, so we expect the number of responses to be exactly 1.

Tip

A full list of possible assertions can be found in the python documentation, available at https://docs.python.org/2/library/unittest.html#assert-methods

Running our tests

You either can start a specific test or let unittest collect and run all cases available for your project, we choose to run all at once for this example letting the framework decide what to do using “discover” mode.

The first line contains the command executed, within our project directory python -m unittest discover followed by the output from our unit test(s).

1$ python -m unittest discover
2.
3----------------------------------------------------------------------
4Ran 1 test in 1.213s
5
6OK

Next step is to make sure we fail our test, to do so, go to our original helloworld service and change the response in something like ‘hello rest of world!’.

 1$ python -m unittest discover
 2F
 3======================================================================
 4FAIL: test_response_message (tests.test_helloworld.TestHelloWorld)
 5----------------------------------------------------------------------
 6Traceback (most recent call last):
 7  File "/Users/ad/Develop/valuea/documentation/source/code_examples/tests/test_helloworld.py", line 16, in test_response_message
 8    self.assertEqual(self._service.execute()['message'], 'hello world!')
 9AssertionError: 'hello rest of world!' != 'hello world!'
10
11----------------------------------------------------------------------
12Ran 1 test in 1.013s
13
14FAILED (failures=1)

Summarize

Extending your code with unit tests or starting to develop tests before building the code using a test driven approach can be challenging. Simple services which come with minimal dependencies are easy to test, the more (data) dependencies the tougher the challenge.

The ValueA framework services can easily be integrated into standard python unit testing, which prevents reinventing the wheel on this subject.