Precision matters, but so does practicality. This is where the pytest approx
module comes into play.
The Issue With Floating Point Arithmetic
Floating-point numbers are encoded in computer hardware using base 2 (binary) fractions.
You know that the decimal fraction 0.125 can be written as 1/10 + 2/100 + 5/1000. Similarly, the binary fraction 0.001 corresponds to 0/2 + 0/4 + 1/8.
Take the example of the fraction 1/3. It can be approximated in base 10 as follows: 0.3, or 0.33 or even better 0.333 and so on.
Understanding Tolerance Levels
Understanding tolerance levels is crucial when dealing with numerical approximations in testing.
Tolerance determines the acceptable deviation between two numbers for them to be considered practically equal.
Syntax of Pytest Approx
The syntax for using the pytest approx functionality in Pytest is quite straightforward. You can use it to perform approximate comparisons between numerical values, accounting for the inherent imprecision of floating-point calculations.
Here’s the basic syntax:
assert actual_value == pytest.approx(expected_value, rel=None, abs=None, nan_ok=False)
Here’s a simple example to implement pytest approx:
tests/unit/test_pytest_approx_1.py
import pytest
def divide(a, b):
return a / b
def test_exact_comparison():
result = divide(1, 3)
assert result == 0.3333333333333333 # Exact value
def test_approximate_comparison():
result = divide(1, 3)
assert result == pytest.approx(0.3333333333333333) # Approximate value
@pytest.mark.xfail(reason="This test is currently expected to fail")
def test_approximation_failure():
result = divide(1, 3)
assert result == pytest.approx(0.333) # This test will fail due to approximation
- test_exact_comparison: Comparing the result of the
divide(1, 3)
function call, which computes1 / 3
, with the exact value0.3333333333333333
. Since the comparison is exact, the test passes. - test_approximate_comparison: This test also compares the result of
divide(1, 3)
with the value0.3333333333333333
, but this time usingpytest.approx
. This test passes becausepytest.approx
considers the small variations due to floating-point imprecision. - test_approximation_failure: Here we compare the result of
divide(1, 3)
with the value0.333
usingpytest.approx
.
Approximating Lists And Arrays
When comparing a list of calculated values with expected results, Pytest Approx simplifies the process, allowing you to assert approximate equality without manually addressing each element’s precision.
Similarly, when you are comparing arrays you can employ pytest.approx
to verify their equality within specified tolerances.
Approximating Dictionaries
For dictionaries, Pytest Approx handles key-value pairs with precision.
tests/unit/test_pytest_approx_6.py
import pytest
def test_measurements():
expected = {
'length': 10.0,
'temperature': 25.5,
'pressure': 1013.25,
}
calculated = {
'length': 10.0001, # Slightly different due to calculations
'temperature': 25.4999, # Slightly different due to calculations
'pressure': 1013.24, # Slightly different due to calculations
}
tolerance = {
'length': 0.01, # Tolerance for length values
'temperature': 0.05, # Tolerance for temperature values
'pressure': 0.1, # Tolerance for pressure values
}
for key in expected:
assert calculated[key] == pytest.approx(expected[key], abs=tolerance[key])
In scenarios where you’re comparing calculated dictionary values with expected outcomes, Pytest Approx lets you assert approximate equality while accounting for floating-point discrepancies.