Testing with NumPy

Note

To ensure you are using compatible versions, install with the testfixtures[numpy] extra.

When numpy is installed, comparers for numpy.ndarray and numpy.ma.MaskedArray are automatically registered with ignore_eq=True. They hand off to numpy.testing.assert_allclose() for float and complex arrays and numpy.testing.assert_array_equal() for arrays of any other dtype, so the diff output is that of numpy’s own test helpers.

Shape and dtype must always match: there is no broadcasting and an int32 array is never equal to an int64 one. NaNs in matching positions compare equal. numpy scalars need none of this: their == returns a real bool, so they already work with compare().

Equal arrays compare equal:

>>> import numpy as np
>>> from testfixtures import compare
>>> compare(np.array([1, 2, 3]), expected=np.array([1, 2, 3]))

When arrays differ, numpy’s excellent summaries are used:

>>> compare(np.array([1., 2., 3.]), expected=np.array([1., 2.5, 3.]))
Traceback (most recent call last):
 ...
AssertionError: ...
Not equal to tolerance rtol=1e-05, atol=1e-08

Mismatched elements: 1 / 3 (33.3%)...
Max absolute difference among violations: 0.5
Max relative difference among violations: 0.2
 ACTUAL: array([1., 2., 3.])
 DESIRED: array([1. , 2.5, 3. ])

When arrays differ inside a larger structure, compare()’s breadcrumbs still point at the location of the difference:

>>> compare({'foo': np.array([1])}, expected={'foo': np.array([2])})
Traceback (most recent call last):
 ...
AssertionError: dict not as expected:

values differ:
'foo': array([2]) (expected) != array([1]) (actual)

While comparing ['foo']: ...
Arrays are not equal

Mismatched elements: 1 / 1 (100%)...
Max absolute difference among violations: 1
Max relative difference among violations: 0.5
 ACTUAL: array([1])
 DESIRED: array([2])

Float and complex arrays compare equal within rtol=1e-5 and atol=1e-8, matching the pandas and polars comparers rather than the tighter defaults of numpy.testing.assert_allclose():

>>> fa = np.array([1.0 + 1e-9, 2.0])
>>> fb = np.array([1.0, 2.0])
>>> compare(fa, expected=fb)

Pass strict=True to require bitwise equality instead:

>>> compare(fa, expected=fb, strict=True)
Traceback (most recent call last):
 ...
AssertionError: ...
Arrays are not equal

Mismatched elements: 1 / 2 (50%)...
Max absolute difference among violations: 1.00000008e-09
Max relative difference among violations: 1.00000008e-09
 ACTUAL: array([1., 2.])
 DESIRED: array([1., 2.])

Masked arrays

For numpy.ma.MaskedArray, the mask is part of the data, much as nulls are in a dataframe: masks must match exactly regardless of other options, while data under matching masked positions is ignored:

>>> m1 = np.ma.MaskedArray([1, 2, 3], mask=[False, True, False])
>>> m2 = np.ma.MaskedArray([1, 9, 3], mask=[False, True, False])
>>> compare(m1, expected=m2)
>>> compare(m1, expected=np.ma.MaskedArray([1, 2, 3]))
Traceback (most recent call last):
 ...
AssertionError: masks differ: ...
Arrays are not equal

Mismatched elements: 1 / 3 (33.3%)...
 ACTUAL: array([False,  True, False])
 DESIRED: array([False, False, False])