This is the first part of a series in which I want to introduce the libraries respectively frameworks I most often use, during my everyday Python projects. The possibly most indispensable framework I use is PyTest, for Test-driven development (TDD) in Python. During the following chapters I want to introduce the main concepts of TDD, why it is important and how you can utilize it in Python.
TDD is a programming approach in which -it is hard to believe- a test function is written for each function you are going to implement. In this context the initial idea behind TDD is the demand for the test function to be written before(!) the actual target function, the so called production code, is implemented. Nevertheless, if you ask me this is a matter of taste and to be honest, better you write the test after the production code, than not writing the test at all, right?
The idea is pretty simple: A testing method calls the target function with a set of appropriate parameters and formulates an assert about how the result should look like. If this requirement is not fulfilled, it can be assumed that something went wrong. Let me give you a quite simple example on this.
Let's assume we want to implement a function that calculates the sum of two integer values, called add_two_integers with two parameters x and y.
According to the prior mentioned demand of writing the test before the production code, we start implementing an appropriate testing function that checks whether the parameters 1 and 1 return a correct value, using the target function.
def test_add_two_integers():
assert add_two_integers(1, 1) == 2
We will now run this test, although -or just because- we know it will fail, which is completely obvious, as we not yet implemented the target function.
platform darwin -- Python 2.7.15, pytest-3.5.0, py-1.5.3, pluggy-0.6.0
rootdir: /Users/localhostport80/Desktop/, inifile:
collected 1 item
test_add.py F [100%]
======= FAILURES =======
======= test_add_two_integers =======
def test_add_two_integers():
> assert add_two_integers(1, 1) == 2
E NameError: global name 'add_two_integers' is not defined
test_add.py:4: NameError
======= 1 failed in 0.05 seconds =======
Afterwards we will implement the respective production code.
def add_two_integers(x, y):
""" Simply adds to integer values.
Args:
x (int): The first value.
y (int): The second value.
Returns:
int: The result of x + y.
"""
return x + y
Running the same test again will result in a successful test.
======= test session starts =======
platform darwin -- Python 2.7.15, pytest-3.5.0, py-1.5.3, pluggy-0.6.0
rootdir: /Users/localhostport80/Desktop/, inifile:
collected 1 item
test_add.py . [100%]
======= 1 passed in 0.01 seconds =======
Congratulations! You successfully ran your first test
This is now where PyTest joins the game. PyTest provides an easy interface to TDD, as explained above.
usage: pytest [options] [file_or_dir] [file_or_dir] [...]
positional arguments:
file_or_dir
What it actually does is, it recursively searches for testing modules in a given set of directories. Those modules are supposed to start with test_<Name of the module> and each method within these modules, representing a testing method, should start with the prefix test_ and hence should look the like following test_<Name of the target method>.
Running the tests again on a small project as shown in the above screenshot will result in an output as shown in the following.
======= test session starts =======
platform darwin -- Python 2.7.15, pytest-3.5.0, py-1.5.3, pluggy-0.6.0
rootdir: /Users/localhostport80/Desktop/MyProject, inifile:
collected 3 items
tests/test_misc.py .. [ 66%]
tests/test_tools.py . [100%]
======= 3 passed in 0.02 seconds =======
I will now sketch the idea behind the most important kinds of tests.
From my point of view TDD is a great approach that helps you to stay in the track. Especially in huge projects with hundereds or thousands of methods and modules -I think every programmer will agree on this- refactoring is a challenging task. With TDD at any point in time you can easily make sure every part of your software is working, as it is supposed to.