Best Practices für die testgetriebene Entwicklung mit C # und RhinoMocks [geschlossen]

86

Um meinem Team beim Schreiben von testbarem Code zu helfen, habe ich diese einfache Liste mit Best Practices erstellt, um unsere C # -Codebasis testbarer zu machen. (Einige der Punkte beziehen sich auf Einschränkungen von Rhino Mocks, einem spöttischen Framework für C #, aber die Regeln gelten möglicherweise auch allgemeiner.) Hat jemand Best Practices, denen er folgt?

Befolgen Sie diese Regeln, um die Testbarkeit von Code zu maximieren:

  1. Schreiben Sie zuerst den Test und dann den Code. Grund: Dadurch wird sichergestellt, dass Sie testbaren Code schreiben und dass für jede Codezeile Tests geschrieben werden.

  2. Entwurfsklassen mit Abhängigkeitsinjektion. Grund: Sie können nicht verspotten oder testen, was nicht gesehen werden kann.

  3. Trennen Sie den UI-Code mithilfe von Model-View-Controller oder Model-View-Presenter von seinem Verhalten. Grund: Ermöglicht das Testen der Geschäftslogik, während die nicht zu testenden Teile (die Benutzeroberfläche) minimiert werden.

  4. Schreiben Sie keine statischen Methoden oder Klassen. Grund: Statische Methoden sind schwer oder unmöglich zu isolieren und Rhino Mocks kann sie nicht verspotten.

  5. Programmieren Sie Schnittstellen, keine Klassen. Grund: Die Verwendung von Schnittstellen verdeutlicht die Beziehungen zwischen Objekten. Eine Schnittstelle sollte einen Dienst definieren, den ein Objekt aus seiner Umgebung benötigt. Außerdem können Schnittstellen mit Rhino Mocks und anderen Verspottungs-Frameworks leicht verspottet werden.

  6. Isolieren Sie externe Abhängigkeiten. Grund: Ungelöste externe Abhängigkeiten können nicht getestet werden.

  7. Markieren Sie die Methoden, die Sie verspotten möchten, als virtuell. Grund: Rhino Mocks kann nicht virtuelle Methoden nicht verspotten.

Kevin Albrecht
quelle
Dies ist eine nützliche Liste. Wir verwenden derzeit NUnit und Rhino.Mocks, und es ist gut, diese Kriterien für Teammitglieder zu formulieren, die mit dieser Seite des Komponententests weniger vertraut sind.
Chris Ballard

Antworten:

58

Auf jeden Fall eine gute Liste. Hier ein paar Gedanken dazu:

Schreiben Sie zuerst den Test und dann den Code.

Ich stimme auf hohem Niveau zu. Aber ich würde genauer sagen: "Schreiben Sie zuerst einen Test, dann schreiben Sie gerade genug Code, um den Test zu bestehen, und wiederholen Sie den Vorgang." Andernfalls hätte ich Angst, dass meine Komponententests eher wie Integrations- oder Abnahmetests aussehen würden.

Entwurfsklassen mit Abhängigkeitsinjektion.

Einverstanden. Wenn ein Objekt seine eigenen Abhängigkeiten erstellt, haben Sie keine Kontrolle über diese. Durch Inversion der Steuerung / Abhängigkeitsinjektion erhalten Sie diese Steuerung, sodass Sie das zu testende Objekt mit Mocks / Stubs / etc. Isolieren können. So testen Sie Objekte isoliert.

Trennen Sie den UI-Code mithilfe von Model-View-Controller oder Model-View-Presenter von seinem Verhalten.

Einverstanden. Beachten Sie, dass sogar der Präsentator / Controller mit DI / IoC getestet werden kann, indem Sie ihm eine stubbierte / verspottete Ansicht und ein Modell geben. Weitere Informationen hierzu finden Sie in Presenter First TDD.

Schreiben Sie keine statischen Methoden oder Klassen.

Ich bin mir nicht sicher, ob ich damit einverstanden bin. Es ist möglich, eine statische Methode / Klasse ohne Verwendung von Mocks zu testen. Vielleicht ist dies eine der Rhino Mock-spezifischen Regeln, die Sie erwähnt haben.

Programmieren Sie Schnittstellen, keine Klassen.

