2009-07-01 12 views
4

Wo immer möglich, ich TDD verwenden:ohne Code von Testabdeckung

  • ich meine Schnittstellen verspotten
  • ich IOC verwenden, so kann meine verspottet ojbects
  • injiziert werden, ich meine Tests sicherzustellen laufen und dass die Abdeckung steigt und ich bin glücklich.

dann ...

  • I create Klassen abgeleitet, die tatsächlich Dinge zu tun, wie in einer Datenbank, oder das Schreiben an eine Nachrichtenwarteschlange usw.

Dies ist, wo Code-Coverage nimmt ab - und ich bin traurig.

Aber dann verbreite ich großzügig [CoverageExclude] über diese konkreten Klassen und Abdeckung geht wieder hoch.

Aber dann fühle ich mich nicht traurig, sondern schmutzig. Ich habe irgendwie das Gefühl zu betrügen, obwohl es nicht möglich ist, die konkreten Klassen zu testen.

Ich bin interessiert zu hören, wie Ihre Projekte organisiert sind, das heißt, wie Sie ordnen Sie körperlich Code dass kann gegen Code getestet werden, die nicht getestet werden.

Ich denke, dass eine nette Lösung wäre, untestable Betontypen in ihre eigene Baugruppe zu trennen und dann die Verwendung von [CoverageExclude] in den Baugruppen zu verbieten, die testbaren Code enthalten. Dies würde es auch einfacher machen, eine NDepend-Regel zu erstellen, die das Build fehlschlägt, wenn dieses Attribut in den testbaren Assemblys nicht korrekt gefunden wird.


Edit: die Essenz dieser Frage berührt die Tatsache, dass man die Dinge testen können, die Ihre verspottet Schnittstellen verwenden, aber Sie können nicht UNIT-Test die Objekte, die die real sind (oder sollte nicht!) Implementierungen dieser Schnittstellen. Hier ein Beispiel:

public void ApplyPatchAndReboot() 
{ 
    _patcher.ApplyPatch() ; 
    _rebooter.Reboot() ; 
} 

Patcher und rebooter werden im Konstruktor injiziert:

public SystemUpdater(IApplyPatches patcher, IRebootTheSystem rebooter)... 

Die Unit-Test wie folgt aussieht:

public void should_reboot_the_system() 
{ 
    ... new SystemUpdater(mockedPatcher, mockedRebooter); 
    update.ApplyPatchAndReboot(); 
} 

Dies funktioniert gut - meine UNIT-Testabdeckung 100%. Ich schreibe jetzt:

Meine UNIT-TEST Abdeckung geht runter und es gibt keine Möglichkeit zu UNIT-TEST der neuen Klasse. Sicher, ich werde einen Funktionstest hinzufügen und es ausführen, wenn ich 20 Minuten übrig habe (!).

Also, ich meine meine Frage läuft darauf hinaus, dass es in der Nähe von 100% UNIT-TEST Abdeckung schön ist. Anders gesagt, es ist schön, in der Lage zu sein, 100% des Verhaltens des Systems in Einheiten zu testen. Im obigen Beispiel sollte der BEHAVIOR des Patcher den Rechner neu starten. Das können wir sicher verify. Der Typ ReallyRebootTheSytemForReal ist nicht nur Verhalten - es hat Nebenwirkungen, was bedeutet, dass es nicht Unit-getestet werden kann. Da es kein Unit-Test sein kann, wirkt sich dies auf den Testabdeckungsprozentsatz aus.Also,

  • Ist es wichtig, dass diese Dinge Unit-Test-Abdeckung Percentage reduzieren?
  • Sollten sie in ihre eigenen Baugruppen getrennt werden, wo die Leute 0% UNIT-TEST Abdeckung erwarten?
  • Sollten Typen Betons wie diese so klein sein (in zyklomatische Komplexität), dass ein Unit-Test (oder anderweitig)

+0

Was meinst du mit "kann nicht getestet werden" ?? Was sind die Barrieren? – Juri

+0

Dinge wie bestimmte Implementierungen einer Schnittstelle, die unantastbare Bereiche berühren, wie Nachrichtenwarteschlangen, Datenbanken, das Dateisystem usw. Zum Beispiel kann eine Schnittstelle wie IWriteToAQueue verspottet werden und alle Bits, die eine IWriteToAQueue erwarten, können mit dem Mock getestet werden. Aber - der konkrete Typ namens "WriteToMsmq", der gerade in MSMQ schreibt, kann nicht Unit-getestet werden. –

Antwort

4

Sie sind auf dem richtigen Weg. Einige der konkreten Implementierungen, die Sie wahrscheinlich können Test, wie Datenzugriffskomponenten. Ein automatisiertes Testen gegen eine relationale Datenbank ist sicherlich möglich, sollte aber auch in einer eigenen Bibliothek (mit einer entsprechenden Komponententestbibliothek) ausgeklammert werden.

Da Sie Dependency Injection bereits verwenden, sollte es ein Kinderspiel sein, eine solche Abhängigkeit wieder in Ihre reale Anwendung einzubauen.

