Was ist der Zweck von Scheinobjekten?

Ich bin neu in der Unit-Prüfung, und ich höre immer wieder die Worte “Mock-Objekte” herumgeworfen. Kann der Laie sagen, was Mock-Objekte sind und wofür sie typischerweise beim Komponententest verwendet werden?

   

Da du sagst, dass du neu bei Unit-Tests bist und nach Mock-Objekten fragst, werde ich das Beispiel eines Laien versuchen.

Komponententest

Stellen Sie sich ein Unit-Testing für dieses System vor:

cook < - waiter <- customer 

Es ist generell einfach, sich das Testen einer Low-Level-Komponente wie dem cook vorzustellen:

 cook < - test driver 

Der Testfahrer bestellt einfach verschiedene Gerichte und überprüft, ob der Koch für jede Bestellung das richtige Gericht zurückgibt.

Es ist schwieriger, eine mittlere Komponente wie den Kellner zu testen, die das Verhalten anderer Komponenten nutzt. Ein naive Tester könnte die Kellner-Komponente genauso testen wie wir die Kochkomponente getestet haben:

 cook < - waiter <- test driver 

Der Testfahrer würde verschiedene Gerichte bestellen und sicherstellen, dass der Kellner das richtige Gericht zurückgibt. Leider bedeutet dies, dass dieser Test der Kellnerkomponente von dem korrekten Verhalten der Kochkomponente abhängen kann. Diese Abhängigkeit ist noch schlimmer, wenn die Köchin-Komponente irgendwelche Test-unfreundlichen Eigenschaften hat, wie nicht-deterministisches Verhalten (die Speisekarte enthält die Überraschung des Kochs als ein Gericht), viele Abhängigkeiten (Koch wird nicht ohne sein gesamtes Personal kochen) oder viel Ressourcen (einige Gerichte erfordern teure Zutaten oder eine Stunde zum Kochen).

Da dies ein Kellner-Test ist, möchten wir idealerweise nur den Kellner, nicht den Koch, testen. Insbesondere möchten wir sicherstellen, dass der Kellner die Bestellung des Kunden korrekt an den Koch weitergibt und das Essen des Kochs korrekt an den Kunden liefert.

Unit-Test bedeutet, dass die Testeinheiten unabhängig voneinander getestet werden. Ein besserer Ansatz wäre also, die zu testende Komponente (den Kellner) zu isolieren, indem man das verwendet, was Fowler Test-Double nennt (Dummies, Stubs, Fälschungen, Mocks) .

  ----------------------- | | v | test cook < - waiter <- test driver 

Hier ist der Testkoch mit dem Testfahrer "in Cahoots". Idealerweise ist das zu testende System so ausgelegt, dass der Testkocher leicht ausgetauscht ( injiziert ) werden kann, um mit dem Kellner zu arbeiten, ohne den Produktionscode zu ändern (z. B. ohne den Kellnercode zu ändern).

Mock-Objekte

Nun könnte der Testkoch (Testdoppel) auf verschiedene Arten implementiert werden:

  • ein falscher Koch - jemand, der vorgibt, ein Koch zu sein, indem er Tiefkühlgerichte und eine Mikrowelle benutzt,
  • ein Stubenkoch - ein Hot Dog-Verkäufer, der Ihnen immer Hot Dogs gibt, egal was Sie bestellen, oder
  • ein Scheinkoch - ein Undercover-Polizist, der einem Drehbuch folgt, das vorgibt, Koch in einer Stacheloperation zu sein.

Siehe Fowlers Artikel für die Details über Fakes vs Stubs vs Mocks vs Dummies , aber für jetzt, konzentrieren wir uns auf einen Mock Köchin.

  ----------------------- | | v | mock cook < - waiter <- test driver 

Ein großer Teil der Einheit, die die Kellnerkomponente prüft, konzentriert sich darauf, wie der Kellner mit der Kochkomponente interagiert. Ein auf Mock basierender Ansatz konzentriert sich darauf, vollständig anzugeben, was die richtige Interaktion ist, und zu erkennen, wann es schief geht.

Das Mock-Objekt weiß im Voraus, was während des Tests passieren soll (z. B. welcher der Methodenaufrufe aufgerufen werden soll etc.) und das Mock-Objekt weiß, wie es reagieren soll (z. B. welcher Rückgabewert). Der Schein wird anzeigen, ob das, was wirklich passiert, sich von dem unterscheidet, was passieren soll. Ein benutzerdefiniertes Mock-Objekt könnte für jeden Testfall von Grund auf neu erstellt werden, um das erwartete Verhalten für diesen Testfall auszuführen, aber ein Mocking-Framework versucht, eine solche Verhaltensspezifikation direkt im Testfall klar und einfach anzuzeigen.

