Was ist der Zweck von Scheinobjekten?

167

Ich bin neu im Unit-Test und höre ständig die Worte "Scheinobjekte", die viel herumgeworfen werden. Kann jemand in Laienbegriffen erklären, was Scheinobjekte sind und wofür sie normalerweise beim Schreiben von Komponententests verwendet werden?

agentbanks217
quelle
12
Sie sind ein Werkzeug, um Dinge mit Flexibilität, die Sie für das vorliegende Problem nicht benötigen, massiv zu überarbeiten.
Dsimcha
2
mögliches Duplikat von Was ist Spott?
Nawfal

Antworten:

358

Da Sie sagen, dass Sie mit Unit-Tests noch nicht vertraut sind und nach Scheinobjekten in "Laienbegriffen" gefragt haben, werde ich das Beispiel eines Laien ausprobieren.

Unit Testing

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

cook <- waiter <- customer

Es ist im Allgemeinen leicht vorstellbar, eine Low-Level-Komponente wie die cookfolgende zu testen :

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 naiver Tester könnte die Kellnerkomponente 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 vom korrekten Verhalten der Kochkomponente abhängen kann. Diese Abhängigkeit ist noch schlimmer, wenn die Kochkomponente testunfreundliche Eigenschaften aufweist, wie nicht deterministisches Verhalten (das Menü enthält die Überraschung des Küchenchefs als Gericht), viele Abhängigkeiten (der Koch kocht nicht ohne sein gesamtes Personal) oder viele Ressourcen (einige Gerichte erfordern teure Zutaten oder brauchen eine Stunde zum Kochen).

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

Unit-Test bedeutet, Einheiten unabhängig zu testen. Ein besserer Ansatz wäre es, die zu testende Komponente (den Kellner) mithilfe von Test-Doubles (Dummies, Stubs, Fakes, Mocks) zu isolieren, die Fowler nennt .

    -----------------------
   |                       |
   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 Testkoch leicht ausgetauscht ( eingespritzt ) werden kann, um mit dem Kellner zu arbeiten, ohne den Produktionscode zu ändern (z. B. ohne den Kellnercode zu ändern).

Scheinobjekte

Jetzt kann der Testkoch (Testdoppel) auf verschiedene Arten implementiert werden:

  • ein gefälschter Koch - jemand, der vorgibt, ein Koch zu sein, indem er gefrorenes Abendessen und eine Mikrowelle benutzt,
  • ein Stub Cook - ein Hot Dog-Anbieter, der Ihnen immer Hot Dogs gibt, egal was Sie bestellen, oder
  • ein Scheinkoch - ein Undercover-Polizist, der einem Skript folgt, das vorgibt, ein Koch in einer Stichoperation zu sein.

In Fowlers Artikel finden Sie weitere Einzelheiten zu Fakes vs Stubs vs Mocks vs Dummies. Konzentrieren wir uns jedoch zunächst auf einen Scheinkoch.

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

Ein großer Teil des Unit-Tests der Kellnerkomponente konzentriert sich darauf, wie der Kellner mit der Kochkomponente interagiert. Ein scheinbasierter Ansatz konzentriert sich darauf, die richtige Interaktion vollständig zu spezifizieren und zu erkennen, wann sie schief geht.

Das Scheinobjekt weiß im Voraus, was während des Tests geschehen soll (z. B. welche seiner Methodenaufrufe aufgerufen werden usw.), und das Scheinobjekt weiß, wie es reagieren soll (z. B. welcher Rückgabewert bereitzustellen ist). Der Schein zeigt an, ob sich das, was wirklich passiert, 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. Ein Mocking-Framework ist jedoch bestrebt, eine solche Verhaltensspezifikation direkt im Testfall klar und einfach anzuzeigen.

Das Gespräch um einen scheinbasierten Test könnte folgendermaßen aussehen:

Testfahrer zum Verspotten des Kochs : Erwarten Sie eine Hot-Dog-Bestellung und geben Sie ihm diesen Dummy-Hot-Dog als Antwort

Testfahrer ( die sich als Kunde) zum Kellner : Ich würde einen Hotdog mögen bitte
Kellner verspotten kochen : 1 Hotdog bitte
Mock Koch zu Kellner : Reihenfolge: 1 Hot Dog bereit (gibt Dummy - Hotdog zu Kellner)
Kellner zu Testfahrer : Hier ist Ihr Hot Dog (gibt dem Testfahrer einen Dummy-Hot Dog)

Testfahrer : TEST ERFOLGREICH!

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

Testfahrer zum Verspotten des Kochs : Erwarten Sie eine Hot-Dog-Bestellung und geben Sie ihm diesen Dummy-Hot-Dog als Antwort

Testfahrer (posiert als Kunde) zum Kellner : Ich möchte einen Hot Dog, bitte
Kellner zum Verspotten des Kochs : 1 Hamburger, bitte zum
Verspotten des Kochs stoppt den Test: Mir wurde gesagt, dass ich eine Hot Dog-Bestellung erwarten soll!

Testfahrer stellt das Problem fest: TEST FEHLGESCHLAGEN! - Der Kellner hat die Bestellung geändert

oder

Testfahrer zum Verspotten des Kochs : Erwarten Sie eine Hot-Dog-Bestellung und geben Sie ihm diesen Dummy-Hot-Dog als Antwort

Testfahrer ( die sich als Kunde) zum Kellner : Ich würde einen Hotdog mögen bitte
Kellner verspotten kochen : 1 Hotdog bitte
Mock Koch zu Kellner : Reihenfolge: 1 Hot Dog bereit (gibt Dummy - Hotdog zu Kellner)
Kellner zu Testfahrer : Hier sind Ihre Pommes Frites (gibt Pommes Frites aus einer anderen Reihenfolge an den Testfahrer)

Testfahrer bemerkt die unerwarteten Pommes Frites: TEST FEHLGESCHLAGEN! Der Kellner gab falsches Gericht zurück

Es mag schwierig sein, den Unterschied zwischen Scheinobjekten und Stubs ohne ein kontrastierendes stubbasiertes Beispiel klar zu erkennen, aber diese Antwort ist schon viel zu lang :-)

Beachten Sie auch, dass dies ein ziemlich vereinfachtes Beispiel ist und dass Mocking-Frameworks einige ziemlich ausgefeilte Spezifikationen des erwarteten Verhaltens von Komponenten ermöglichen, um umfassende Tests zu unterstützen. Für weitere Informationen gibt es viel Material zu Scheinobjekten und Verspottungs-Frameworks.

Bert F.
quelle
12
Dies ist eine gute Erklärung, aber testen Sie nicht bis zu einem gewissen Grad die Implementierung des Kellners? In Ihrem Fall ist es wahrscheinlich in Ordnung, weil Sie überprüfen, ob die richtige API verwendet wird. Was ist jedoch, wenn es verschiedene Möglichkeiten gibt und der Kellner möglicherweise die eine oder die andere auswählt? Ich dachte, der Sinn des Unit-Tests bestand darin, die API und nicht die Implementierung zu testen. (Dies ist eine Frage, die ich mir immer stelle, wenn ich über Spott lese.)
Davidtbernal
8
Vielen Dank. Ich kann nicht sagen, ob wir die "Implementierung" testen, ohne die Spezifikation für den Kellner zu sehen (oder zu definieren). Sie können davon ausgehen, dass der Kellner das Gericht selbst kochen oder die Bestellung auf der Straße ausführen darf, aber ich gehe davon aus, dass die Spezifikation für den Kellner die Verwendung des vorgesehenen Küchenchefs beinhaltet - schließlich ist der Produktionskoch ein teurer Gourmetkoch, und wir ' Ich würde es vorziehen, wenn unser Kellner ihn benutzt. Ohne diese Spezifikation müsste ich wohl zu dem Schluss kommen, dass Sie Recht haben - der Kellner kann die Bestellung nach Belieben ausführen, um "korrekt" zu sein. OTOH, ohne Spezifikation ist der Test bedeutungslos. [Fortsetzung ...]
Bert F
8
NIEMALS machen Sie einen großartigen Punkt, der zum fantastischen Thema White Box vs. Black Box Unit Testing führt. Ich glaube nicht, dass es einen Branchenkonsens gibt, der besagt, dass Unit-Tests Black-Box statt White-Box sein müssen ("Testen Sie die API, nicht die Implementierung"). Ich denke, dass der beste Unit-Test wahrscheinlich eine Kombination aus beiden sein muss, um die Sprödigkeit des Tests gegen die Codeabdeckung und die Vollständigkeit des Testfalls abzuwägen.
Bert F
1
Diese Antwort ist meiner Meinung nach nicht technisch genug. Ich möchte wissen, warum ich ein Scheinobjekt verwenden sollte, wenn ich echte Objekte verwenden kann.
Niklas R.
1
Tolle Erklärung !! Danke!! @ BertF
Bharath Murali
28