Ich stimme zu, aber aus einem etwas anderen Grund. Schnittstellen bieten dem Softwareentwickler ein hohes Maß an Flexibilität - über die reine Unterstützung verschiedener Mock-Object-Frameworks hinaus. Beispielsweise ist es ohne Schnittstellen nicht möglich, DI ordnungsgemäß zu unterstützen.

Isolieren Sie externe Abhängigkeiten.

Einverstanden. Verstecken Sie externe Abhängigkeiten hinter Ihrer eigenen Fassade oder Ihrem Adapter (je nach Bedarf) mit einer Schnittstelle. Auf diese Weise können Sie Ihre Software von der externen Abhängigkeit isolieren, sei es ein Webdienst, eine Warteschlange, eine Datenbank oder etwas anderes. Dies ist besonders wichtig, wenn Ihr Team die Abhängigkeit nicht kontrolliert (auch bekannt als extern).

Markieren Sie die Methoden, die Sie verspotten möchten, als virtuell.

Das ist eine Einschränkung von Rhino Mocks. In einer Umgebung, in der handcodierte Stubs einem Scheinobjekt-Framework vorgezogen werden, wäre dies nicht erforderlich.

Und ein paar neue Punkte zu beachten:

Verwenden Sie kreative Designmuster. Dies hilft bei DI, ermöglicht es Ihnen jedoch auch, diesen Code zu isolieren und unabhängig von anderer Logik zu testen.

Schreiben Sie Tests mit der Arrange / Act / Assert-Technik von Bill Wake . Diese Technik macht sehr deutlich, welche Konfiguration erforderlich ist, was tatsächlich getestet wird und was erwartet wird.

Haben Sie keine Angst, Ihre eigenen Mocks / Stubs zu rollen. Oft werden Sie feststellen, dass die Verwendung von Scheinobjekt-Frameworks Ihre Tests unglaublich schwer lesbar macht. Wenn Sie Ihre eigenen rollen, haben Sie die vollständige Kontrolle über Ihre Mocks / Stubs und können Ihre Tests lesbar halten. (Siehe vorherigen Punkt.)

Vermeiden Sie die Versuchung, die Duplizierung Ihrer Komponententests in abstrakte Basisklassen oder Setup- / Teardown-Methoden umzugestalten. Dadurch wird der Konfigurations- / Bereinigungscode vor dem Entwickler ausgeblendet, der versucht, den Komponententest durchzuführen. In diesem Fall ist die Klarheit jedes einzelnen Tests wichtiger als das Umgestalten von Duplikaten.

Kontinuierliche Integration implementieren. Checken Sie Ihren Code an jedem "grünen Balken" ein. Erstellen Sie Ihre Software und führen Sie bei jedem Check-in Ihre gesamte Suite von Unit-Tests durch. (Sicher, dies ist per se keine Codierungspraxis; aber es ist ein unglaubliches Werkzeug, um Ihre Software sauber und vollständig integriert zu halten.)

aridlehoover
quelle
3
Ich finde normalerweise, dass wenn ein Test schwer zu lesen ist, es nicht die Schuld des Frameworks ist, sondern des Codes, den es testet. Wenn die Einrichtung des SUT kompliziert ist, sollte es möglicherweise in weitere Konzepte unterteilt werden.
Steve Freeman
10

Wenn Sie mit .Net 3.5 arbeiten, sollten Sie sich die Moq- Verspottungsbibliothek ansehen. Sie verwendet Ausdrucksbäume und Lambdas, um nicht intuitive Datensatz-Antwort-Redewendungen der meisten anderen Verspottungsbibliotheken zu entfernen.

Sehen Sie sich diesen Schnellstart an, um zu sehen, wie viel intuitiver Ihre Testfälle werden. Hier ein einfaches Beispiel:

// ShouldExpectMethodCallWithVariable
int value = 5;
var mock = new Mock<IFoo>();

mock.Expect(x => x.Duplicate(value)).Returns(() => value * 2);

Assert.AreEqual(value * 2, mock.Object.Duplicate(value));
Zadam
quelle
5
Ich denke, die neue Version von Rhino Mocks funktioniert auch so
George Mauer
3

Dies ist ein sehr hilfreicher Beitrag!

