Wie verhindere ich, dass meine Komponententests Kenntnisse über Implementierungsinterna benötigen, wenn Mock-Objekte verwendet werden?

8

Ich bin immer noch in den Lernphasen bezüglich Komponententests und insbesondere im Hinblick auf Spott (ich benutze den PascalMock und DUnit Frameworks). Eine Sache, über die ich jetzt gestolpert bin, war, dass ich keinen Weg gefunden habe, um hartcodierte Implementierungsdetails der getesteten Klasse / Schnittstelle in meinen Komponententest zu finden, und das fühlt sich einfach falsch an ...

Beispiel: Ich möchte eine Klasse testen, die eine sehr einfache Schnittstelle zum Lesen und Schreiben von Anwendungseinstellungen implementiert (im Grunde Name / Wert-Paare). Die Schnittstelle, die dem Verbraucher präsentiert wird, ist völlig unabhängig davon, wo und wie die Werte tatsächlich gespeichert sind (z. B. Registrierung, INI-Datei, XML, Datenbank usw.). Natürlich wird die Zugriffsschicht durch eine andere Klasse implementiert, die in die getestete Klasse beim Aufbau injiziert wird. Ich habe ein Mock-Objekt für diese Zugriffsebene erstellt und bin jetzt in der Lage, die Interface-Implementierungsklasse vollständig zu testen, ohne tatsächlich etwas in eine Registry / INI-Datei / was auch immer zu lesen oder zu schreiben.

Um sicherzustellen, dass sich der Schein genau so verhält wie der reale Fall, wenn die getestete Klasse darauf zugreift, müssen meine Einheitentests das Scheinobjekt einrichten, indem sie explizit erwartete Methodenaufrufe und die von der getesteten Klasse erwarteten Rückgabewerte definieren . Dies bedeutet, dass, wenn ich jemals Änderungen an der Schnittstelle der Zugriffsebene oder an der Art und Weise vornehmen müsste, wie die getestete Klasse diese Schicht verwendet, ich auch die Komponententests für die Klasse ändern muss, die intern diese Schnittstelle verwendet, obwohl < starke> Schnittstelle der Klasse, die ich tatsächlich testen, hat sich überhaupt nicht geändert. Ist das etwas, mit dem ich nur leben muss, wenn ich Mocks benutze oder gibt es eine bessere Möglichkeit, die Klassenabhängigkeiten zu entwerfen, die das vermeiden würden?

    
Oliver Giesen 10.08.2010, 10:47
quelle

3 Antworten

6
  

Ist das etwas, was ich einfach tun muss?   leben mit wenn man Mocks benutzt oder da ist   eine bessere Art, das zu entwerfen   Klassenabhängigkeiten, die zu vermeiden wären   das?

Viele Mocks (besonders sensible Frameworks wie JMock) zwingen Sie dazu, Details zu berücksichtigen, die nicht direkt mit dem Verhalten zu tun haben, das Sie testen möchten. Manchmal kann dies sogar hilfreich sein, indem Sie verdächtigen Code aufdecken zu viel tun und hat zu viele Aufrufe / Abhängigkeiten.

Aber in Ihrem Fall, wenn ich Ihre Beschreibung richtig gelesen habe, klingt es so, als hätten Sie wirklich kein Problem. Wenn Sie den Lese- / Schreib-Layer korrekt und mit einem geeigneten Abstraktionsgrad entwerfen, sollten Sie ihn nicht ändern müssen.

  

Dies bedeutet, dass wenn ich jemals hätte   Änderungen an der Schnittstelle von   die Zugriffsschicht oder auf die Art und Weise   Die getestete Klasse verwendet diese Schicht I   muss auch die Einheit ändern   Tests für die Klasse, die intern   verwendet diese Schnittstelle, obwohl die   Schnittstelle der Klasse, die ich eigentlich bin   Testen hat sich überhaupt nicht geändert.

