Um den Titel ein wenig zu erweitern, versuche ich zu einer Schlussfolgerung zu gelangen, ob es notwendig ist, reine Funktionen, von denen eine andere Funktion oder Klasse abhängt, explizit zu deklarieren (dh zu injizieren).
Ist ein bestimmter Code weniger testbar oder schlechter konzipiert, wenn er reine Funktionen verwendet, ohne danach zu fragen? Ich mag zu einem Abschluss über die Angelegenheit bekommen, für jede Art von reiner Funktion von einfachen, nativen Funktionen (zB max()
, min()
- unabhängig von der Sprache) , um benutzerdefinierte, kompliziertere, die wiederum implizit auf anderen reinen Funktionen abhängen.
Die übliche Sorge ist, dass wenn ein Code nur eine Abhängigkeit direkt verwendet, Sie diese nicht isoliert testen können, sondern gleichzeitig alle Dinge testen, die Sie stillschweigend mitgebracht haben. Aber das fügt ziemlich viel Boilerplate hinzu, wenn Sie es für jede kleine Funktion tun müssen, also frage ich mich, ob dies immer noch für reine Funktionen gilt und warum oder warum nicht.
Antworten:
Nein, das ist nicht schlecht
Den Tests, die Sie schreiben, sollte es egal sein, wie eine bestimmte Klasse oder Funktion implementiert wird. Vielmehr sollte sichergestellt werden, dass sie die gewünschten Ergebnisse liefern, unabhängig davon, wie genau sie implementiert sind.
Betrachten Sie als Beispiel die folgende Klasse:
Sie möchten die Funktion 'Round' testen, um sicherzustellen, dass sie das zurückgibt, was Sie erwarten, unabhängig davon, wie die Funktion tatsächlich implementiert ist. Sie würden wahrscheinlich einen Test schreiben, der dem folgenden ähnlich ist;
Unter Testgesichtspunkten ist es Ihnen egal, wie Ihre Coord2d :: Round () -Funktion das zurückgibt, was Sie erwarten.
In einigen Fällen kann das Injizieren einer reinen Funktion eine wirklich hervorragende Möglichkeit sein, eine Klasse testbarer oder erweiterbarer zu machen.
In den meisten Fällen, wie bei der obigen Funktion Coord2d :: Round (), muss jedoch keine Min / Max / Floor / Ceil / Trunc-Funktion eingefügt werden. Die Funktion ist so konzipiert, dass ihre Werte auf die nächste ganze Zahl gerundet werden. Die Tests, die Sie schreiben, sollten überprüfen, ob die Funktion dies tut.
Wenn Sie Code testen möchten, von dem eine Klassen- / Funktionsimplementierung abhängt, können Sie dies tun, indem Sie einfach Tests für die Abhängigkeit schreiben.
Zum Beispiel, wenn die Funktion Coord2d :: Round () wie folgt implementiert wurde ...
Wenn Sie die Bodenfunktion testen möchten, können Sie dies in einem separaten Komponententest tun.
quelle
Aus Testsicht - Nein, aber nur bei reinen Funktionen, wenn die Funktion immer dieselbe Ausgabe für dieselbe Eingabe zurückgibt.
Wie testen Sie Geräte, die explizit injizierte Funktionen verwenden?
Sie werden eine verspottete (gefälschte) Funktion einfügen und überprüfen, ob die Funktion mit korrekten Argumenten aufgerufen wurde.
Da wir jedoch eine reine Funktion haben, die immer dasselbe Ergebnis für dieselben Argumente zurückgibt, ist die Überprüfung auf Eingabeargumente für die Überprüfung des Ausgabeergebnisses gleich.
Mit reinen Funktionen müssen Sie keine zusätzlichen Abhängigkeiten / Zustände konfigurieren.
Als zusätzlichen Vorteil erhalten Sie eine bessere Kapselung und testen das tatsächliche Verhalten des Geräts.
Und aus Sicht des Testens sehr wichtig: Sie können den internen Code der Einheit frei ändern, ohne die Tests zu ändern. Sie möchten beispielsweise ein weiteres Argument für die reine Funktion hinzufügen - mit explizit injizierter Funktion (in den Tests verspottet), die Sie benötigen Ändern Sie die Testkonfiguration, wobei Sie mit der implizit verwendeten Funktion nichts tun.
Ich kann mir eine Situation vorstellen, in der Sie reine Funktion einspeisen müssen - wenn Sie den Verbrauchern anbieten möchten, das Verhalten des Geräts zu ändern.
Für die oben beschriebene Methode erwarten Sie, dass die Rabattberechnung von den Verbrauchern geändert werden kann. Daher müssen Sie das Testen der Logik von den Komponententests für die
CalcualteTotalPrice
Methode ausschließen.quelle
In einer idealen Welt der SOLID OO-Programmierung würden Sie jedes externe Verhalten injizieren. In der Praxis werden Sie immer einige einfache (oder nicht so einfache) Funktionen direkt verwenden.
Wenn Sie das Maximum einer Reihe von Zahlen berechnen, wäre es wahrscheinlich übertrieben, eine
max
Funktion zu injizieren und sie in Komponententests zu verspotten. Normalerweise ist es Ihnen egal, Ihren Code von einer bestimmten Implementierung zu entkoppelnmax
und isoliert zu testen.Wenn Sie etwas Komplexes tun, z. B. einen Pfad mit minimalen Kosten in einem Diagramm finden, sollten Sie die konkrete Implementierung aus Gründen der Flexibilität einfügen und sie in Komponententests verspotten, um eine bessere Leistung zu erzielen. In diesem Fall kann es sich lohnen, den Pfadfindungsalgorithmus von dem Code zu entkoppeln, der ihn verwendet.
Es kann keine Antwort für "irgendeine Art von reiner Funktion" geben, Sie müssen entscheiden, wo die Linie gezogen werden soll. Bei dieser Entscheidung sind zu viele Faktoren beteiligt. Am Ende müssen Sie die Vorteile gegen die Probleme abwägen, die Ihnen die Entkopplung bereitet, und das hängt von Ihnen und Ihrem Kontext ab.
Ich sehe in den Kommentaren, dass Sie nach dem Unterschied zwischen injizierenden Klassen (tatsächlich injizieren Sie Objekte) und Funktionen fragen. Es gibt keinen Unterschied, nur Sprachfunktionen lassen es anders aussehen. Aus abstrakter Sicht unterscheidet sich das Aufrufen einer Funktion nicht vom Aufrufen der Methode eines Objekts, und Sie injizieren (oder nicht) die Funktion aus den gleichen Gründen, aus denen Sie das Objekt injizieren (oder nicht): um dieses Verhalten vom aufrufenden Code zu entkoppeln und die Fähigkeit, verschiedene Implementierungen des Verhaltens zu verwenden und woanders zu entscheiden, welche Implementierung verwendet werden soll.
Grundsätzlich können Sie alle Kriterien, die Sie für die Abhängigkeitsinjektion für gültig halten, unabhängig davon verwenden, ob es sich bei der Abhängigkeit um ein Objekt oder eine Funktion handelt. Abhängig von der Sprache kann es einige Nuancen geben (dh wenn Sie Java verwenden, ist dies kein Problem).
quelle
max()
etwas zu verbinden (im Gegensatz zu etwas anderem)?Reine Funktionen beeinträchtigen die Testbarkeit von Klassen aufgrund ihrer Eigenschaften nicht:
Dies bedeutet, dass sie sich ungefähr im selben Bereich befinden wie private Methoden, die vollständig unter der Kontrolle der Klasse stehen, mit dem zusätzlichen Bonus, dass sie nicht einmal vom aktuellen Status der Instanz abhängen (dh "dies"). Die Benutzerklasse "steuert" die Ausgabe, da sie die Eingaben vollständig steuert.
Die von Ihnen angegebenen Beispiele max () und min () sind deterministisch. Random () und currentTime () sind jedoch Prozeduren, keine reinen Funktionen (sie hängen beispielsweise vom Weltzustand außerhalb des Bandes ab / ändern ihn). Diese beiden letzten müssten injiziert werden, um Tests zu ermöglichen.
quelle