Die Konversation um einen Scheintest könnte folgendermaßen aussehen:

Testfahrer, zum des Kochs zu verspotten : erwarten Sie einen Würstchenauftrag und geben Sie ihm diesen blinden Hotdog als Antwort

Testfahrer (posiert als Kunde) zum Kellner : Ich hätte gerne einen Hot Dog
Kellner zum Kichern : 1 Hot Dog bitte
Mock Koch zum Kellner : Bestellung: 1 Hot Dog bereit (gibt Dummy Hot Dog zum Kellner)
Kellner , um Fahrer zu testen : Hier ist Ihr Hot Dog (gibt Dummy Hot Dog zum Testfahrer)

Testfahrer : TEST ERFOLGREICH!

Aber da unser Kellner neu ist, könnte das passieren:

Testfahrer, zum des Kochs zu verspotten : erwarten Sie einen Würstchenauftrag und geben Sie ihm diesen blinden Hotdog als Antwort

Testfahrer (posiert als Kunde) zum Kellner : Ich hätte gerne einen Hot Dog
Kellner zum Köcheln : 1 Hamburger bitte
Scheinkoch stoppt den Test: Ich wurde aufgefordert, einen Hot Dog-Auftrag zu erwarten!

Testfahrer bemerkt das Problem: Test fehlgeschlagen! - Der Kellner hat die Bestellung geändert

oder

Testfahrer, zum des Kochs zu verspotten : erwarten Sie einen Würstchenauftrag und geben Sie ihm diesen blinden Hotdog als Antwort

Testfahrer (posiert als Kunde) zum Kellner : Ich hätte gerne einen Hot Dog
Kellner zum Kichern : 1 Hot Dog bitte
Mock Koch zum Kellner : Bestellung: 1 Hot Dog bereit (gibt Dummy Hot Dog zum Kellner)
Kellner , um Fahrer zu testen : Hier ist Ihre Pommes Frites (gibt Pommes Frites von einem anderen Auftrag, um Fahrer zu testen)

Testfahrer notiert die unerwarteten Pommes Frites: TEST FAILED! der Kellner gab ein falsches Gericht zurück

Es mag schwierig sein, den Unterschied zwischen Mock-Objekten und Stubs ohne ein kontrastierendes stub-basiertes Beispiel zu sehen, aber diese Antwort ist schon viel zu lang 🙂

Beachten Sie auch, dass dies ein ziemlich simples Beispiel ist und dass spöttische Frameworks einige ziemlich anspruchsvolle Spezifikationen des erwarteten Verhaltens von Komponenten ermöglichen, um umfassende Tests zu unterstützen. Es gibt viel Material über Mock-Objekte und spöttische Frameworks für mehr Informationen.

Ein Mock-Objekt ist ein Objekt, das ein reales Objekt ersetzt. In der objektorientierten Programmierung sind Mock-Objekte simulierte Objekte, die das Verhalten realer Objekte auf kontrollierte Weise nachahmen.

Ein Computerprogrammierer erzeugt typischerweise ein Mock-Objekt, um das Verhalten eines anderen Objekts zu testen, ähnlich wie ein Autodesigner einen Crash-Test-Dummy verwendet, um das dynamische Verhalten eines Menschen in Fahrzeugstößen zu simulieren.

http://en.wikipedia.org/wiki/Mock_object

Mit Mock-Objekten können Sie Testszenarien einrichten, ohne große, unhandliche Ressourcen wie databaseen zu verwenden. Anstatt eine database zum Testen aufzurufen, können Sie Ihre database mit einem Mock-Objekt in Ihren Komponententests simulieren. Dies befreit Sie von der Last, eine echte database einrichten und abreißen zu müssen, nur um eine einzige Methode in Ihrer class zu testen.

Das Wort “Mock” wird manchmal fälschlicherweise austauschbar mit “Stub” verwendet. Die Unterschiede zwischen den beiden Wörtern werden hier beschrieben. Im Wesentlichen ist ein Mock ein Stub-Objekt, das auch die Erwartungen (dh “Assertions”) für das richtige Verhalten des zu testenden Objekts / der Methode enthält.

Beispielsweise:

 class OrderInteractionTester... public void testOrderSendsMailIfUnfilled() { Order order = new Order(TALISKER, 51); Mock warehouse = mock(Warehouse.class); Mock mailer = mock(MailService.class); order.setMailer((MailService) mailer.proxy()); mailer.expects(once()).method("send"); warehouse.expects(once()).method("hasInventory") .withAnyArguments() .will(returnValue(false)); order.fill((Warehouse) warehouse.proxy()); } } 

