import pytest
pytest.__path__
Unit Tests
Disclaimer: this course is adapted from the following sources:
- Python, git & testing by Andreas Müller.
- VSCode documentation Python testing.
Tests
Tests are small pieces of code ensuring that a part of a program is working as expected.
Why tests are useful
This is why we place the uttermost importance on implementing tests along the development steps. It will help you to ensure:
- that code works correctly.
- that changes do not break anything.
- that bugs are not reintroduced.
- robustness to user errors.
- code is reachable (i.e., it will actually be executed)
- etc.
Types of tests
There are different kinds of tests, with the more important ones being:
- Unit tests: test whether a simple unit element (function, class, etc.) does the right thing.
- Integration tests: test whether the different parts used by your software work well together.
- Non-regression tests: test whether a new or modified functionality works correctly and that previous functionalities were not affected (e.g., removing a bug does not alter the rest of the code).
How to test?
Many coding languages come with their own test framework. In python
, we will focus on pytest
. It is simple though powerful. pytest
searches for all test*.py
files and runs all test*
methods found. It outputs a nice error report.
Get the path to pytest
binary as follows with python
:
This outputs a directory containing the pytest
binary, say /path/to/pytest
. Then, to add the path containing pytest
in your (Linux) system, you have to type the following command in your terminal:
$ export PATH=$PATH:/path/to/pytest $ pytest --help
Example
Let us assume we have a file inc.py
containing
def inc1(x):
return x + 1
def inc2(x):
return x + 2
Thence, the content of test_inc.py
is
from inc import inc1, inc2
# This test will work
def test_inc1():
assert inc1(3) == 4
# This test will fail
def test_inc2():
assert inc2(-1) == 4
To run these tests:
$ pytest test_inc.py
Code coverage
pytest
comes with some useful plugins. In particular, we will use the coverage report plugin.
A test coverage is a measure used to describe the degree to which the source code of a program is executed when a particular test suite runs. A program with high test coverage, measured as a percentage, has had more of its source code executed during testing: this suggests it has a lower chance of containing undetected software bugs compared to a program with low test coverage.
To install the coverage plugin simply run the pip
command:
$ pip install pytest-cov
Assuming the inc_cov.py
contains:
def inc(x):
if x < 0:
return 0
return x + 1
def dec(x):
return x - 1
and a single test is performed through the file test_inc_cov.py
from inc_cov import inc
def test_inc():
assert inc(3) == 4
then
pytest test_inc_cov.py --cov
============================= test session starts =============================
platform linux -- Python 3.10.10, pytest-7.4.2, pluggy-1.0.0
rootdir: /home/jsalmon/Documents/Mes_cours/Montpellier/HAX712X/Courses/Test
plugins: dash-2.9.3, cov-4.1.0, anyio-3.6.2
collected 1 item
test_inc_cov.py [100%]
---------- coverage: platform linux, python 3.10.10-final-0 ----------
Name Stmts Miss Cover
-------------------------------------
inc_cov.py 6 2 67%
test_inc_cov.py 3 0 100%
-------------------------------------
TOTAL 9 2 78%
===============================================================================
1 passed in 0.02s ===============================================================================
Two lines in inc_cov
module were not used. See
pytest --cov --cov-report=html test_inc_cov.py
============================= test session starts =============================
platform linux -- Python 3.10.10, pytest-7.4.2, pluggy-1.0.0
rootdir: /home/jsalmon/Documents/Mes_cours/Montpellier/HAX712X/Courses/Test
plugins: dash-2.9.3, cov-4.1.0, anyio-3.6.2
collected 1 item
test_inc_cov.py [100%]
---------- coverage: platform linux, python 3.10.10-final-0 ---------- Coverage HTML written to dir htmlcov
for details.
import hashlib
def md5(fname):
= hashlib.md5()
hash_md5 with open(fname, "rb") as f:
for chunk in iter(lambda: f.read(4096), b""):
hash_md5.update(chunk)return hash_md5.hexdigest()
By running the following line in the biketrauma/biketrauma
location:
pytest --cov-report=html --cov=biketrauma tests/
you should reach 79% of code coverage, and the report should look like this (see the htmlcov/index.html
file for an interactive report):
---------- coverage: platform linux, python 3.10.10-final-0 ----------
Name Stmts Miss Cover
----------------------------------------------------------------------
biketrauma/__init__.py 4 0 100%
biketrauma/io/Load_db.py 22 5 77%
biketrauma/io/__init__.py 3 0 100%
biketrauma/preprocess/__init__.py 0 0 100%
biketrauma/preprocess/get_accident.py 7 0 100%
biketrauma/vis/__init__.py 0 0 100%
biketrauma/vis/plot_location.py 6 4 33%
---------------------------------------------------------------------- TOTAL 42 9 79%