pytest: affirmer presque égal

145

Comment faire assert almost equalavec py.test pour les flotteurs sans recourir à quelque chose comme:

assert x - 0.00001 <= y <= x + 0.00001

Plus précisément il sera utile de connaître une solution soignée pour comparer rapidement des paires de flotteurs, sans les déballer:

assert (1.32, 2.4) == i_return_tuple_of_two_floats()
Vladimir Keleshev
la source
3
py.test a maintenant une fonctionnalité qui fait cela.
dbn le
Voir cette réponse pour une description de cette fonctionnalité
Tom Hale

Réponses:

233

J'ai remarqué que cette question posait spécifiquement sur py.test. py.test 3.0 inclut une approx()fonction (enfin, vraiment classe) qui est très utile à cette fin.

import pytest

assert 2.2 == pytest.approx(2.3)
# fails, default is ± 2.3e-06
assert 2.2 == pytest.approx(2.3, 0.1)
# passes

# also works the other way, in case you were worried:
assert pytest.approx(2.3, 0.1) == 2.2
# passes

La documentation est ici: https://docs.pytest.org/en/latest/reference.html#pytest-approx

dbn
la source
12
Agréable! Aussi trouvé que cela fonctionne aussi pour les séquences de nombres, par exempleassert [0.1 + 0.2, 0.2 + 0.4] == pytest.approx([0.3, 0.6])
M. Kriss
4
@Mr Kriss Et même pour les dicts:assert {'a': 0.1+0.2} == pytest.approx({'a': 0.3})
Antony Hatchkins
4
Cela ne fonctionne pas pour les listes de listes: par exemple, assert [[0.1 + 0.2], [0.2 + 0.4]] == pytest.approx([[0.3], [0.6]])conduit à un fichier TypeError. Si trouvé que Numpy np.testing.assert_allclose([[0.1 + 0.2], [0.2 + 0.4]], [[0.3], [0.6]])(voir la réponse ci-dessous) a fonctionné pour ce cas.
Kurt Peek
43

Vous devrez spécifier ce qui est "presque" pour vous:

assert abs(x-y) < 0.0001

pour appliquer aux tuples (ou à toute séquence):

def almost_equal(x,y,threshold=0.0001):
  return abs(x-y) < threshold

assert all(map(almost_equal, zip((1.32, 2.4), i_return_tuple_of_two_floats())
yourib
la source
3
La question demande comment le faire "sans recourir à quelque chose comme" ceci
endolith
J'interprète "quelque chose comme ça" comme une expression répétitive et maladroite comme x - d <= y <= x+d, semble que c'est ce que OP voulait dire aussi. Si vous ne souhaitez pas spécifier explicitement le seuil pour «presque», consultez la réponse de @ jiffyclub.
yurib
2
py.test a maintenant une fonctionnalité qui fait cela. J'ai ajouté une réponse en discutant.
dbn
2
@NeilG Pourquoi diable devrait-il être supprimé? S'il y a manifestement quelque chose qui ne va pas, veuillez expliquer de quoi il s'agit.
user2699
1
@ user2699 La question est de savoir comment faire cela dans pytest. La bonne façon de le faire dans pytest est d'utiliser pytest.approx. Ecrire votre propre fonction approximative est une mauvaise idée. (Celui de cette réponse n'est même pas aussi bon que celui inclus.)
Neil G
31

Si vous avez accès à NumPy, il dispose d'excellentes fonctions de comparaison en virgule flottante qui font déjà une comparaison par paires avec numpy.testing.

Ensuite, vous pouvez faire quelque chose comme:

numpy.testing.assert_allclose(i_return_tuple_of_two_floats(), (1.32, 2.4))
jiffyclub
la source
11

Quelque chose comme

assert round(x-y, 5) == 0

C'est ce que unittest - t

Pour la deuxième partie

assert all(round(x-y, 5) == 0 for x,y in zip((1.32, 2.4), i_return_tuple_of_two_floats()))

Probablement mieux d'envelopper ça dans une fonction

def tuples_of_floats_are_almost_equal(X, Y):
    return all(round(x-y, 5) == 0 for x,y in zip(X, Y))

assert tuples_of_floats_are_almost_equal((1.32, 2.4), i_return_tuple_of_two_floats())
John La Rooy
la source
11

Ces réponses existent depuis longtemps, mais je pense que le moyen le plus simple et le plus lisible est d'utiliser unittest pour ses nombreuses affirmations. sans l'utiliser pour la structure de test.

Obtenez des assertions, ignorez le reste de unittest.

(basé sur cette réponse )

import unittest

assertions = unittest.TestCase('__init__')

Faites quelques affirmations

x = 0.00000001
assertions.assertAlmostEqual(x, 0)  # pass
assertions.assertEqual(x, 0)  # fail
# AssertionError: 1e-08 != 0

Mettre en œuvre le test de décompression automatique des questions originales

Utilisez simplement * pour décompresser votre valeur de retour sans avoir à introduire de nouveaux noms.

i_return_tuple_of_two_floats = lambda: (1.32, 2.4)
assertions.assertAlmostEqual(*i_return_tuple_of_two_floats())  # fail
# AssertionError: 1.32 != 2.4 within 7 places
KobeJohn
la source
6

Si vous voulez quelque chose qui fonctionne non seulement avec les flottants mais par exemple Decimals, vous pouvez utiliser python math.isclose:

    # - rel_tol=0.01` is 1% difference tolerance.
    assert math.isclose(actual_value, expected_value, rel_tol=0.01)

Docs - https://docs.python.org/3/library/math.html#math.isclose

nom valide
la source
Ici, la tolérance relative (ou la différence en pourcentage) est pratique à utiliser dans certains cas d'utilisation, par exemple scienfific.
Karioki
3

J'utiliserais des outils nasaux. Il joue bien avec py.test runner et a d'autres assertions tout aussi utiles - assert_dict_equal (), assert_list_equal (), etc.

from nose.tools import assert_almost_equals
assert_almost_equals(x, y, places=7) #default is 7 
volodymyr
la source
2
En plus pytest a une option pour cela, je ne considère pas comme une bonne option d'ajouter une dépendance supplémentaire (dans ce cas, un cadre de test complet) uniquement pour cela.
Marc Tudurí