Beachten Sie, dass die warehouse und mailer Mock-Objekte mit den erwarteten Ergebnissen programmiert sind.

Mock-Objekte sind simulierte Objekte, die das Verhalten der realen nachahmen. Normalerweise schreiben Sie ein Mock-Objekt, wenn

  • Das reale Objekt ist zu komplex, um es in einen Unit-Test zu integrieren (zum Beispiel eine Netzwerkkommunikation, Sie können ein Mock-Objekt haben, das den anderen Peer simuliert hat)
  • Das Ergebnis Ihres Objekts ist nicht deterministisch
  • Das reale Objekt ist noch nicht verfügbar

Ein Mock-Objekt ist eine Art von Test-Double . Sie verwenden Scheinobjekte, um das Protokoll / die Interaktion der getesteten class mit anderen classn zu testen und zu verifizieren.

In der Regel werden Sie die Erwartungen “programmieren” oder “aufzeichnen”: Methodenaufrufe erwarten Sie von Ihrer class an einem zugrunde liegenden Objekt.

Sagen wir zum Beispiel, wir testen eine Service-Methode, um ein Feld in einem Widget zu aktualisieren. Und das in Ihrer Architektur gibt es einen WidgetDAO, der sich mit der database beschäftigt. Das Gespräch mit der database ist langsam und die Einrichtung und das anschließende Putzen ist kompliziert, daher werden wir das WidgetDao ausspionieren.

Lass uns überlegen, was der Dienst tun soll: Er sollte ein Widget von der database bekommen, etwas damit machen und es erneut speichern.

In einer Pseudo-Sprache mit einer Pseudo-Schein-Bibliothek hätten wir also so etwas wie:

 Widget sampleWidget = new Widget(); WidgetDao mock = createMock(WidgetDao.class); WidgetService svc = new WidgetService(mock); // record expected calls on the dao expect(mock.getById(id)).andReturn(sampleWidget); expect(mock.save(sampleWidget); // turn the dao in replay mode replay(mock); svc.updateWidgetPrice(id,newPrice); verify(mock); // verify the expected calls were made assertEquals(newPrice,sampleWidget.getPrice()); 

Auf diese Weise können wir leicht die Entwicklung von classn testen, die von anderen classn abhängen.

Ich empfehle einen großartigen Artikel von Martin Fowler, der erklärt, was genau Mocks sind und wie sie sich von Stubs unterscheiden.

Wenn Sie einen Teil eines Computerprogramms testen, möchten Sie idealerweise nur das Verhalten dieses bestimmten Teils testen.

Betrachten Sie zum Beispiel den folgenden Pseudo-Code von einem imaginären Teil eines Programms, das ein anderes Programm verwendet, um etwas zu drucken:

 If theUserIsFred then Call Printer(HelloFred) Else Call Printer(YouAreNotFred) End 

Wenn Sie dies testen, möchten Sie hauptsächlich den Teil testen, der sich anschaut, ob der Benutzer Fred ist oder nicht. Sie möchten den Printer Teil der Dinge nicht wirklich testen. Das wäre ein weiterer Test.

Hier kommen Mock-Objekte ins Spiel. Sie geben vor, andere Dinge zu sein. In diesem Fall würden Sie einen Mock Printer so dass er sich wie ein echter Drucker verhält, aber keine unbequemen Dinge wie Drucken tut.


Es gibt mehrere andere Arten von vorgetäuschten Objekten, die Sie verwenden können, die keine Mocks sind. Die wichtigste Sache, die Mocks Mocks macht, ist, dass sie mit Verhaltensweisen und Erwartungen konfiguriert werden können.

Erwartungen erlauben Ihrem Mock, einen Fehler zu melden, wenn es falsch verwendet wird. Im obigen Beispiel können Sie also sicher sein, dass der Drucker im Testfall “Benutzer ist Fred” mit HelloFred aufgerufen wird. Wenn das nicht passiert, kann dein Mock dich warnen.

Verhalten in Mocks bedeutet, dass zum Beispiel in Ihrem Code etwas wie

 If Call Printer(HelloFred) Returned SaidHello Then Do Something End 

Jetzt möchten Sie testen, was Ihr Code macht, wenn der Drucker aufgerufen wird und SaidHello zurückgibt. Sie können also das Mock so einrichten, dass SaidHello zurückgegeben wird, wenn es mit HelloFred aufgerufen wird.

Eine gute Ressource in diesem Bereich ist Martin Fowlers post Mocks Are not Stubs