Ein Scheinobjekt ist ein Objekt, das ein reales Objekt ersetzt. Bei der objektorientierten Programmierung sind Scheinobjekte simulierte Objekte, die das Verhalten realer Objekte auf kontrollierte Weise nachahmen.

Ein Computerprogrammierer erstellt normalerweise ein Scheinobjekt, um das Verhalten eines anderen Objekts zu testen, ähnlich wie ein Autodesigner einen Crashtest-Dummy verwendet, um das dynamische Verhalten eines Menschen bei Fahrzeugaufprallen zu simulieren.

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

Mock-Objekte ermöglichen es Ihnen, Testszenarien einzurichten, ohne große, unhandliche Ressourcen wie Datenbanken zu nutzen. Anstatt eine Datenbank zum Testen aufzurufen, können Sie Ihre Datenbank mithilfe eines Scheinobjekts in Ihren Komponententests simulieren. Dies befreit Sie von der Last, eine echte Datenbank einrichten und abbauen zu müssen, nur um eine einzelne Methode in Ihrer Klasse zu testen.

Das Wort "Mock" wird manchmal fälschlicherweise synonym 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 "Behauptungen") an das ordnungsgemäße Verhalten des zu testenden Objekts / der zu testenden 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 Objekte warehouseund mailermock mit den erwarteten Ergebnissen programmiert werden.

Robert Harvey
quelle
2
Die Definition, die Sie gegeben haben, unterscheidet sich nicht zuletzt von "Stub-Objekt" und erklärt daher nicht, was ein Scheinobjekt ist.
Brent Arias
Eine andere Korrektur "das Wort 'Mock' wird manchmal fälschlicherweise synonym mit 'stub' verwendet".
Brent Arias
@Myst: Die Verwendung der beiden Wörter ist nicht universell; es variiert zwischen den Autoren. Fowler sagt es und der Wikipedia-Artikel sagt es. Sie können die Änderung jedoch jederzeit bearbeiten und Ihre Ablehnung entfernen. :)
Robert Harvey
1
Ich stimme Robert zu: Die Verwendung des Wortes "Mock" variiert in der Regel in der Branche, aber meiner Erfahrung nach gibt es keine festgelegte Definition, außer dass es sich normalerweise NICHT um das tatsächlich getestete Objekt handelt, sondern um das Testen zu erleichtern, wenn das tatsächliche verwendet wird Objekt oder alle Teile davon wären sehr unpraktisch und von geringer Bedeutung.
mkelley33
15