Schreibt man nicht die abstrahierte Zugriffsebene, um dies zu vermeiden? Im Allgemeinen sollte nach dem Open / Closed-Prinzip eine solche Schnittstelle nicht ändern und sollte nicht den Vertrag mit der Klasse brechen, die es verbraucht, und dadurch wird es auch nicht brechen Ihre Unit-Tests. Wenn Sie jetzt die Reihenfolge der Methodenaufrufe ändern oder neue Aufrufe für die abstrahierte Ebene vornehmen müssen, dann werden Ihre gespiegelten Erwartungen, insbesondere bei einigen Frameworks, gebrochen. Dies ist nur ein Teil der Kosten für die Verwendung von Mocks, und es ist durchaus akzeptabel. Aber die Schnittstelle selbst sollte im Allgemeinen stabil bleiben.

    
Dave Sims 10.08.2010, 16:55
quelle
7
  

Um sicherzustellen, dass sich der Mock genau so verhält wie der reale Fall, wenn die getestete Klasse darauf zugreift, müssen meine Komponententests das Mock-Objekt einrichten, indem sie explizit erwartete Methodenaufrufe und die von der getesteten Klasse erwarteten Rückgabewerte definieren.

Korrigieren.

  

Änderungen an der Schnittstelle der Zugriffsschicht oder an der Art und Weise, wie die getestete Klasse diese Schicht verwendet, muss ich auch die Komponententests ändern

Korrigieren.

  

obwohl die Schnittstelle der Klasse, die ich gerade teste, sich überhaupt nicht geändert hat.

"Tatsächlich testen"? Du meinst die exponierte Interface-Klasse? Das ist gut.

Die Art und Weise, in der die getestete Klasse (Schnittstelle) die Zugriffsschicht verwendet, bedeutet, dass Sie die interne Schnittstelle in die Zugriffsebene geändert haben. Schnittstellenänderungen (auch interne) erfordern Teständerungen und können zum Bruch führen, wenn Sie etwas falsch gemacht haben.

Damit ist nichts verkehrt. In der Tat ist der springende Punkt, dass jede Änderung an der Zugriffsebene muss Änderungen an den Mocks erfordern, um sicherzustellen, dass die Änderung "funktioniert".

Testen soll nicht "robust" sein. Es soll brüchig sein. Wenn Sie eine Änderung vornehmen, die das interne Verhalten ändert, können Dinge brechen . Wenn deine Tests zu robust wären, würden sie nichts testen - sie würden einfach funktionieren. Und das ist falsch.

Tests sollten nur für den genau richtigen Grund funktionieren.

    
S.Lott 10.08.2010 11:16
quelle
1

Nur um ein paar Namen in Ihr Beispiel zu bringen,

  • RegistryBasedDictionary implementiert das Role (interface) Dictionary.
  • RegistryBasedDictionary hat eine Abhängigkeit von der Rolle RegistryAccessor, die von RegistryWinAPIWrapper implementiert wird.

Sie sind derzeit daran interessiert, RegistryBasedDictionary zu testen. Die Komponententests würden eine Scheinabhängigkeit für die RegistryAccessor-Rolle einfügen und die erwartete Interaktion mit den Abhängigkeiten testen.

  • Der Trick hier, um unnötige Test-Wartung zu vermeiden, ist " Genau angeben, was passieren soll ... und nicht mehr. " (Von das GOOS-Buch (muss gelesen werden für TDD mit gefälschtem Geschmack), wenn also die Reihenfolge der Abhängigkeitsmethoden nicht wichtig ist, geben Sie es nicht im Test an frei, um die Reihenfolge der Anrufe in der Implementierung zu ändern.)
  • Entwerfen Sie die Rollen so, dass sie keine Lecks aus den tatsächlichen Implementierungen enthalten. - Behalten Sie die Implementierung der Rolle "agnostic" bei.

Der einzige Grund, RegistryBasedDictionary-Tests zu ändern, wäre eine Änderung im Verhalten von RegistryBasedDictionary und nicht in einer seiner Abhängigkeiten. Wenn sich also die Interaktion mit ihren Abhängigkeiten oder den Rollen / Verträgen ändert, müssten die Tests aktualisiert werden . Das ist der Preis interaktionsbasierter Tests, die Sie bezahlen müssen, um flexibel zu testen. In der Praxis passiert dies jedoch nicht oft.

    
Gishu 11.08.2010 10:02
quelle