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:
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.
Entwurfsklassen mit Abhängigkeitsinjektion. Grund: Sie können nicht verspotten oder testen, was nicht gesehen werden kann.
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.
Schreiben Sie keine statischen Methoden oder Klassen. Grund: Statische Methoden sind schwer oder unmöglich zu isolieren und Rhino Mocks kann sie nicht verspotten.
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.
Isolieren Sie externe Abhängigkeiten. Grund: Ungelöste externe Abhängigkeiten können nicht getestet werden.
Markieren Sie die Methoden, die Sie verspotten möchten, als virtuell. Grund: Rhino Mocks kann nicht virtuelle Methoden nicht verspotten.
quelle
Antworten:
Auf jeden Fall eine gute Liste. Hier ein paar Gedanken dazu:
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.
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.
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.
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.
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.
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).
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.)
quelle
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:
quelle
Kennen Sie den Unterschied zwischen Fälschungen, Verspottungen und Stummeln und wann Sie sie verwenden sollten.
Vermeiden Sie es, Interaktionen mithilfe von Mocks zu spezifizieren. Dies macht Tests spröde .
quelle
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
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.
quelle
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.
quelle
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.
quelle