Scheinobjekte sind simulierte Objekte, die das Verhalten der realen Objekte nachahmen. Normalerweise schreiben Sie ein Scheinobjekt, wenn:

  • Das reale Objekt ist zu komplex, um es in einen Komponententest einzubeziehen (z. B. bei einer Netzwerkkommunikation können Sie ein Scheinobjekt haben, das den anderen Peer simuliert).
  • Das Ergebnis Ihres Objekts ist nicht deterministisch
  • Das reale Objekt ist noch nicht verfügbar
Dani Cricco
quelle
12

Ein Mock-Objekt ist eine Art Test-Double . Sie verwenden Mockobjects, um das Protokoll / die Interaktion der zu testenden Klasse mit anderen Klassen zu testen und zu überprüfen.

In der Regel werden Sie die Erwartungen "programmieren" oder "aufzeichnen": Methodenaufrufe, die Ihre Klasse für ein zugrunde liegendes Objekt ausführen soll.

Angenommen, wir testen eine Dienstmethode, um ein Feld in einem Widget zu aktualisieren. Und dass es in Ihrer Architektur ein WidgetDAO gibt, das sich mit der Datenbank befasst. Das Gespräch mit der Datenbank ist langsam und das Einrichten und anschließende Bereinigen ist kompliziert. Daher werden wir das WidgetDao verspotten.

Lassen Sie uns überlegen, was der Dienst tun muss: Er sollte ein Widget aus der Datenbank abrufen, etwas damit tun und es erneut speichern.

In einer Pseudosprache mit einer Pseudo-Mock-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 die Entwicklung von Klassen, die von anderen Klassen abhängen, problemlos testen.

Peter Tillemans
quelle
11

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

Adam Byrtek
quelle
10
Nicht gerade anfängerfreundlich, oder?
Robert Harvey
@ Robert Harvey: Vielleicht ist es trotzdem gut zu sehen, dass es hilfreich war, Ihre Antwort zu klären :)
Adam Byrtek
Martin Fowler Artikel sind in der Art von RFCs geschrieben: trocken und kalt.
Revo
9

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

Schauen Sie sich zum Beispiel den folgenden Pseudocode aus einem imaginären Teil eines Programms an, das ein anderes Programm verwendet, um etwas aufzurufen:

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

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

Hier kommen Scheinobjekte ins Spiel. Sie geben vor, andere Arten von Dingen zu sein. In diesem Fall würden Sie einen Mock verwenden, Printerdamit er sich wie ein echter Drucker verhält, aber keine unbequemen Dinge wie das Drucken ausführt.


Es gibt verschiedene andere Arten von vorgetäuschten Objekten, die Sie verwenden können und die keine Mocks sind. Die Hauptsache, die Mocks Mocks ausmacht, ist, dass sie mit Verhalten und Erwartungen konfiguriert werden können.

Durch die Erwartungen kann Ihr Mock einen Fehler auslösen, wenn er falsch verwendet wird. Im obigen Beispiel möchten Sie möglicherweise sicherstellen, dass der Drucker im Testfall "Benutzer ist Fred" mit HelloFred aufgerufen wird. Wenn das nicht passiert, kann Ihr Mock Sie warnen.

Verhalten in Mocks bedeutet, dass beispielsweise Ihr Code Folgendes getan hat:

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

Jetzt möchten Sie testen, was Ihr Code tut, wenn der Drucker aufgerufen wird und SaidHello zurückgibt, damit Sie den Mock so einrichten können, dass SaidHello zurückgegeben wird, wenn er mit HelloFred aufgerufen wird.

Eine gute Ressource ist Martin Fowlers Post Mocks Aren't Stubs

David Hall
quelle
7

Mock- und Stub-Objekte sind ein wesentlicher Bestandteil der Unit-Tests. Tatsächlich tragen sie wesentlich dazu bei, dass Sie Einheiten und nicht Gruppen von Einheiten testen .

Auf den Punkt gebracht, verwenden Sie Stubs die brechen SUT (System Under Test) die Abhängigkeit von anderen Objekten und spottet , das zu tun und sicherstellen , dass SUT bestimmte Methoden / Eigenschaften auf die Abhängigkeit genannt. Dies geht auf die Grundprinzipien des Komponententests zurück - dass die Tests leicht lesbar und schnell sein sollten und keine Konfiguration erfordern, was die Verwendung aller realen Klassen implizieren könnte.