Auf der anderen Seite wird es auch konkrete Abhängigkeiten geben, die im Wesentlichen nicht testbar (oder testbar, wie Fowler einmal scherzte). Solche Implementierungen sollten so dünn wie möglich gehalten werden. Oft ist es möglich, die API so zu gestalten, dass eine solche Abhängigkeit in einer Weise auftritt, dass die gesamte Logik im Verbraucher passiert und die Komplexität der realen Implementierung sehr gering ist.

Die Implementierung solcher konkreten Abhängigkeiten ist eine explizite Entwurfsentscheidung, und wenn Sie diese Entscheidung treffen, entscheiden Sie gleichzeitig, dass eine solche Bibliothek nicht Unit-getestet werden sollte, und daher sollte die Codeabdeckung nicht gemessen werden.

Eine solche Bibliothek heißt Humble Object. Es (und viele andere Muster) sind in der ausgezeichneten xUnit Test Patterns beschrieben.

Als Faustregel akzeptiere ich, dass der Code nicht getestet ist, wenn er eine zyklomatische Komplexität von hat. In diesem Fall ist es mehr oder weniger rein deklarativ. Pragmatisch, nicht testbare Komponenten sind in Ordnung, solange sie niedrig Cyclomatic Complexity haben. Wie niedrig Sie sind, müssen Sie selbst entscheiden.

In jedem Fall scheint [CoverageExclude] wie ein Geruch für mich (ich wusste nicht einmal, dass es existiert, bevor ich Ihre Frage gelesen habe).

+0

+1: ausgezeichneter Lesevorschlag –

+0

Danke für den Kommentar Mark. Ich werde sicherlich das Testmusterbuch lesen. Ich werde mir auch das CC ansehen; Ich vermute, dass es sehr niedrig ist. Nur um klar zu sein, wenn ich das Wort "Test" verwendete, meinte ich rein "Unit-Test", d.h. das Verhalten verschiedener Typen zu testen. Das CoverageExclude-Attribut wird von NCover erkannt. –

1

Ich verstehe nicht, wie Sie Ihre konkreten Klassen untestable sind überflüssig. Das riecht schrecklich für mich.

Wenn Sie eine konkrete Klasse haben, die in eine Nachrichtenwarteschlange schreibt, sollten Sie in der Lage sein, eine Scheinwarteschlange zu übergeben und alle Methoden zu testen. Wenn Ihre Klasse zu einer Datenbank geht, sollten Sie in der Lage sein, ihr eine Pseudo-Datenbank zu übergeben.

Es kann Situationen geben, die zu nicht testbarem Code führen können, ich werde das nicht leugnen - aber das sollte die Ausnahme sein, nicht die Regel. Alle Ihre konkreten Klassenarbeitsobjekte? Etwas stimmt nicht.

+0

Wenn Ihre Ressourcenzugriffskomponente mit einem Legacy-System oder einem öffentlichen Webdienst kommuniziert, ist es ziemlich schwierig, die tatsächliche Implementierung zu testen, die mit solchen Systemen kommuniziert. –

+1

Womp, einige der konkreten Klassen sind nicht testbar, weil sie Dinge wie Datenbanken, Nachrichtenwarteschlangen etc. berühren. Sie sagen, wenn ich eine konkrete Klasse habe, die in eine Nachrichtenwarteschlange schreibt, kann ich ihr eine Scheinwarteschlange übergeben. Das ist völlig richtig, aber letztendlich wird es eine Reihe von Software geben, die physikalisch in die Warteschlange schreibt: die ultimative konkrete Implementierung, die eine Warteschlange physisch berührt. Dies ist nicht testbar, obwohl alles, was es verwendet (oder genauer gesagt, seine Schnittstelle), getestet wurde. –

+0

Wenn Sie Ihre Warteschlange injizieren, dann macht Ihre Klasse nichts anderes als die eigentliche Warteschlange. Es berührt physikalisch eine Warteschlange, es schreibt physikalisch in die Warteschlange. Das kannst du testen. Es gibt dort keine Codezeile, die nicht getestet werden kann. – womp

1

Um auf Womps zu erweitern Antwort: Ich vermute, dass Sie mehr in Betracht ziehen, "untestable" als wirklich zu sein. Untestable in der strengen "eine Einheit zu einer Zeit" Unit Testing ohne gleichzeitige Tests der Abhängigkeiten? Sicher. Aber es sollte leicht mit langsameren und seltener laufenden Integrationstests möglich sein.

Sie erwähnen den Zugriff auf die Datenbank und das Schreiben von Nachrichten in Warteschlangen.Wie bereits erwähnt, können Sie diese während des Komponententests in Datenbanken und Scheinwarteschlangen einspeisen und das tatsächliche konkrete Verhalten in Integrationstests testen. Persönlich sehe ich nichts falsch daran, konkrete Implementierungen direkt als Unit-Tests zu testen, zumindest wenn sie nicht remote (oder Legacy) sind. Sicher laufen sie ein bisschen langsamer, aber hey, zumindest werden sie von automatisierten Tests bedeckt.

Würden Sie ein System in Produktion bringen, in dem Nachrichten in Warteschlangen geschrieben werden und nicht wirklich getestet wurde, dass die Nachrichten in die tatsächliche physische/logische Warteschlange geschrieben werden? Ich würde nicht.

Verwandte Themen