Categories
Blog Software Development

Test Driven Development – A practical Example

In this article I demonstrate the principles of Test Driven Development on a real project. I will guide you step by step through the development of a working application.

The Project

The application I am going to develop in this example is a calculator that can be used on the command line.

The application is written in Python 3.6. I use the Python unittest framework and the Python mock framework. Both are shipped with Python (if you have at least version 3.3), so you don’t have to install them yourself.

Step One

At the beginning of the development session, we start with a list of functionalities that we want to implement. Using the list, we make sure that we won’t forget any task. And it helps us to focus on one task at a time because we don’t have to think about the other tasks. During the development, we will cross the tasks off, one after the other. It can also grow over time as we find more aspects that need to be handled.

This is the list of tasks that come to mind at the beginning of the session:

  • Empty Input
  • Invalid Input
  • Addition
  • Subtraction
  • Multiplication
  • Division
  • Bracketed Expressions

As we start the project from scratch, we want to give it some structure now. In order to make progress in that direction, let’s choose the simplest task from the list: handling an empty input.

import unittest

class TestCalculatorApplication(unittest.TestCase):
    def test_empty_input(self):
        app = CalculatorApplication()
        output = app.calculate("")
        self.assertEqual(output, "0")

The test shows the first steps that we are going take:

  1. We want to create a class CalculatorApplication. It will act as the central object of the application, which will be used to perform all calculations.
  2. The class CalculatorApplication shall have a method calculate that will take a string with the expression to calculate and return the output as a string.
  3. If the input is empty, then the value “0” shall be returned.
  4. The test fixture class TestCalculatorApplication will group all tests for CalculatorApplication.

To run the test from the command line, we can use the following command. It scans for files in the directory that contain test fixtures – classes that start with Test and are derived from unittest.TestCase. Then it executes the tests in these fixtures.

python -m unittest

The test fails. The error message tells us that the class CalculatorApplication is not defined:

ERROR: test_empty_input (testCalculatorApplication.TestCalculatorApplication)
----------------------------------------------------------------------
Traceback (most recent call last):
  File ".../testCalculatorApplication.py", line 5, in test_empty_input
    output = CalculatorApplication.calculate("")
NameError: name 'CalculatorApplication' is not defined

At this time, we are in the first phase of the TDD cycle. We have written a new test but it is failing. It is red.

Let’s fix that by defining the class CalculatorApplication and giving it a method calculate, using a dummy implementation:

class CalculatorApplication():
    def calculate(self, expression):
        return ""

Let’s execute the test again. It still fails. But the error message is different:

FAIL: test_empty_input (testCalculatorApplication.TestCalculatorApplication)
----------------------------------------------------------------------
Traceback (most recent call last):
  File ".../testCalculatorApplication.py", line 8, in test_empty_input
    self.assertEqual(output, "0")
AssertionError: '' != '0'

This new error message shows us that the test works correctly – The method calculate returns a value that is not expected by the test, and that is why the test fails.

Let’s make the test green by changing the dummy implementation to return the correct value:

class CalculatorApplication():
    def calculate(self, expression):
        return "0"

The test passes:

Ran 1 test in 0.000s

OK

This completes the second phase – the test is now green. We will skip the third phase – refactor – because there is nothing to refactor yet.

Summary of Step One

The first test is in place. This seems like a tiny step, but is has brought us some progress:

  • We know that our toolchain works.
  • We have our first class that we can extend later.
  • We have a first realistic test case that will prevent us from introducing errors during later refactorings.

We can now check the item “Empty Input” in our list.

 

Leave a Reply

Your email address will not be published. Required fields are marked *