Im Allgemeinen können Sie mehr als einen Stub in Ihrem Test haben, aber Sie sollten nur einen Mock haben. Dies liegt daran, dass Mock das Verhalten überprüfen soll und Ihr Test nur eines testen sollte.

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<IInput> input = new Mock<IInput>();
  //output is a mock, because we want to verify some behavior on it.
  Mock<IOutput> output = new Mock<IOutput>();

  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 für beide dieselbe Klasse - Mock<T>was es etwas verwirrend macht. Unabhängig davon schlägt der Test zur Laufzeit fehl, wenn er output.Writenicht mit data as parameteraufgerufen input.Read()wird , während ein fehlgeschlagener Aufruf nicht fehlschlägt.

Igor Zevaka
quelle
4

Wie eine andere Antwort über einen Link zu " Mocks Aren't Stubs " vorschlägt, sind Mocks eine Form von "Test Double", die anstelle eines realen Objekts verwendet werden kann. Was sie von anderen Formen von Test-Doubles wie Stub-Objekten unterscheidet, ist, dass andere Test-Doubles eine Zustandsüberprüfung (und optional eine Simulation) bieten, während Mocks eine Verhaltensüberprüfung (und optional eine Simulation) bieten.

Mit einem Stub können Sie mehrere Methoden für den Stub in beliebiger Reihenfolge (oder sogar wiederholt) aufrufen und den Erfolg bestimmen, wenn der Stub einen von Ihnen beabsichtigten Wert oder Status erfasst hat. Im Gegensatz dazu erwartet ein Scheinobjekt, dass sehr bestimmte Funktionen in einer bestimmten Reihenfolge und sogar eine bestimmte Anzahl von Malen aufgerufen werden. Der Test mit einem Scheinobjekt wird einfach deshalb als "fehlgeschlagen" betrachtet, weil die Methoden in einer anderen Reihenfolge oder Anzahl aufgerufen wurden - selbst wenn das Scheinobjekt zum Zeitpunkt des Testabschlusses den richtigen Status hatte!

Auf diese Weise werden Scheinobjekte häufig 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.

Brent Arien
quelle
3

Ein Teil der Verwendung von Scheinobjekten besteht darin, dass sie nicht wirklich gemäß den Spezifikationen implementiert werden müssen. Sie können nur Dummy-Antworten geben. Wenn Sie beispielsweise die Komponenten A und B implementieren müssen und beide miteinander "aufrufen" (interagieren), können Sie A erst testen, wenn B implementiert ist, und umgekehrt. In der testgetriebenen Entwicklung ist dies ein Problem. Sie erstellen also Scheinobjekte ("Dummy") für A und B, die sehr einfach sind, aber bei der Interaktion eine Art Antwort geben . Auf diese Weise können Sie A mithilfe eines Scheinobjekts für B implementieren und testen.

LarsH
quelle
1

Für PHP und PHPUNIT wird in PHPUNIT Documentaion gut erklärt. siehe hier phpunit dokumentation

In einfachen Worten ist das Mocking-Objekt nur ein Dummy-Objekt Ihres ursprünglichen Objekts und gibt seinen Rückgabewert zurück. Dieser Rückgabewert kann in der Testklasse verwendet werden

Gautam Rai
quelle
0

Dies ist eine der Hauptperspektiven von Unit-Tests. Ja, Sie versuchen, Ihre einzelne Codeeinheit zu testen, und Ihre Testergebnisse sollten für das Verhalten anderer Beans oder Objekte nicht relevant sein. Sie sollten sie daher verspotten, indem Sie Mock-Objekte mit einer vereinfachten entsprechenden Antwort verwenden.

Mohsen Msr
quelle