Ich würde hinzufügen, dass es immer wichtig ist, den Kontext und das zu testende System (SUT) zu verstehen. Das Befolgen von TDD-Prinzipien auf den Buchstaben ist viel einfacher, wenn Sie neuen Code in einer Umgebung schreiben, in der vorhandener Code denselben Prinzipien folgt. Wenn Sie jedoch neuen Code in einer Legacy-Umgebung ohne TDD schreiben, stellen Sie fest, dass Ihre TDD-Bemühungen schnell weit über Ihre Schätzungen und Erwartungen hinausgehen können.

Für einige von Ihnen, die in einer rein akademischen Welt leben, sind Zeitpläne und Bereitstellung möglicherweise nicht wichtig, aber in einer Umgebung, in der Software Geld ist, ist es entscheidend, Ihre TDD-Bemühungen effektiv zu nutzen.

TDD unterliegt in hohem Maße dem Gesetz zur Verringerung der Grenzrendite . Kurz gesagt, Ihre Bemühungen um TDD werden immer wertvoller, bis Sie einen Punkt maximaler Rendite erreicht haben. Danach hat die in TDD investierte Zeit immer weniger Wert.

Ich neige dazu zu glauben, dass der primäre Wert von TDD sowohl in der Grenze (Blackbox) als auch in gelegentlichen Whitebox-Tests von geschäftskritischen Bereichen des Systems liegt.


quelle
2

Der wahre Grund für die Programmierung gegen Schnittstellen besteht nicht darin, Rhino das Leben zu erleichtern, sondern die Beziehungen zwischen Objekten im Code zu klären. Eine Schnittstelle sollte einen Dienst definieren, den ein Objekt aus seiner Umgebung benötigt. Eine Klasse bietet eine bestimmte Implementierung dieses Dienstes. Lesen Sie Rebecca Wirfs-Brocks Buch "Object Design" über Rollen, Verantwortlichkeiten und Mitarbeiter.

Steve Freeman
quelle
Einverstanden ... Ich werde meine Frage aktualisieren, um dies widerzuspiegeln.
Kevin Albrecht
1

Gute Liste. Eines der Dinge, die Sie möglicherweise festlegen möchten - und ich kann Ihnen nicht viele Ratschläge geben, da ich gerade erst anfange, darüber nachzudenken -, ist, wann sich eine Klasse in einer anderen Bibliothek, einem anderen Namespace oder verschachtelten Namespaces befinden sollte. Vielleicht möchten Sie sogar vorher eine Liste von Bibliotheken und Namespaces erstellen und das Team beauftragen, sich zu treffen und zu entscheiden, zwei zusammenzuführen / eine neue hinzuzufügen.

Oh, ich habe gerade an etwas gedacht, das ich mache, das du vielleicht auch willst. Ich habe im Allgemeinen eine Unit-Test-Bibliothek mit einem Testgerät pro Klassenrichtlinie, in der jeder Test in einen entsprechenden Namespace verschoben wird. Ich neige auch dazu, eine andere Bibliothek von Tests (Integrationstests?) Zu haben, die eher BDD-artig ist . Auf diese Weise kann ich Tests schreiben, um festzulegen, was die Methode und die Anwendung insgesamt tun sollen.

George Mauer
quelle
Ich mache auch einen ähnlichen Testabschnitt im BDD-Stil (zusätzlich zum Unit-Test-Code) in einem persönlichen Projekt.
Kevin Albrecht
0

Hier ist eine andere, an die ich gedacht habe, die ich gerne mache.

Wenn Sie vorhaben, Tests über die Unit-Test-Benutzeroberfläche im Gegensatz zu TestDriven.Net oder NAnt auszuführen, ist es für mich einfacher, den Unit-Test-Projekttyp auf Konsolenanwendung anstatt auf Bibliothek festzulegen. Auf diese Weise können Sie Tests manuell ausführen und im Debug-Modus durchlaufen (was das oben genannte TestDriven.Net tatsächlich für Sie tun kann).

Außerdem möchte ich immer ein Playground-Projekt zum Testen von Code und Ideen haben, mit denen ich nicht vertraut bin. Dies sollte nicht in die Quellcodeverwaltung eingecheckt werden. Noch besser ist, dass es sich nur in einem separaten Quellcodeverwaltungs-Repository auf dem Computer des Entwicklers befindet.

George Mauer
quelle