1
votes

Comment faire des comparaisons personnalisées dans pytest?

Par exemple, je voudrais affirmer que deux Pyspark DataFrame ont les mêmes données, mais simplement en utilisant == vérifie qu'ils sont le même objet. Dans l'idéal, j'aimerais également préciser si l'ordre compte ou non.

J'ai essayé d'écrire une fonction qui déclenche une AssertionError mais qui ajoute beaucoup de bruit à la sortie pytest car elle montre le traçage de cette fonction.

L'autre pensée que j'ai eue était de me moquer de la méthode __eq__ des DataFrames, mais je ne suis pas convaincu que ce soit la bonne voie à suivre.

Modifier:

J'ai envisagé d'utiliser simplement une fonction qui renvoie vrai ou faux au lieu d'un opérateur, mais cela ne semble pas fonctionner avec pytest_assertrepr_compare . Je ne connais pas assez bien le fonctionnement de ce hook, il est donc possible qu'il existe un moyen de l'utiliser avec une fonction au lieu d'un opérateur.


3 commentaires

Il semble que ce ne soit pas aussi simple qu'il y paraît. lien


Je suis d'accord pour comparer les deux DataFrames, ma question est de savoir comment utiliser cette logique de comparaison dans une assertion pytest


Si vous voulez faire ce test spécifique, vous pouvez créer une fonction qui renvoie True ou False pour la comparaison que vous souhaitez faire, puis faire une simple assertion IsTrue? Quelque chose comme ca. Je connais unittest et je n'ai pas essayé pytest, mais ça doit être la même logique, non?


4 Réponses :


0
votes

Pour faire une comparaison brute entre les valeurs des DataFrames (doit être dans l'ordre exact), vous pouvez faire quelque chose comme ceci:

def assert_frame_equal_with_sort(results, expected, keycolumns):
  results = results.reindex(sorted(results.columns), axis=1)
  expected = expected.reindex(sorted(expected.columns), axis=1)

  results_sorted = results.sort_values(by=keycolumns).reset_index(drop=True)
  expected_sorted = expected.sort_values(by=keycolumns).reset_index(drop=True)

  pd.testing.assert_frame_equal(results_sorted, expected_sorted)


df1 = spark.createDataFrame([Row(a=1, b=2, c=3), Row(a=1, b=3, c=3)])
df2 = spark.createDataFrame([Row(a=1, b=3, c=3), Row(a=1, b=2, c=3)])

assert_frame_equal_with_sort(df1.toPandas(), df2.toPandas(), ['b'])

Si vous voulez spécifier par ordre, vous pouvez faire quelques transformations sur le pandas DataFrame pour trier par une colonne particulière en utilisant d'abord la fonction suivante:

import pandas as pd
from pyspark.sql import Row

df1 = spark.createDataFrame([Row(a=1, b=2, c=3), Row(a=1, b=3, c=3)])
df2 = spark.createDataFrame([Row(a=1, b=2, c=3), Row(a=1, b=3, c=3)])

pd.testing.assert_frame_equal(df1.toPandas(), df2.toPandas())


1 commentaires

Je cherche davantage comment utiliser quelque chose comme ça avec pytest, j'ai déjà le code pour comparer les DataFrame



0
votes

Vous pouvez utiliser l'un des hooks pytest, en particulier le pytest_assertrepr_compare . Là, vous pouvez définir ce que vous voulez comparer et comment, les documents sont également très bons et avec des exemples. Bonne chance. :)


1 commentaires

Au départ, je pensais que c'était aussi la réponse, mais je crois comprendre que c'est simplement utilisé pour déterminer la façon dont les données sont représentées dans le journal une fois que l'appel assert échoue, il n'a aucun contrôle sur la façon dont le assert est géré.



2
votes

Ma solution actuelle est d'utiliser un patch pour remplacer la méthode __eq__ de DataFrame. Voici un exemple avec Pandas car il est plus rapide à tester avec, l'idée devrait s'appliquer à n'importe quel objet.

class SortedDF(object):
    "Indicates that the order of data matters when comparing to another df"

    def __init__(self, df):
        self.df = df

    def __eq__(self, other):
        # Put logic for comparing df's including order of data here
        # Returning True for demonstration purposes
        return True


def test_sorted_df():
    df1 = pd.DataFrame(
        {"id": [1, 2, 3], "name": ["a", "b", "c"]}, columns=["id", "name"]
    )
    df2 = pd.DataFrame(
        {"id": [2, 3, 4], "name": ["b", "c", "d"]}, columns=["id", "name"]
    )

    # Passes because SortedDF.__eq__ is used
    assert SortedDF(df1) == df2
    # Fails because df2's __eq__ method is used
    assert df2 == SortedDF(df2)

Je ne l'ai pas encore essayé mais je prévois de l'ajouter comme appareil et d'utiliser autouse pour l'utiliser automatiquement pour tous les tests.

Afin de gérer avec élégance l'indicateur "order Matters", je joue avec une approche similaire à pytest.approx qui renvoie une nouvelle classe avec son propre __eq__ par exemple:

import pandas as pd
# use this import for python3
# from unittest.mock import patch
from mock import patch


def custom_df_compare(self, other):
    # Put logic for comparing df's here
    # Returning True for demonstration
    return True


@patch("pandas.DataFrame.__eq__", custom_df_compare)
def test_df_equal():
    df1 = pd.DataFrame(
        {"id": [1, 2, 3], "name": ["a", "b", "c"]}, columns=["id", "name"]
    )
    df2 = pd.DataFrame(
        {"id": [2, 3, 4], "name": ["b", "c", "d"]}, columns=["id", "name"]
    )

    assert df1 == df2

Le problème mineur que je n'ai pas pu résoudre est l'échec de la deuxième assertion, assert df2 == SortedDF ( df2) . Cet ordre fonctionne bien avec pytest.approx mais pas ici. J'ai essayé de lire sur l'opérateur == mais je n'ai pas été en mesure de trouver comment résoudre le deuxième cas.


0 commentaires

0
votes

utilisez simplement la méthode pandas.Dataframe.equals https://pandas.pydata.org/ pandas-docs / stable / reference / api / pandas.DataFrame.equals.html

Par exemple

assert df1.equals(df2)

assert peut être utilisé avec tout ce qui renvoie un booléen. Donc oui, vous pouvez écrire n'importe quelle fonction de comparaison personnalisée pour comparer deux objets. Tant que la fonction personnalisée renvoie un booléen. Cependant, dans ce cas, il n'y a pas besoin d'une fonction personnalisée car pandas en fournit déjà une


1 commentaires

Le problème avec cette approche est qu'elle ne semble pas fonctionner avec pytest_assertrepr_compare dont j'aimerais également profiter. Cela agit comme un hook qui reçoit l'opérateur, les éléments gauche et droit et vous permet de définir comment l'échec doit apparaître dans le journal. J'ajouterai ce détail à ma question.