115 votes

assertAlmostEqual dans le test unitaire Python pour les collections de flottants

En assertAlmostEqual(x, y) méthode dans Le cadre de tests unitaires de Python vérifie si x et y sont approximativement égaux en supposant qu'il s'agit de flottants.

Le problème avec assertAlmostEqual() est qu'il ne fonctionne que sur les flottants. Je cherche une méthode comme assertAlmostEqual() qui fonctionne sur des listes de flottants, des ensembles de flottants, des dictionnaires de flottants, des tuples de flottants, des listes de tuples de flottants, des ensembles de listes de flottants, etc.

Par exemple, laissez x = 0.1234567890 , y = 0.1234567891 . x et y sont presque égaux car ils sont d'accord sur chaque chiffre, sauf le dernier. Par conséquent, self.assertAlmostEqual(x, y) est True parce que assertAlmostEqual() fonctionne pour les flotteurs.

Je cherche une solution plus générique assertAlmostEquals() qui évalue également les appels suivants à True :

  • self.assertAlmostEqual_generic([x, x, x], [y, y, y]) .
  • self.assertAlmostEqual_generic({1: x, 2: x, 3: x}, {1: y, 2: y, 3: y}) .
  • self.assertAlmostEqual_generic([(x,x)], [(y,y)]) .

Existe-t-il une telle méthode ou dois-je la mettre en œuvre moi-même ?

Clarifications :

  • assertAlmostEquals() a un paramètre optionnel nommé places et les nombres sont comparés en calculant la différence arrondie au nombre de décimales. places . Par défaut places=7 et donc self.assertAlmostEqual(0.5, 0.4) est Faux alors que self.assertAlmostEqual(0.12345678, 0.12345679) est vrai. Ma spéculation assertAlmostEqual_generic() devrait avoir la même fonctionnalité.

  • Deux listes sont considérées comme presque égales si elles ont des nombres presque égaux dans exactement le même ordre. formellement, for i in range(n): self.assertAlmostEqual(list1[i], list2[i]) .

  • De même, deux ensembles sont considérés comme presque égaux s'ils peuvent être convertis en listes presque égales (en attribuant un ordre à chaque ensemble).

  • De même, deux dictionnaires sont considérés comme presque égaux si l'ensemble de clés de chaque dictionnaire est presque égal à l'ensemble de clés de l'autre dictionnaire, et pour chaque paire de clés presque égale, il existe une valeur presque égale correspondante.

  • En général : Je considère deux collections presque égales si elles sont égales à l'exception de quelques flottants correspondants qui sont juste presque égaux entre eux. En d'autres termes, je voudrais vraiment comparer des objets mais avec une faible précision (personnalisée) lors de la comparaison des flottants en cours de route.

95voto

Pierre GM Points 7792

Si l'utilisation de NumPy (qui est fourni avec votre Python(x,y)) ne vous dérange pas, vous pouvez consulter le module np.testing qui définit, entre autres, un assert_almost_equal fonction.

La signature est np.testing.assert_almost_equal(actual, desired, decimal=7, err_msg='', verbose=True)

>>> x = 1.000001
>>> y = 1.000002
>>> np.testing.assert_almost_equal(x, y)
AssertionError: 
Arrays are not almost equal to 7 decimals
ACTUAL: 1.000001
DESIRED: 1.000002
>>> np.testing.assert_almost_equal(x, y, 5)
>>> np.testing.assert_almost_equal([x, x, x], [y, y, y], 5)
>>> np.testing.assert_almost_equal((x, x, x), (y, y, y), 5)

13voto

À partir de python 3.5, vous pouvez comparer en utilisant

math.isclose(a, b, rel_tol=1e-9, abs_tol=0.0)

Comme décrit dans pep-0485 . La mise en œuvre devrait être équivalente à

abs(a-b) <= max( rel_tol * max(abs(a), abs(b)), abs_tol )

9voto

snakile Points 10342

Voici comment j'ai implémenté un générique. is_almost_equal(first, second) fonction :

Tout d'abord, dupliquez les objets que vous devez comparer ( first et second ), mais n'en faites pas une copie exacte : coupez les chiffres décimaux insignifiants de tout flotteur que vous rencontrez à l'intérieur de l'objet.

Maintenant que vous avez des copies de first et second pour laquelle les chiffres décimaux insignifiants ont disparu, il suffit de comparer first et second en utilisant le == opérateur.

Supposons que nous ayons un cut_insignificant_digits_recursively(obj, places) qui duplique la fonction obj mais ne laisse que le places les chiffres décimaux les plus significatifs de chaque flottant dans l'original obj . Voici une mise en œuvre fonctionnelle de is_almost_equals(first, second, places) :

from insignificant_digit_cutter import cut_insignificant_digits_recursively

def is_almost_equal(first, second, places):
    '''returns True if first and second equal. 
    returns true if first and second aren't equal but have exactly the same
    structure and values except for a bunch of floats which are just almost
    equal (floats are almost equal if they're equal when we consider only the
    [places] most significant digits of each).'''
    if first == second: return True
    cut_first = cut_insignificant_digits_recursively(first, places)
    cut_second = cut_insignificant_digits_recursively(second, places)
    return cut_first == cut_second

Et voici une implémentation fonctionnelle de cut_insignificant_digits_recursively(obj, places) :

def cut_insignificant_digits(number, places):
    '''cut the least significant decimal digits of a number, 
    leave only [places] decimal digits'''
    if  type(number) != float: return number
    number_as_str = str(number)
    end_of_number = number_as_str.find('.')+places+1
    if end_of_number > len(number_as_str): return number
    return float(number_as_str[:end_of_number])

def cut_insignificant_digits_lazy(iterable, places):
    for obj in iterable:
        yield cut_insignificant_digits_recursively(obj, places)

def cut_insignificant_digits_recursively(obj, places):
    '''return a copy of obj except that every float loses its least significant 
    decimal digits remaining only [places] decimal digits'''
    t = type(obj)
    if t == float: return cut_insignificant_digits(obj, places)
    if t in (list, tuple, set):
        return t(cut_insignificant_digits_lazy(obj, places))
    if t == dict:
        return {cut_insignificant_digits_recursively(key, places):
                cut_insignificant_digits_recursively(val, places)
                for key,val in obj.items()}
    return obj

Le code et ses tests unitaires sont disponibles ici : https://github.com/snakile/approximate_comparator . Toute amélioration ou correction de bogue est la bienvenue.

6voto

DJCowley Points 73

Si cela ne vous dérange pas d'utiliser le numpy paquet alors numpy.testing a le assert_array_almost_equal méthode.

Cela fonctionne pour array_like Il convient donc pour les tableaux, les listes et les tuples de flottants, mais il ne fonctionne pas pour les ensembles et les dictionnaires.

La documentation est aquí .

4voto

BrenBarn Points 63718

Cette méthode n'existe pas, vous devez le faire vous-même.

Pour les listes et les tuples, la définition est évidente, mais notez que les autres cas que vous mentionnez ne sont pas évidents, il n'est donc pas étonnant qu'une telle fonction ne soit pas fournie. Par exemple, est-ce que {1.00001: 1.00002} presque égale à {1.00002: 1.00001} ? Pour traiter ces cas, il faut choisir si la proximité dépend des clés, des valeurs ou des deux. Pour les ensembles, il est peu probable que vous trouviez une définition significative, puisque les ensembles ne sont pas ordonnés et qu'il n'existe donc pas de notion d'éléments "correspondants".

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X