Ich wollte mir beibringen, den TDD-Ansatz zu verwenden, und ich hatte ein Projekt, an dem ich schon eine Weile arbeiten wollte. Es war kein großes Projekt, also dachte ich, es wäre ein guter Kandidat für TDD. Ich habe jedoch das Gefühl, dass etwas schief gelaufen ist. Lassen Sie mich ein Beispiel geben:
Auf hoher Ebene ist mein Projekt ein Add-In für Microsoft OneNote, mit dem ich Projekte einfacher nachverfolgen und verwalten kann. Jetzt wollte ich auch die Geschäftslogik dafür so weit wie möglich von OneNote entkoppelt halten, falls ich beschloss, eines Tages meinen eigenen benutzerdefinierten Speicher und ein Back-End zu erstellen.
Zuerst begann ich mit einem Akzeptanztest für einfache Wörter, um zu skizzieren, was meine erste Funktion tun sollte. Es sieht ungefähr so aus (der Kürze halber dumm stellen):
- Benutzer klickt auf Projekt erstellen
- Benutzer gibt den Titel des Projekts ein
- Stellen Sie sicher, dass das Projekt korrekt erstellt wurde
Überspringe die UI-Sachen und mache eine Zwischenplanung. Ich komme zu meinem ersten Unit-Test:
[TestMethod]
public void CreateProject_BasicParameters_ProjectIsValid()
{
var testController = new Controller();
Project newProject = testController(A.Dummy<String>());
Assert.IsNotNull(newProject);
}
So weit, ist es gut. Rot, Grün, Refactor, etc. Okay, jetzt muss es wirklich etwas sparen. Hier ein paar Schritte rauszuschneiden, macht mich fertig.
[TestMethod]
public void CreateProject_BasicParameters_ProjectMatchesExpected()
{
var fakeDataStore = A.Fake<IDataStore>();
var testController = new Controller(fakeDataStore);
String expectedTitle = fixture.Create<String>("Title");
Project newProject = testController(expectedTitle);
Assert.AreEqual(expectedTitle, newProject.Title);
}
Ich fühle mich zu diesem Zeitpunkt immer noch gut. Ich habe noch keinen konkreten Datenspeicher, aber ich habe die Benutzeroberfläche so erstellt, wie ich es mir vorgestellt habe.
Ich werde hier ein paar Schritte überspringen, weil dieser Beitrag lang genug wird, aber ich habe ähnliche Prozesse befolgt und komme schließlich zu diesem Test für meinen Datenspeicher:
[TestMethod]
public void SaveNewProject_BasicParameters_RequestsNewPage()
{
/* snip init code */
testDataStore.SaveNewProject(A.Dummy<IProject>());
A.CallTo(() => oneNoteInterop.SavePage()).MustHaveHappened();
}
Dies war gut, bis ich versuchte, es umzusetzen:
public String SaveNewProject(IProject project)
{
Page projectPage = oneNoteInterop.CreatePage(...);
}
Und da ist das Problem, wo das "..." ist. Ich erkenne jetzt an DIESEM Punkt, dass CreatePage eine Abschnitts-ID erfordert. Ich habe das damals nicht gemerkt, als ich auf Controllerebene nachgedacht habe, weil ich mich nur mit dem Testen der für den Controller relevanten Bits befasst habe. Den ganzen Weg hier unten ist mir jedoch klar, dass ich den Benutzer nach einem Speicherort für das Projekt fragen muss. Jetzt muss ich dem Datenspeicher eine Standort-ID hinzufügen, dann eine zum Projekt hinzufügen, dann eine zum Controller hinzufügen und zu ALLEN Tests hinzufügen, die bereits für all diese Dinge geschrieben wurden. Es ist sehr schnell langweilig geworden und ich kann nicht anders, als das Gefühl zu haben, ich hätte es schneller bemerkt, wenn ich das Design im Voraus entworfen hätte, anstatt es während des TDD-Prozesses entwerfen zu lassen.
Kann mir bitte jemand erklären, ob ich etwas falsch gemacht habe? Gibt es überhaupt eine Möglichkeit, diese Art des Refactorings zu vermeiden? Oder ist das üblich? Wenn es üblich ist, gibt es Möglichkeiten, es schmerzloser zu machen?
Vielen Dank an alle!
quelle
Antworten:
Auch wenn TDD (zu Recht) als eine Möglichkeit angepriesen wird, Ihre Software zu entwerfen und zu erweitern, ist es dennoch eine gute Idee, im Voraus über Design und Architektur nachzudenken. IMO, "das Design im Voraus skizzieren" ist faires Spiel. Häufig liegt dies jedoch auf einer höheren Ebene als die Entwurfsentscheidungen, zu denen Sie durch TDD geführt werden.
Es stimmt auch, dass Sie bei Änderungen in der Regel Tests aktualisieren müssen. Es gibt keine Möglichkeit, dies vollständig zu beseitigen, aber Sie können einige Maßnahmen ergreifen, um Ihre Tests weniger brüchig zu machen und die Schmerzen zu minimieren.
Halten Sie Implementierungsdetails so weit wie möglich aus Ihren Tests heraus. Dies bedeutet, nur mit öffentlichen Methoden zu testen und wenn möglich zustandsbasiert statt interaktionsbasiert zu überprüfen . Mit anderen Worten, wenn Sie das Ergebnis von etwas testen und nicht die Schritte , um dorthin zu gelangen, sollten Ihre Tests weniger anfällig sein.
Minimieren Sie die Duplizierung in Ihrem Testcode, genau wie Sie es im Produktionscode tun würden. Dieser Beitrag ist eine gute Referenz. In Ihrem Beispiel scheint es schmerzhaft zu sein, die
ID
Eigenschaft zu Ihrem Konstruktor hinzuzufügen , da Sie den Konstruktor in mehreren Tests direkt aufgerufen haben. Versuchen Sie stattdessen, die Erstellung des Objekts in eine Methode zu extrahieren oder sie für jeden Test in einer Testinitialisierungsmethode einmal zu initialisieren.quelle
Vielleicht, vielleicht nicht
Einerseits hat TDD einwandfrei funktioniert und Ihnen automatisierte Tests während des Aufbaus der Funktionalität ermöglicht, die sich sofort lösten, wenn Sie die Benutzeroberfläche ändern mussten.
Wenn Sie dagegen mit der Funktion auf hoher Ebene (SaveProject) anstelle einer Funktion auf niedrigerer Ebene (CreateProject) begonnen hätten, wären Sie möglicherweise früher auf fehlende Parameter gestoßen.
Andererseits hättest du es vielleicht nicht. Es ist ein unwiederholbares Experiment.
Aber wenn Sie eine Lektion für das nächste Mal suchen: Beginnen Sie oben. Und denken Sie an das Design, so oft Sie möchten.
quelle
https://frontendmasters.com/courses/angularjs-and-code-testability/ Von ungefähr 2:22:00 bis zum Ende (ungefähr 1 Stunde). Es tut mir leid, dass das Video nicht kostenlos ist, aber ich habe kein kostenloses gefunden, das es so gut erklärt.
Eine der besten Präsentationen zum Schreiben von testbarem Code befindet sich in dieser Lektion. Es ist eine AngularJS-Klasse, aber der Testteil handelt von Java-Code, vor allem, weil das, wovon er spricht, nichts mit der Sprache zu tun hat, und alles, was mit dem Schreiben von gut testbarem Code zu tun hat.
Die Magie besteht darin, testbaren Code zu schreiben, anstatt Code-Tests zu schreiben. Es geht nicht darum, Code zu schreiben, der vorgibt, ein Benutzer zu sein.
Er verbringt auch einige Zeit damit, die Spezifikation in Form von Testbehauptungen zu schreiben.
quelle