Unit-Tests für Daten-Munging-Pipelines, die aus einzeiligen Funktionen bestehen

10

Sie liest Mary Rose Cooks praktische Einführung in die funktionale Programmierung und gibt ein Beispiel für ein Anti-Pattern

def format_bands(bands):
    for band in bands:
        band['country'] = 'Canada'
        band['name'] = band['name'].replace('.', '')
        band['name'] = band['name'].title()

schon seit

  • Die Funktion macht mehr als eine Sache
  • Der Name ist nicht beschreibend
  • es hat Nebenwirkungen

Als Lösungsvorschlag schlägt sie das Pipelining anonymer Funktionen vor

pipeline_each(bands, [call(lambda x: 'Canada', 'country'),
                      call(lambda x: x.replace('.', ''), 'name'),
                      call(str.title, 'name')])

Dies scheint mir jedoch den Nachteil zu haben, noch weniger überprüfbar zu sein; Zumindest format_bands könnte einen Komponententest durchführen, um zu überprüfen, ob es das tut, was es soll, aber wie wird die Pipeline getestet? Oder ist die Idee, dass die anonymen Funktionen so selbsterklärend sind, dass sie nicht getestet werden müssen?

Meine reale Anwendung hierfür besteht darin, meinen pandasCode funktionsfähiger zu machen . Ich habe oft eine Art Pipeline in einer "Munging" -Funktion "

def munge_data(df)
     df['name'] = df['name'].str.lower()
     df = df.drop_duplicates()
     return df

Oder im Pipeline-Stil umschreiben:

def munge_data(df)
    munged = (df.assign(lambda x: x['name'].str.lower()
                .drop_duplicates())
    return munged

Irgendwelche Vorschläge für Best Practices in solchen Situationen?

Max Flander
quelle
4
Diese einzelnen Lambda-Funktionen sind zu klein für einen Unit-Test. Testen Sie das Endergebnis. Anders ausgedrückt, anonyme Funktionen können nicht in Einheiten getestet werden. Schreiben Sie die Funktion daher nicht als anonyme Funktion, wenn Sie sie einzeln testen möchten.
Robert Harvey

Antworten:

1

Ich denke, Sie haben wahrscheinlich den wichtigeren Teil des korrigierten Beispiels des Buches verpasst. Die grundlegendere Änderung des Codes erfolgt von der Methode, die mit allen Werten in einer Liste ausgeführt wird, zur Methode, die mit einem Element ausgeführt wird.

Es gibt bereits Funktionen wie iter(in diesem Fall benannt pipeline_foreach), die eine bestimmte Operation für alle Elemente in einer Liste ausführen. Es war nicht nötig, dies mit einer forSchleife zu duplizieren . Auch die Verwendung einer bekannten Listenoperation macht Ihre Absicht klar. Mit maptransformieren Sie die Werte. Mit führen iterSie mit jedem Element einen Nebeneffekt durch. Mit forSchleife bist du ... nun, du weißt es nicht wirklich, bis du es durchschaust.

Der beispielkorrigierte Code ist immer noch nicht sehr funktional, da er (soweit ich das beurteilen kann) die Werte in der Liste mutiert, ohne sie zurückzugeben, wodurch weitere Rohrleitungen oder Funktionszusammensetzungen verhindert werden. Die funktional bevorzugte Methode mapwürde eine neue Liste von Bändern mit dem aktualisierten countryund erstellen name. Dann können Sie diese Ausgabe an die nächste Funktion weiterleiten oder mapmit einer anderen Funktion komponieren , die eine Bandliste erstellt hat. Mit iter, es ist wie eine Sackgasse.

Ich denke, der Endergebniscode hat kleine Funktionen, die zu trivial sind, um sie hier zu testen. Schließlich sollten Sie keine Unit-Tests gegen replaceoder schreiben müssen title. Vielleicht möchten Sie diese jetzt zu Ihrem eigenen Funktions- und Komponententest zusammensetzen, damit die gewünschte Kombination für einen einzelnen Artikel erreicht wird. Ich selbst hätte wahrscheinlich nur format_bandszu format_bandSingular gewechselt , die for-Schleife gelöscht und angerufen pipeline_each(bands, format_band). Dann können Sie format_band testen, um sicherzustellen, dass Sie nichts vergessen haben.

Wie auch immer, weiter zu deinem Code. Ihr zweites Beispiel für Code scheint eher Pipeline-y zu sein. Dies allein bietet jedoch nicht die Vorteile einer funktionalen Programmierung. In der Praxis bedeutet funktionale Programmierung, die Kompatibilität von Funktionen mit anderen Funktionen sicherzustellen, indem ihre Kompatibilität nur anhand ihrer Ein- und Ausgänge definiert wird. Wenn die Funktion versteckte Nebenwirkungen aufweist, können Sie trotz der Übereinstimmung der Eingabe / Ausgabe mit anderen Funktionen erst zur Laufzeit feststellen, ob sie kompatibel sind. Wenn jedoch zwei Funktionen nebenwirkungsfrei sind und Ausgabe zu Eingabe übereinstimmen, können Sie sie mit wenig Sorge um unerwartete Ergebnisse weiterleiten oder zusammenstellen .

Kasey Speakman
quelle