Coverage with pytest-cov
A crucial metric in testing is code coverage, indicating the percentage of code covered by at least one test. The goal is 100% code coverage.
Define an operate function that takes two parameters a and b and performs an addition or subtraction based on the operation.
# example.py
def operate(a, b, operation):
if operation == "sum":
return a + b
if operation == "subtraction":
return a - b
else:
raise ValueError(f"Invalid operation: {operation}")
Create a file with a test.
# example_test.py
import pytest
from example import operate
def test_operate():
assert operate(2, 3, "sum") == 5
Install the pytest-cov package.
pip install pytest-cov
Running tests with --cov provides a coverage report. Here, it is 50%.
pytest --cov=example
# Name Stmts Miss Cover
# --------------------------------
# example.py 6 3 50%
It is 50% because only the sum operation is tested, not other paths. It is an incomplete test. To achieve 100%, test all paths:
- β Addition.
- β Subtraction.
- β οΈ An unrecognized operator.
The following test covers all three paths.
def test_operate():
assert operate(2, 3, "sum") == 5
assert operate(2, 3, "subtraction") == -1
with pytest.raises(ValueError):
operate(2, 3, "multiplication")
Now, coverage is 100%.
pytest --cov=example
# Name Stmts Miss Cover
# --------------------------------
# example.py 6 0 100%
# --------------------------------
# TOTAL 6 0 100%
Although 100% coverage is ideal, it does not guarantee anything. Code with 100% coverage may still have bugs. A test shows the presence of a bug but not its absence.
Let us prove it. Imagine a mistake where the operation subtract uses + instead of -. This code is incorrect.
def operate(a, b, operation):
if operation == "sum":
return a + b
if operation == "subtraction":
return a + b # <- Mistake. WRONG!
else:
raise ValueError(f"Invalid operation: {operation}")
In the test, if 0 and 0 are subtracted, the result is 0. The correct result is achieved with incorrect code.
def test_operate():
assert operate(2, 3, "sum") == 5
# This assert passes, but there is a bug.
assert operate(0, 0, "subtraction") == 0
This test passes, and coverage is 100%. This could falsely assure that the code is correct. But it is not.
The bug can be detected with the following code, a counterexample showing something is wrong.
def test_operate():
assert operate(5, 1, "subtraction") == 4
It is important to test as many combinations as possible, including positive numbers, negative numbers, zero, decimals, and unexpected inputs.
To exclude a function from the coverage metric, use the comment # pragma: no cover. Python will not consider it in the percentage calculation.
def operate(a, b, operation): # pragma: no cover
# ...
pytest generates interesting coverage reports. When you have hundreds of functions, a nice user interface to navigate is helpful.
pytest --cov=example --cov-report=html
