Verstoßen Mocks gegen das Open / Closed-Prinzip?

13

Vor einiger Zeit las ich auf einer Stapelüberlauf-Antwort, die ich nicht finden kann, einen Satz, der erklärte, dass Sie öffentliche APIs testen sollten, und der Autor sagte, dass Sie Schnittstellen testen sollten. Der Autor erklärte auch, dass bei einer Änderung der Methodenimplementierung der Testfall nicht geändert werden muss, da dies den Vertrag unterbrechen würde, der sicherstellt, dass das zu testende System funktioniert. Mit anderen Worten, ein Test sollte fehlschlagen, wenn die Methode nicht funktioniert, aber nicht, weil sich die Implementierung geändert hat.

Dies machte mich aufmerksam, wenn wir über das Verspotten sprechen. Da die Verspottung stark von den Erwartungsaufrufen der Abhängigkeiten des zu testenden Systems abhängt, sind die Verspottungen eng mit der Implementierung und nicht mit der Schnittstelle verbunden.

Bei der Untersuchung von Mock vs Stub stimmen mehrere Artikel darin überein, dass Stubs anstelle von Mocks verwendet werden sollten, da sie sich nicht auf die Erwartungen aus Abhängigkeiten stützen. Dies bedeutet, dass für den Test keine Kenntnisse des zugrunde liegenden Systems erforderlich sind, das der Testimplementierung zugrunde liegt.

Meine Fragen wären:

  1. Verstoßen Mocks gegen das Open / Closed-Prinzip?
  2. Fehlt etwas in dem Argument zugunsten von Stubs im letzten Absatz, das Stubs im Vergleich zu Mocks nicht so großartig macht?
  3. Wenn ja, wann wäre es sinnvoll, Stubs zu verspotten, und wann wäre es sinnvoll, Stubs zu verwenden?
Christopher Francisco
quelle
8
Since mocking relays heavily on expectation calls from system under test's dependencies...Ich denke, das ist, wo Sie schief gehen. Ein Mock ist eine künstliche Darstellung eines externen Systems. Es stellt das externe System in keiner Weise dar, es sei denn, es simuliert das externe System so, dass Tests mit Code ausgeführt werden können, der Abhängigkeiten von diesem externen System aufweist. Sie benötigen noch Integrationstests, um zu beweisen, dass Ihr Code mit dem realen, nicht gespielten System funktioniert.
Robert Harvey
8
Anders ausgedrückt, das Mock ist eine Ersatzimplementierung. Aus diesem Grund haben wir in erster Linie eine Schnittstelle programmiert, sodass wir Mocks als Ersatz für die eigentliche Implementierung verwenden können. Mit anderen Worten, Mocks sind von der tatsächlichen Implementierung entkoppelt und nicht an diese gekoppelt.
Robert Harvey
3
"Mit anderen Worten, ein Test sollte fehlschlagen, wenn die Methode nicht funktioniert, aber nicht, weil sich die Implementierung geändert hat", ist dies nicht immer der Fall. Es gibt viele Umstände, unter denen Sie sowohl Ihre Implementierung als auch Ihre Tests ändern sollten.
Whatsisname

Antworten:

4
  1. Ich verstehe nicht, warum Mocks das Open / Closed-Prinzip verletzen würden. Wenn Sie uns erklären könnten, warum Sie glauben, dass dies der Fall ist, können wir Ihre Bedenken möglicherweise zerstreuen.

  2. Der einzige Nachteil von Stubs, den ich mir vorstellen kann, ist, dass sie im Allgemeinen mehr Arbeit als Mocks erfordern, da jeder von ihnen tatsächlich eine alternative Implementierung einer abhängigen Schnittstelle ist und daher im Allgemeinen eine vollständige (oder überzeugend vollständige) Schnittstelle bereitstellen muss. Implementierung der abhängigen Schnittstelle. Um ein extremes Beispiel zu geben: Wenn Ihr zu testendes Subsystem ein RDBMS aufruft, antwortet ein Schein des RDBMS einfach auf bestimmte Abfragen, von denen bekannt ist, dass sie vom zu testenden Subsystem ausgegeben werden, und liefert vorbestimmte Sätze von Testdaten. Auf der anderen Seite wäre eine alternative Implementierung ein vollwertiges speicherinternes RDBMS, möglicherweise mit der zusätzlichen Belastung, die Macken des tatsächlichen Client-Server-RDBMS emulieren zu müssen, das Sie in der Produktion verwenden. (Zum Glück haben wir Dinge wie HSQLDB, also können wir das tatsächlich tun, aber trotzdem,

  3. Gute Anwendungsfälle für das Verspotten sind, wenn die abhängige Schnittstelle zu kompliziert ist, um eine alternative Implementierung dafür zu schreiben, oder wenn Sie sicher sind, dass Sie die Verspottung nur einmal schreiben und sie nie wieder berühren werden. Verwenden Sie in diesen Fällen einen schnellen und schmutzigen Schein. Gute Use Cases für Stubs (alternative Implementierungen) sind daher so ziemlich alles andere. Vor allem, wenn Sie eine langfristige Beziehung mit dem zu testenden Subsystem vorsehen, sollten Sie auf jeden Fall eine alternative Implementierung wählen, die nett und sauber ist und nur dann gewartet werden muss, wenn sich die Schnittstelle ändert, anstatt bei jeder Änderung der Schnittstelle gewartet zu werden Änderungen und wann immer sich die Implementierung des zu testenden Teilsystems ändert.

PS Die Person, auf die Sie sich beziehen, könnte ich gewesen sein, in einer meiner anderen testbezogenen Antworten hier auf programmers.stackexchange.com, zum Beispiel dieser .

Mike Nakis
quelle
an alternative implementation would be a full-blown in-memory RDBMS- Sie müssen nicht unbedingt so weit mit einem Stummel gehen.
Robert Harvey
@RobertHarvey Nun, mit HSQLDB und H2 ist es nicht so schwierig, tatsächlich so weit zu kommen. Es ist wahrscheinlich schwieriger, etwas halbherziges zu tun, um nicht so weit zu kommen. Wenn Sie dies jedoch alleine tun möchten, müssen Sie zunächst einen SQL-Parser schreiben. Klar, man kann ein paar Ecken abschneiden, aber es gibt viel Arbeit . Wie ich bereits sagte, ist dies nur ein extremes Beispiel.
Mike Nakis
9
  1. Beim Open / Closed-Prinzip geht es hauptsächlich darum, das Verhalten einer Klasse zu ändern, ohne es zu ändern. Das Einfügen einer verspotteten Komponentenabhängigkeit in eine zu testende Klasse verletzt sie daher nicht.

  2. Das Problem bei Test-Doubles (Mock / Stub) besteht darin, dass Sie grundsätzlich willkürliche Annahmen darüber treffen, wie die zu testende Klasse mit ihrer Umgebung interagiert. Wenn diese Erwartungen falsch sind, treten wahrscheinlich Probleme auf, sobald der Code bereitgestellt wird. Wenn Sie es sich leisten können, testen Sie Ihren Code unter denselben Bedingungen wie den, die für Ihre Produktionsumgebung gelten. Wenn dies nicht möglich ist, machen Sie die geringstmöglichen Annahmen und verspotten Sie nur die Peripheriegeräte Ihres Systems (Datenbank, Authentifizierungsdienst, HTTP-Client usw.).

Der einzig gültige Grund, warum, IMHO, ein Double verwendet werden sollte, ist, wenn Sie die Interaktionen mit der getesteten Klasse aufzeichnen müssen oder wenn Sie gefälschte Daten bereitstellen müssen (was mit beiden Techniken möglich ist). Seien Sie jedoch vorsichtig, da ein Missbrauch auf ein schlechtes Design oder einen Test zurückzuführen ist, der zu stark von der API in der Testimplementierung abhängt.

Francis Toth
quelle
6

Anmerkung: Ich nehme an, Sie Mock definieren bedeuten „ohne Implementierung einer Klasse, etwas können Sie überwachen“ und Stub zu sein „Teil Mock, auch bekannt als ein Teil des realen Verhaltens der implementierten Klasse verwendet“, wie pro diesem Stapel Überlauffrage .

Ich bin mir nicht sicher, warum Sie glauben, dass der Konsens darin besteht, Stichleitungen zu verwenden, zum Beispiel ist es in der Mockito-Dokumentation genau umgekehrt

Wie üblich lesen Sie die Teil-Mock-Warnung: Objektorientierte Programmierung verringert die Komplexität, indem sie in separate, spezifische SRPy-Objekte unterteilt wird. Wie passt partielle Verspottung in dieses Paradigma? Nun, es ist einfach nicht so ... Partielle Verspottung bedeutet normalerweise, dass die Komplexität auf eine andere Methode für dasselbe Objekt verschoben wurde. In den meisten Fällen ist dies nicht die Art und Weise, wie Sie Ihre Anwendung entwerfen möchten.

Es gibt jedoch seltene Fälle, in denen sich teilweise Mocks als nützlich erweisen: Der Umgang mit Code, den Sie nicht leicht ändern können (Schnittstellen von Drittanbietern, zwischenzeitliches Refactoring von Legacy-Code usw.). entworfener Code.

Diese Dokumentation sagt es besser als ich kann. Mit Mocks können Sie nur diese eine bestimmte Klasse testen und sonst nichts. Wenn Sie Teil-Mocks benötigen, um das gesuchte Verhalten zu erreichen, haben Sie wahrscheinlich etwas falsch gemacht, verstoßen gegen die SRP usw., und Ihr Code könnte einen Umgestalter vertragen. Mocks verstoßen nicht gegen das Open-Closed-Prinzip, da sie ohnehin nur in Tests verwendet werden. Sie sind keine wirklichen Änderungen an diesem Code. Normalerweise werden sie sowieso von einer Bibliothek wie cglib im laufenden Betrieb generiert.

durron597
quelle
2
Ausgehend von der SO-Frage (akzeptierte Antwort) ist dies auch die Mock / Stub-Definition, auf die ich mich bezog: Mock-Objekte werden verwendet, um Erwartungen zu definieren, dh: In diesem Szenario erwarte ich, dass Methode A () mit solchen und solchen Parametern aufgerufen wird. Mocks zeichnen solche Erwartungen auf und überprüfen sie. Stubs hingegen haben einen anderen Zweck: Sie erfassen oder überprüfen nicht die Erwartungen, sondern ermöglichen es uns, das Verhalten und den Status des "gefälschten" Objekts zu "ersetzen", um ein Testszenario zu verwenden ...
Christopher Francisco
2

Ich denke, das Problem könnte sich aus der Annahme ergeben, dass die einzigen gültigen Tests diejenigen sind, die den offenen / geschlossenen Test erfüllen.

Es ist leicht zu erkennen, dass die einzige Prüfung, die von Bedeutung sein sollte, die Prüfung der Schnittstelle ist. In der Realität ist es jedoch oft effektiver, diese Schnittstelle zu testen, indem Sie das Innenleben testen.

Beispielsweise ist es nahezu unmöglich, negative Anforderungen zu testen, z. B. "Die Implementierung darf keine Ausnahmen auslösen". Stellen Sie sich eine Kartenschnittstelle vor, die mit einer Hashmap implementiert wird. Sie möchten sicher sein, dass die Hashmap mit der Kartenoberfläche übereinstimmt, ohne sie zu werfen, auch wenn es erforderlich ist, Dinge erneut aufzuwärmen (die sich als problematisch erweisen könnten). Sie können jede Kombination von Eingaben testen, um sicherzustellen, dass sie die Schnittstellenanforderungen erfüllen. Dies kann jedoch länger dauern als der Hitzetod des Universums. Stattdessen unterbrechen Sie die Kapselung ein wenig und entwickeln Mocks, die enger miteinander interagieren. Dadurch wird die Hashmap gezwungen, genau die Aufbereitung durchzuführen, die erforderlich ist, um sicherzustellen, dass der Aufbereitungsalgorithmus nicht ausgelöst wird.

Tl / Dr: "By the Book" zu machen ist schön, aber wenn es hart auf hart kommt, ist es sinnvoller, ein Produkt bis Freitag auf dem Schreibtisch Ihres Chefs zu haben, als eine buchmäßige Testsuite, die bis zum heißen Tod des Unternehmens dauert Universum, um die Konformität zu bestätigen.

Cort Ammon - Setzen Sie Monica wieder ein
quelle