Test-driven development (TDD) is a software development process that has been documented considerably over recent years. At its heart is the practice of baking tests right into your everyday coding, as opposed to an afterthought.
The doctest Module
There is a standard python module called “doctest” that is useful for setting up and easy to use TDD testing framework. The doctest module searches for pieces of text that look like interactive Python sessions inside of the documentation parts of a module, and then executes (or reexecutes) the commands of those sessions to verify that they work exactly as shown, i.e. that the same results can be achieved. In other words: The help text of the module is parsed for example python sessions. These examples are run and the results are compared against the expected value.
Usage of doctest:
To use “doctest” it has to be imported. The part of an interactive Python sessions with the examples and the output has to be copied inside of the docstring the the corresponding function.
We demonstrate this way of proceeding with the following simple example. We have slimmed down the previous module, so that only the function fib is left:
import doctest
def fib(n):
“”” Calculates the n-th Fibonacci number iteratively “””
a, b = 0, 1
for i in range(n):
a, b = b, a + b
return a
We now call this module in an interactive Python shell and do some calculations:
>> from fibonacci import fib
>>> fib(0)
0
>>> fib(1)
1
>>> fib(10)
55
>>> fib(15)
610
>>>
We copy the complete session of the interactive shell into the docstring of our function. To start the module doctest we have to call the method testmod(), but only if the module is called standalone. The complete module looks like this now:
import doctest
def fib(n):
“””
Calculates the n-th Fibonacci number iteratively
>>> fib(0)
0
>>> fib(1)
1
>>> fib(10)
55
>>> fib(15)
610
>>>
“””
a, b = 0, 1
for i in range(n):
a, b = b, a + b
return a
if __name__ == “__main__”:
doctest.testmod()
If we start our module directly like this
$ python3 fibonacci_doctest.py
we get no output, because everything is okay.
To see how doctest works, if something is wrong, we place an error in our code:
We change again
a, b = 0, 1
into
a, b = 1, 1
Now we get the following, if we start our module:
$ python3 fibonacci_doctest.py
**********************************************************************
File “fibonacci_doctest.py”, line 8, in __main__.fib
Failed example:
fib(0)
Expected:
0
Got:
1
**********************************************************************
File “fibonacci_doctest.py”, line 12, in __main__.fib
Failed example:
fib(10)
Expected:
55
Got:
89
**********************************************************************
File “fibonacci_doctest.py”, line 14, in __main__.fib
Failed example:
fib(15)
Expected:
610
Got:
987
**********************************************************************
1 items had failures:
3 of 4 in __main__.fib
***Test Failed*** 3 failures.
The output depicts all the calls, which return faulty results. We can see the call with the arguments in the line following “Failed example:”. We can see the expected value for the argument in the line following “Expected:”. The output shows us the newly calculated value as well. We can find this value behind “Got:”
Test-driven Development (TDD)
In the previous chapters, we tested functions, which we had already been finished. What about testing code you haven’t yet written? You think that this is not possible? It is not only possible, it is the underlying idea of test-driven development. In the extreme case, you define tests before you start coding the actual source code. The program developer writes an automated test case which defines the desired “behaviour” of a function. This test case will – that’s the idea behind the approach – initially fail, because the code has still to be written.
The major problem or difficulty of this approach is the task of writing suitable tests. Naturally, the perfect test would check all possible inputs and validate the output. Of course, this is generally not always feasible.
We have set the return value of the fib function to 0 in the following example:
import doctest
def fib(n):
“””
Calculates the n-th Fibonacci number iteratively
>>> fib(0)
0
>>> fib(1)
1
>>> fib(10)
55
>>> fib(15)
610
>>>
“””
return 0
if __name__ == “__main__”:
doctest.testmod()
It hardly needs mentioning that the function returns except for fib(0) only wrong return values:
$ python3 fibonacci_TDD.py
**********************************************************************
File “fibonacci_TDD.py”, line 10, in __main__.fib
Failed example:
fib(1)
Expected:
1
Got:
0
**********************************************************************
File “fibonacci_TDD.py”, line 12, in __main__.fib
Failed example:
fib(10)
Expected:
55
Got:
0
**********************************************************************
File “fibonacci_TDD.py”, line 14, in __main__.fib
Failed example:
fib(15)
Expected:
610
Got:
0
**********************************************************************
1 items had failures:
3 of 4 in __main__.fib
***Test Failed*** 3 failures.
Now we have to keep on writing and changing the code for the function fib until it passes the test.
Discussion
No comments yet.