Warum ist das Testen von Einheiten in der objektorientierten Programmierung schwieriger als in der funktionalen Programmierung?

9

Ich gehe diese Serie durch . Der Autor erwähnt, dass es schwieriger ist, Komponententests zu schreiben, da der Status in der objektorientierten Programmierung beibehalten wird. Er sagt auch, dass es einfacher ist, Komponententests zu schreiben, da die funktionale Programmierung den Status nicht beibehält (nicht immer). Ich habe keine Beispiele gesehen, die dieses Problem demonstrieren. Wenn dies zutrifft, können Sie mir ein Beispiel zum Vergleich von Unit-Tests in der objektorientierten und funktionalen Programmierung geben?

0112
quelle
2
Ich bin nicht der Meinung, dass funktionale Programmierung einfacher zu testen ist. Während es wahr ist, dass eine zustandslose Funktion einfacher zu testen ist als ein zustandsbehaftetes Objekt, führt die Umwandlung eines zustandsbehafteten Objekts in eine zustandslose Funktion zu einer viel komplexeren statelären Funktion, möglicherweise mit einer komplexen Zustandsemulation wie Monaden.
Euphoric
1
@Euphoric Eine funktionale Wiedergabe einer Lösung für ein Problem unterscheidet sich wahrscheinlich erheblich davon, nur den Status durch eine StateMonade zu ersetzen (die den Status emuliert). Sie haben teilweise einen Punkt - Code, der in diesem Stil geschrieben wurde, hat nicht lokale Interaktionen -, aber für Unit-Tests können Sie den Status immer noch explizit übergeben, Sie müssen nichts verspotten. Auf der anderen Seite führen Sie mit Monaden wie STund IO(die einen wahren, impliziten veränderlichen Zustand haben) einfach eine zustandsbehaftete Programmierung durch, und Unit-Tests sind nicht einfacher als in jeder anderen Sprache.
Derek Elkins verließ SE
10
Es gibt nichts in der Definition von OO , die wandelbar Zustand erfordert. Es ist durchaus möglich (und weit verbreitet), reinen, referenziell transparenten OO-Code zu schreiben.
Jörg W Mittag
1
Alle mit der funktionalen Programmierung verbundenen Techniken sind in der objektorientierten Programmierung perfekt anwendbar. Gut konzipierte OO-Programme verwenden in der Regel mindestens funktionale Techniken auf den unteren Ebenen.
Frank Hileman
1
@Fabio Wenn dies der Fall ist, ist es sinnvoller, die FP-Programmierung zu verwenden. OO erfordert keinen veränderlichen Zustand, aber die Veränderlichkeit ist in dem Beispiel impliziert, das ich Ihnen gerade gegeben habe. Gleiches gilt für f # - Sie können eine Variable markieren, die den Anforderungen entspricht. Wenn Sie jedoch zu viele veränderbare Variablen verwenden, können Sie auch c # verwenden.

Antworten:

17

Ich möchte zwischen zwei verschiedenen Arten der objektorientierten Programmierung unterscheiden

  1. Simulationist: Ihre Objekte stellen reale Domänenobjekte dar. Sie haben sie so programmiert, dass sie alle mit dieser Domäne verbundenen Funktionen ausführen. Auf diese Weise programmierte Objekte haben wahrscheinlich viele veränderbare Status und versteckte Kollaborateure, die zur Implementierung dieser Funktionalität verwendet werden.
  2. Datensätze + Funktionen: Ihre Objekte sind nur Datenbündel und Funktionen, die über diese Daten ausgeführt werden. Auf diese Weise programmierte Objekte sind eher unveränderlich, übernehmen weniger Verantwortung und ermöglichen es, Mitarbeiter einzuschleusen.

Als Faustregel gilt, dass ein auf die erste Weise programmiertes Objekt mehr Methoden und voidMethoden haben wird als auf die zweite Weise. Angenommen, wir wollten einen Flugsimulator schreiben und eine Flugzeugklasse entwerfen. Wir hätten so etwas wie:

class Plane {
    void accelerate();
    void deccelerate();
    void toggleRightFlaps();
    void toggleLeftFlaps();
    void turnRudderRight();
    void turnRudderLeft();
    void deployLandingGear();
    void liftLandingGear();
    // etc.
    void tick() throws PlaneCrashedException;
}

Dies ist vielleicht etwas extremer, als man es sich vorstellen könnte, aber es bringt den Punkt auf den Punkt. Wenn Sie diese Art von Schnittstelle implementieren möchten, müssen Sie sich im Objekt befinden:

  1. Alle Informationen über den Zustand der Flugzeugausrüstung.
  2. Alle Informationen über die Geschwindigkeit / Beschleunigung des Flugzeugs.
  3. Die Bildwiederholfrequenz unserer Simulation (um Tick zu implementieren).
  4. Vollständige Details zum 3D-Modell der Simulation und ihrer Physik zur Implementierung von Tick.