Mock- und Stub-Objekte sind ein entscheidender Teil des Unit-Tests. In der Tat sind sie ein langer Weg, um sicherzustellen, dass Sie Einheiten testen, anstatt Gruppen von Einheiten.

Kurz gesagt, Sie verwenden Stubs, um die Abhängigkeit von SUTs (System Under Test) von anderen Objekten und Mocks zu unterbrechen und zu überprüfen, ob SUT bestimmte Methoden / Eigenschaften für die Abhängigkeit aufgerufen hat. Dies geht auf die grundlegenden Prinzipien der Komponententests zurück – dass die Tests leicht lesbar, schnell und ohne Konfiguration sein sollten, was die Verwendung aller realen classn bedeuten könnte.

Im Allgemeinen können Sie mehr als einen Stummel in Ihrem Test haben, aber Sie sollten nur einen Schein haben. Dies liegt daran, dass der Zweck des Spottes darin besteht, das Verhalten zu verifizieren, und der Test sollte nur eine Sache testen.

Einfaches Szenario mit C # und Moq:

 public interface IInput { object Read(); } public interface IOutput { void Write(object data); } class SUT { IInput input; IOutput output; public SUT (IInput input, IOutput output) { this.input = input; this.output = output; } void ReadAndWrite() { var data = input.Read(); output.Write(data); } } [TestMethod] public void ReadAndWriteShouldWriteSameObjectAsRead() { //we want to verify that SUT writes to the output interface //input is a stub, since we don't record any expectations Mock input = new Mock(); //output is a mock, because we want to verify some behavior on it. Mock output = new Mock(); var data = new object(); input.Setup(i=>i.Read()).Returns(data); var sut = new SUT(input.Object, output.Object); //calling verify on a mock object makes the object a mock, with respect to method being verified. output.Verify(o=>o.Write(data)); } 

Im obigen Beispiel habe ich Moq verwendet, um Stubs und Mocks zu demonstrieren. Moq verwendet dieselbe class für beide – Mock , was es etwas verwirrend macht. Ungeachtet dessen wird der Test zur Laufzeit fehlschlagen, wenn output.Write nicht mit Daten als parameter aufgerufen parameter , während ein Fehler beim Aufruf von input.Read() nicht fehlschlägt.

Als eine andere Antwort, die über einen Link zu ” Mocks Are not Stubs ” vorgeschlagen wurde, sind Mocks eine Form von “Test Double”, die anstelle eines realen Objekts zu verwenden ist. Sie unterscheiden sich von anderen Formen von Testdoppelwerten wie Stubobjekten dadurch, dass andere Testdoppelungen eine Zustandsverifikation (und optional eine Simulation) bieten, während Mocks eine Verhaltensverifikation (und optional eine Simulation) bieten.

Mit einem Stub können Sie mehrere Methoden auf dem Stub in beliebiger Reihenfolge (oder sogar wiederholt) aufrufen und den Erfolg bestimmen, wenn der Stub einen Wert oder einen gewünschten Status erfasst hat. Im Gegensatz dazu erwartet ein Mock-Objekt, dass sehr spezifische functionen in einer bestimmten Reihenfolge und sogar eine bestimmte Anzahl von Malen aufgerufen werden. Der Test mit einem Mock-Objekt wird als “fehlgeschlagen” betrachtet, einfach weil die Methoden in einer anderen Reihenfolge oder Anzahl aufgerufen wurden – selbst wenn das Mock-Objekt den korrekten Zustand hatte, als der Test abgeschlossen war!

Auf diese Weise werden Mock-Objekte oft als enger an den SUT-Code gekoppelt betrachtet als Stub-Objekte. Das kann gut oder schlecht sein, je nachdem, was Sie überprüfen möchten.

Teil der Verwendung von Mock-Objekten ist, dass sie nicht wirklich gemäß Spezifikation implementiert werden müssen. Sie können nur Dummy-Antworten geben. Wenn Sie z. B. die Komponenten A und B implementieren und beide miteinander “aufrufen” (interagieren) müssen, können Sie A erst testen, wenn B implementiert ist und umgekehrt. In der testgetriebenen Entwicklung ist dies ein Problem. Sie erstellen also Schein- (“Dummy”) Objekte für A und B, die sehr einfach sind, aber sie geben eine Art von Antwort, wenn sie interagiert werden. Auf diese Weise können Sie A unter Verwendung eines Mock-Objekts für B implementieren und testen.

Für php und phpunit ist in phpunit documentaion gut erklärt. siehe hier phpunit Dokumentation

Im einfachen Wort Mocking-Objekt ist nur Dummy-Objekt Ihres ursprünglichen und gibt seinen Rückgabewert zurück, dieser Rückgabewert kann in der Testklasse verwendet werden