Das Schreiben eines Komponententests für ein im Modus geschriebenes Objekt ist absolut schwierig, weil:

  1. Sie müssen zu Beginn Ihres Tests all die vielen verschiedenen Daten und Mitarbeiter angeben, die dieses Objekt benötigt (das Einsetzen dieser Daten kann sehr mühsam sein).
  2. Wenn Sie eine Methode testen möchten, treten zwei Probleme auf: a) Die Schnittstelle stellt häufig nicht genügend Daten zum Testen bereit (sodass Sie am Ende Mocks / Reflection verwenden müssen, um die Erwartungen zu überprüfen). B) Es sind viele Komponenten gebunden in eine, die Sie bei jedem Test überprüfen müssen.

Grundsätzlich beginnen Sie mit einer Schnittstelle, die vernünftig aussieht und gut zur Domäne passt, aber die Schönheit der Simulation hat Sie dazu verleitet, ein Objekt zu erstellen, das wirklich schwer zu testen ist.

Sie können jedoch Objekte erstellen, die denselben Zweck erfüllen. Sie möchten Ihre Planein kleinere Teile bremsen . Haben Sie ein PlaneParticle, das die physikalischen Teile des Flugzeugs, die Position, Geschwindigkeit, Beschleunigung, Roll, Gieren usw. usw. verfolgt und diese freilegt und manipulieren kann. Dann könnte ein PlanePartsObjekt den Status von verfolgen. Sie würden Schiff tick()zu einem ganz anderen Ort, sagen einen haben PlanePhysicsObjekt, zB durch parametriert, die Schwerkraft, die ein bestimmtes kennt PlaneParticleund ein PlanePartsneues , wie auszuspucken PlaneParticle. All dies könnte völlig unveränderlich sein, obwohl es nicht unbedingt ein Beispiel sein muss.

Sie haben jetzt folgende Vorteile beim Testen:

  1. Jede einzelne Komponente hat weniger zu tun und ist einfacher einzurichten.
  2. Sie können Ihre Komponenten isoliert testen.
  3. Diese Objekte können mit dem Freilegen ihrer Einbauten davonkommen (insbesondere wenn sie unveränderlich gemacht werden), so dass man keine Klugheit braucht, um sie zu messen.

Hier ist der Trick: Der zweite objektorientierte Ansatz, den ich beschrieben habe, kommt der funktionalen Programmierung sehr nahe. Vielleicht sind in reinen Funktionsprogrammen Ihre Datensätze und Ihre Funktionen getrennt und nicht in Objekten miteinander verbunden. Auf jeden Fall würde ein Funktionsprogramm sicherstellen, dass all diese Dinge. Was ich denke, macht Unit-Tests wirklich einfach

  1. Kleine Einheiten (kleiner Zustandsraum).
  2. Funktionen mit minimaler Eingabe (keine versteckten Eingaben).
  3. Funktionen mit minimaler Ausgabe.

Funktionale Programmierung fördert diese Dinge (aber man kann in jedem Paradigma schlechte Programme schreiben), aber sie sind in objektorientierten Programmen erreichbar. Und ich möchte weiter betonen, dass funktionale Programmierung und objektorientierte Programmierung nicht inkompatibel sind.

Walpen
quelle
2
Ich bin mir nicht sicher, ob dies eine gute Unterscheidung ist. Selbst wenn Ansatz 1 verwendet wird, würden viele die Techniken, die Sie für Ansatz 2 beschreiben, als Standardpraxis betrachten. Die beiden Ansätze schließen sich nicht gegenseitig aus. Die Teile der Ebene können zusammengesetzt werden, um eine größere Abstraktion zu erstellen, und Sie befinden sich jetzt im Bereich von Ansatz 1. Ich würde sogar sagen, dass Ansatz 1, wie Sie ihn beschreiben, nicht logisch ist.
Frank Hileman
3
"Und ich möchte weiter betonen, dass funktionale Programmierung und objektorientierte Programmierung nicht inkompatibel sind.": Einige würden sagen, dass objektorientierte Programmierung nur durch dynamischen Versand gekennzeichnet ist. Sie können also im imperativen OOP-Stil schreiben : veränderbare Objekte + dynamischer Versand oder im funktionalen OOP-Stil: unveränderliche Objekte + dynamischer Versand.
Giorgio
3
Das Aufteilen eines großen Objekts in kleinere Teile ist keine funktionale Programmierung. Und die Lösung für ein großes Objekt besteht nicht darin, die Logik von den Daten zu trennen und sie anämisch zu machen, was funktionsfähig ist . Ich könnte Ihren ganzen Teil jedoch falsch verstehen.
Chris Wohlert