Reflexionen über TDD. Warum diese Methode nicht allgemein anerkannt ist

Hallo Habr!

Lange und fast erfolglos haben wir nach einem klugen Kopf gesucht, der Herrn Kent Beck auf dem Markt verdrängen will - das heißt, wir suchen jemanden, der bereit ist, ein Buch über TDD für uns zu schreiben. Mit realen Beispielen eine Geschichte über Ihre eigenen Zapfen und Erfolge. Es gibt nur sehr wenige Bücher zu diesem Thema, und Sie werden die Klassiker nicht bestreiten ... Vielleicht haben wir diesen Kopf deshalb noch nicht kennengelernt.

Aus diesem Grund haben wir uns entschlossen, uns nicht nur erneut daran zu erinnern, dass wir nach einer solchen Person suchen, sondern auch die Übersetzung eines eher kontroversen Artikels anzubieten, dessen Autor Doug Arcuri seine eigenen Gedanken darüber teilt, warum TDD nie zum Mainstream wurde. Lassen Sie uns diskutieren, ob er Recht hat und wenn nicht, warum.



Dies ist keine Einführung in die Entwicklung durch Testen. Hier werde ich meine eigenen Ideen zum Neustart dieser Disziplin vorstellen und über die praktischen Schwierigkeiten beim Testen von Einheiten sprechen.

Der legendäre Programmierer Kent Beck ist der Autor der TDD-Methodik (Development Through Testing) im modernen Sinne. Kent trug zusammen mit Erich Gamma zur Entwicklung von JUnit bei, einem weit verbreiteten Test-Framework.

In seinem XP Explained- Buch (zweite Ausgabe) beschreibt Kent, wie Prinzipien an der Schnittstelle von Werten und Praktiken gebildet werden . Wenn Sie eine Liste von Konzepten erstellen und diese durch eine Art Formel ersetzen, erhalten Sie eine Transformation.

[KISS, Quality, YAGNI, ...] + [Testing, Specs, ...] == [TDD, ...] 

Ich respektiere diese Arbeit zutiefst, die für Kent ein lebenslanges Werk ist - nicht nur für seine Meisterwerke in der Programmierung, sondern auch für die Tatsache, dass er unermüdlich die Essenz von Vertrauen , Mut , Verleihung , Einfachheit und Verletzlichkeit erforscht. Alle diese Eigenschaften waren für die Erfindung der Extreme Programming (XP) unverzichtbar.

TDD ist ein Prinzip und eine Disziplin , die in der XP-Community eingehalten wird. Diese Disziplin ist bereits 19 Jahre alt.

In diesem Artikel werde ich meine Meinung darüber teilen, wie TDD es geschafft hat, sich zu assimilieren. Dann werde ich interessante persönliche Beobachtungen teilen, die während meiner TDD-Sitzung erschienen sind. Abschließend werde ich versuchen zu erklären, warum der TDD nicht so hart geschossen hat, wie es schien. Lass uns gehen.

TDD, Forschung und Professionalität

In den letzten 19 Jahren war die Disziplin TDD in der Programmiergemeinschaft umstritten.
Die erste Frage, die ein professioneller Analyst Ihnen stellen würde, lautet: "Wie viel Prozent der Entwickler verwenden TDD heute?" Wenn Sie einen Freund von Robert Martin (Onkel Bob) und einen Freund von Kent Beck danach fragen würden, wäre die Antwort „100%“.

Nur Onkel Bob ist sich sicher, dass es unmöglich ist, sich als Profi zu betrachten, wenn Sie die Entwicklung nicht durch Tests üben .

Onkel Bob ist seit mehreren Jahren eng in diese Disziplin involviert, daher ist es selbstverständlich, in dieser Rezension auf ihn zu achten. Onkel Bob verteidigte TDD und erweiterte die Grenzen dieser Disziplin erheblich. Sie können sicher sein, dass ich Onkel Bob und seinem pragmatischen Dogmatismus größten Respekt entgegenbringe.

Allerdings stellt niemand die folgende Frage: "Üben heißt schließlich" bewusst verwenden "- aber es erlaubt nicht, den Prozentsatz zu beurteilen, oder?" Meiner subjektiven Meinung nach haben sich die meisten Programmierer selbst für eine symbolische Periode nicht mit TDD befasst.

Die Realität ist, dass wir diese Zahlen wirklich nicht kennen, da niemand diesen Prozentsatz aktiv untersucht hat. Alle spezifischen Daten beschränken sich auf eine kleine Auswahl von Unternehmen, die auf der WeDoTDD- Website gesammelt wurden. Hier finden Sie Statistiken über solche Unternehmen, Interviews mit denen, die ständig TDD praktizieren, aber diese Liste ist nicht groß. Darüber hinaus ist es unvollständig, da bereits eine einfache Suche andere große Organisationen aufdeckt, die an TDD beteiligt sind - aber möglicherweise nicht voll ausgelastet sind.

Wenn wir nicht wissen, wie viele Unternehmen TDD praktizieren, stellt sich die folgende Frage: "Wie effektiv ist TDD, gemessen an seinen messbaren Vorzügen"?
Sie werden wahrscheinlich erfreut sein, dass im Laufe der Jahre eine Reihe von Studien durchgeführt wurden, die die Wirksamkeit von TDD bestätigen. Darunter befinden sich definitiv maßgebliche Berichte von Microsoft , IBM , der University of North Carolina und der University of Helsinki .



Ein aussagekräftiges Diagramm aus einem Bericht der Universität Helsinki.

Bis zu einem gewissen Grad belegen diese Berichte, dass die Fehlerdichte um 40-60% reduziert werden kann, was mehr Arbeit erfordert. Laufzeit erhöht sich um 15-35%. Diese Zahlen werden bereits in Büchern und neuen industriellen Methoden, insbesondere in der DevOps-Community, nachverfolgt.

Wenn wir diese Fragen teilweise beantworten, wenden wir uns der letzten zu: „Worauf kann ich zählen, wenn ich anfange, TDD zu üben?“ Für die Antwort darauf formulierte ich meine persönlichen Beobachtungen von TDD. Gehen wir weiter zu ihnen.

1. TDD erfordert einen verbalisierenden Ansatz

Beim Üben von TDD stoßen wir auf das Phänomen der "Zielbezeichnung". Kurz gesagt, kurze Projekte wie die Vorbereitung fehlgeschlagener und erfolgreicher Tests sind eine ernsthafte intellektuelle Herausforderung für den Entwickler. Der Entwickler muss klar formulieren: "Ich glaube, dass dieser Test erfolgreich sein wird" und "Ich glaube, dass dieser Test fehlschlagen wird" oder "Ich bin nicht sicher, lassen Sie mich nachdenken, nachdem ich diesen Ansatz ausprobiert habe."

Die IDE ist für den Entwickler zu dieser Gummiente geworden, die darum bittet, aktiv mit ihr zu sprechen. In TDD-Unternehmen sollten Gespräche dieser Art zumindest zu einem kontinuierlichen Summen verschmelzen.

Denken Sie zuerst nach - und machen Sie dann Ihren nächsten Schritt (oder Ihre nächsten Schritte).

Eine solche Verstärkung spielt eine Schlüsselrolle in der Kommunikation: Sie ermöglicht Ihnen nicht nur, Ihren nächsten Schritt vorherzusagen, sondern Sie auch dazu anzuregen, den einfachsten Code zu schreiben, mit dem Sie den Komponententest bestehen können. Wenn der Entwickler schweigt, verliert er natürlich mit ziemlicher Sicherheit seinen Kurs, wonach er auf die Strecke zurückkehren muss.

2. TDD pumpt den Motorspeicher

Der Entwickler, der seine ersten TDD-Zyklen durchläuft, wird schnell müde - schließlich ist dieser Prozess unpraktisch und bleibt ständig stehen. Dies ist eine häufige Situation bei Aktivitäten, die eine Person gerade erst beginnt, aber noch nicht gemeistert hat. Der Entwickler greift auf Verknüpfungen zurück und versucht, diesen Zyklus zu optimieren, um seine Hand zu füllen und das Motorspeicher zu verbessern.
Das motorische Gedächtnis ist unverzichtbar, damit die Arbeit Spaß macht und wie am Schnürchen läuft. In TDD ist dies aufgrund der Wiederholung von Aktionen erforderlich.

Holen Sie sich den Spickzettel mit solchen Verknüpfungen. Holen Sie das Beste aus Ihren Tastaturkürzeln in Ihrer IDE heraus, um Schleifen effektiv zu machen. Dann suchen Sie weiter.

In nur wenigen Sitzungen beherrscht der Entwickler die Auswahl der Verknüpfungen perfekt, insbesondere reichen einige Sitzungen aus, um einen Prüfstand zusammenzustellen und auszuführen. Wenn Sie üben, neue Artefakte zu erstellen, Text hervorzuheben und durch die IDE zu navigieren, erscheint Ihnen dies alles völlig natürlich. Schließlich werden Sie ein echter Profi und beherrschen alle Refactoring-Techniken: insbesondere Extrahieren, Umbenennen, Generieren, Erhöhen, Neuformatieren und Abstieg.

3. TDD erfordert zumindest ein wenig Nachdenken über ihre Aktionen im Voraus

Wenn ein Entwickler darüber nachdenkt, eine TDD zu starten, muss er eine kurze mentale Karte der Aufgaben berücksichtigen, die gelöst werden müssen. Beim traditionellen Programmieransatz ist eine solche Karte nicht immer vorhanden, und die Aufgabe selbst kann "auf Makroebene" dargestellt werden oder einen Forschungscharakter haben. Vielleicht weiß der Entwickler nicht, wie er das Problem lösen soll, sondern stellt sich das Ziel nur grob vor. Unit-Tests werden zu diesem Ziel vernachlässigt.

Während Sie sich bei der Arbeit hinsetzen und mit einem weiteren „Sitzen“ enden, versuchen Sie auch, daraus ein Ritual zu machen. Denken Sie nach und listen Sie zuerst auf. Spiel damit. Liste mehr. Dann mach weiter, mach, denk nach. Feiern. Mehrmals wiederholen. Dann überlegen Sie noch einmal und hören Sie auf.

Sei unnachgiebig in Bezug auf die Arbeit. Verfolgen Sie, was bereits getan wurde - aktivieren Sie die Kontrollkästchen. Falten Sie niemals, bis mindestens eine vorhanden ist. Denken Sie nach!

Möglicherweise dauert der Wortlaut der Liste einige Zeit, die nicht in den Arbeitszyklus passt. Bevor Sie beginnen, müssen Sie jedoch eine Liste haben. Ohne sie wissen Sie nicht, wohin Sie sich bewegen. Nirgendwo ohne Karte.

 //   // "" ->   // "a" ->   // "aa" ->  // "racecar" ->  // "Racecar" ->  //   //    

Der Entwickler sollte die von Kent Beck beschriebenen Tests auflisten. Mit der Testliste können Sie das Problem in Form von Zyklen lösen, die reibungslos ineinander übergehen. Über der Liste der Tests müssen Sie ständig verarbeiten und aktualisieren, auch wenn dies nur wenige Sekunden vor den Tests geschieht. Wenn die Testliste fast vollständig abzüglich der letzten Stufe bestanden wurde, ist das Ergebnis „rot“ und der gesamte Test ist fehlgeschlagen.

4. TDD hängt von der Kommunikation mit Kollegen ab

Nach Abschluss der obigen Liste können einige Schritte blockiert werden, da sie nicht ganz klar beschreiben, was zu tun ist. Der Entwickler hat die Liste der Tests nicht verstanden. Das Gegenteil passiert auch - die Liste ist zu grob, in der es viele Annahmen über die Anforderungen gibt, die noch nicht formuliert wurden. Wenn Sie so etwas bekommen, hören Sie sofort auf.

Das Handeln ohne TDD kann zu übermäßig komplexen Implementierungen führen. Arbeiten im Stil von TDD, aber gedankenlos ohne Liste, ist nicht weniger gefährlich.

Wenn Sie feststellen, dass die Testliste Lücken enthält, stehen Sie auf und sagen Sie es laut.

In TDD muss der Entwickler verstehen, welches Produkt zu tun ist, wobei er sich von der Idee der notwendigen Anforderungen bei der Interpretation des Eigentümers leiten lässt - und nicht mehr. Wenn die Anforderung in diesem Zusammenhang unklar ist, beginnt die Liste der Tests auseinanderzufallen. Dieser Fehler muss diskutiert werden. Eine ruhige Diskussion hilft schnell, Vertrauen und Respekt aufzubauen. Außerdem werden auf diese Weise schnelle Rückkopplungsschleifen gebildet.

5. TDD erfordert eine iterative Architektur

Bereits in der ersten Ausgabe seines XP-Buches schlug Kent vor, dass Tests die treibende Kraft hinter der Architektur sein sollten. Im Laufe mehrerer Jahre sind jedoch Geschichten darüber aufgetaucht, wie Sprintteams bereits in wenigen Sprints über eine Wand stolpern.

Natürlich ist es irrational, eine auf Tests basierende Architektur aufzubauen. Onkel Bob selbst stimmte anderen Experten zu, dass es nicht gut sei. Eine umfangreichere Karte ist erforderlich, jedoch nicht zu weit von den Testlisten entfernt, die Sie „vor Ort“ entwickelt haben.

Viele Jahre später äußerte Kent diese These auch in seinem Buch TDD By Example . Wettbewerbsfähigkeit und Sicherheit sind zwei Hauptbereiche, in denen TDD nicht die treibende Kraft sein kann, und der Entwickler muss sie separat behandeln. Wir können sagen, dass Wettbewerbsfähigkeit eine andere Ebene des Systemdesigns ist. Die Wettbewerbsfähigkeit muss durch Iterationen entwickelt werden, die diesen Prozess mit TDD koordinieren. Dies gilt insbesondere heute, da sich einige Architekturen in Richtung eines reaktiven Paradigmas und reaktiver Erweiterungen entwickeln ( Reaktivität ist Wettbewerbsfähigkeit auf ihrem Höhepunkt).

Erstellen Sie eine größere Karte der gesamten Organisation. Helfen, die Dinge ein wenig in der Perspektive zu sehen. Stellen Sie sicher, dass Sie und das Team sich im selben Kurs bewegen.

Die wichtigste Idee ist jedoch die Organisation des gesamten Systems, und eine TDD-Organisation ist nicht vorgesehen. Tatsache ist, dass Unit-Tests eine einfache Sache sind. Die iterative Architektur und die TDD-Orchestrierung sind in der Praxis komplex und erfordern Vertrauen zwischen allen Teammitgliedern, Paarprogrammierung und solide Codeüberprüfungen. Es ist nicht ganz klar, wie dies erreicht werden soll, aber bald können Sie sehen, dass kurze Entwurfssitzungen im Einklang mit der Implementierung von Testlisten im Themenbereich durchgeführt werden sollten.

6. TDD zeigt die Fragilität von Unit-Tests und die entartete Implementierung

Unit-Tests haben eine unterhaltsame Funktion, und TDD verrät sie vollständig. Sie erlauben nicht, die Richtigkeit zu beweisen. E.V.Dijkstra arbeitete an diesem Problem und diskutierte, wie in unserem Fall mathematische Beweise möglich sind, die diese Lücke füllen würden.

Im folgenden Beispiel werden beispielsweise alle Tests gelöst, die sich auf ein hypothetisches unvollkommenes Palindrom beziehen, das von der Geschäftslogik vorgegeben wird. Ein Beispiel wird unter Verwendung der TDD-Methodik entwickelt.

 //    @Test fun `Given "", then it does not validate`() { "".validate().shouldBeFalse() } @Test fun `Given "a", then it does not validate`() { "a".validate().shouldBeFalse() } @Test fun `Given "aa", then it validates`() { "aa".validate().shouldBeTrue() } @Test fun `Given "abba", then it validates`() { "abba".validate().shouldBeTrue() } @Test fun `Given "racecar", then it validates`() { "racecar".validate().shouldBeTrue() } @Test fun `Given "Racecar", then it validates`() { "Racecar".validate().shouldBeTrue() } 

In der Tat gibt es Mängel bei diesen Tests. Unit-Tests sind selbst in den trivialsten Fällen fragil. Es gelingt ihnen nie, ihre Richtigkeit zu beweisen, denn wenn wir es versuchen würden, würde dies eine unglaubliche mentale Arbeit erfordern, und der dafür erforderliche Input wäre unmöglich vorstellbar.

 //   ,      fun String.validate() = if (isEmpty() || length == 1) false else toLowerCase() == toLowerCase().reversed() //   ,    fun String.validate() = length > 1 length > 1 

length > 1 kann als entartete Implementierung bezeichnet werden . Es reicht völlig aus, um die Aufgabe zu lösen, berichtet jedoch selbst nichts über das Problem, das wir zu lösen versuchen.

Die Frage ist - wann sollte ein Entwickler aufhören, Tests zu schreiben? Die Antwort scheint einfach zu sein: Wenn es aus Sicht der Geschäftslogik ausreicht und nicht nach dem Autor des Codes. Es kann unsere Designleidenschaft verletzen und Einfachheit kann Menschen verärgern . Diese Gefühle werden durch die Zufriedenheit beim Anblick unseres eigenen sauberen Codes und das Verständnis kompensiert, dass der Code anschließend sicher überarbeitet werden kann. Der gesamte Code wird sehr ordentlich sein.

Beachten Sie, dass bei aller Unzuverlässigkeit Unit-Tests erforderlich sind. Verstehe ihre Stärken und Schwächen. Wenn sich das vollständige Bild nicht ergibt, hilft diese Lücke möglicherweise beim Ausfüllen von Mutationstests .

TDD hat seine Vorteile, aber diese Methode kann uns vom Bau unnötiger Sandburgen ablenken. Ja, dies ist eine Einschränkung , aber dank dieser Einschränkung können Sie sich schneller, weiter und zuverlässiger bewegen. Vielleicht hatte Onkel Bob genau das im Sinn, als er beschrieb, was es aus seiner Sicht bedeutet , ein Profi zu sein .

Aber! Egal wie fragil uns die Unit-Tests erscheinen, sie sind ein absolutes Muss. Sie machen Angst zu Mut . Tests bieten ein schonendes Code-Refactoring. Darüber hinaus können sie als Leitfaden und Dokumentation für jeden neuen Entwickler dienen, der sofort auf dem richtigen Weg ist und zum Wohle des Projekts arbeitet - sofern dieses Projekt durch Unit-Tests gut abgedeckt ist.

7. TDD demonstriert die umgekehrte Schleife von Testanweisungen

Gehen Sie noch einen Schritt weiter. Um die folgenden zwei Phänomene zu verstehen, untersuchen wir seltsame wiederkehrende Ereignisse. Schauen wir uns zunächst FizzBuzz an. Hier ist unsere Liste von Tests.

 //    9  15. [OK] //  ,  3,  Fizz  . // ... 

Wir gingen ein paar Schritte vorwärts. Jetzt schlägt unser Test fehl.

 @Test fun `Given numbers, replace those divisible by 3 with "Fizz"`() { val machine = FizzBuzz() assertEquals(machine.print(), "?") } class FizzBuzz { fun print(): String { var output = "" for (i in 9..15) { output += if (i % 3 == 0) { "Fizz " } else "${i} " } return output.trim() } } Expected <Fizz 10 11 Fizz 13 14 Fizz>, actual <?>. 

Wenn wir die erwarteten Assertionsdaten in assertEquals , wird assertEquals das gewünschte Ergebnis erzielt und der Test durchgeführt.

Manchmal liefern fehlgeschlagene Tests das richtige Ergebnis, um den Test zu bestehen. Ich weiß nicht, wie ich solche Ereignisse nennen soll ... vielleicht Voodoo-Tests . Wie oft Sie dies zufällig sehen - hängt teilweise von Ihrer Faulheit und Etikette beim Testen ab, aber ich habe solche Dinge oft bemerkt, wenn eine Person versucht, eine Implementierung zu erhalten, die normal mit vorgefertigten und vorhersehbaren Datensätzen funktioniert.

8. TDD zeigt die Reihenfolge der Transformationen

TDD kann Sie fangen. Es kommt vor, dass der Entwickler in den von ihm vorgenommenen Transformationen verwirrt ist, mit denen er die gewünschte Implementierung erreicht. Irgendwann wird der Testcode zu einem Engpass, bei dem wir stehen bleiben.

Es bildet sich eine Sackgasse . Der Entwickler muss einen Schritt zurücktreten und deaktivieren und einige der Tests entfernen, um aus dieser Falle herauszukommen. Der Entwickler bleibt ungeschützt.

Onkel Bob würde wahrscheinlich im Laufe der Jahre seiner Karriere in solche Sackgassen geraten. Danach wurde ihm anscheinend klar, dass er, um den Test zu bestehen, die richtige Reihenfolge der Aktionen festlegen musste, um die Wahrscheinlichkeit einer Sackgasse zu minimieren. Außerdem musste er eine andere Bedingung erkennen. Je spezifischer die Tests werden, desto allgemeiner ist der Code .



Die Abfolge der Transformationen. Sie sollten immer nach der einfachsten Option streben (ganz oben in der Liste).

Dies ist die Übergangsprioritätsbedingung . Anscheinend besteht ein gewisses Risiko des Refactorings, das wir nach Bestehen des Tests erreichen können. Normalerweise ist es am besten, die am Anfang der Liste angezeigte Konvertierungsoption zu wählen (die einfachste). In diesem Fall bleibt die Wahrscheinlichkeit, in eine Sackgasse zu geraten, minimal.

Die Testanalyse von TPP oder Onkel Bob ist sozusagen eines der faszinierendsten, technologischsten und aufregendsten Phänomene, die derzeit beobachtet werden.

Verwenden Sie es, um Ihren Code so einfach wie möglich zu halten.
Drucken Sie die TPP-Liste aus und legen Sie sie auf Ihren Schreibtisch. Fragen Sie ihn, um nicht in Sackgassen zu geraten. Machen Sie es sich zur Regel: Die Bestellung sollte einfach sein.

Damit ist die Geschichte meiner primären Beobachtungen abgeschlossen. Im letzten Teil des Artikels möchte ich jedoch auf die Frage zurückkommen, die wir zu Beginn vergessen haben zu beantworten: „Wie viel Prozent der professionellen Programmierer verwenden heute TDD?“ Ich würde antworten: "Ich denke, es gibt nur wenige von ihnen." Ich möchte diese Frage unten untersuchen und versuchen zu erklären, warum.

Ist TDD in der Praxis verankert?

Leider gibt es keine. Subjektiv scheint der Prozentsatz seiner Unterstützer gering zu sein, und ich suche weiterhin nach Daten. Meine Erfahrung in der Rekrutierung, Teamführung und Selbstentwicklung (was mich fasziniert) ermöglicht es mir, die folgenden Beobachtungen zu machen.

Grund 1: Mangelnder Kontakt zur realen Testkultur

Ich kann davon ausgehen, dass die meisten Entwickler nicht die Möglichkeit hatten, in einer echten Testkultur zu lernen und zu arbeiten.

Testkultur ist eine Umgebung, in der Entwickler die Kunst des Testens bewusst üben und verbessern. Sie bilden ständig Kollegen aus, die noch nicht genug Erfahrung auf diesem Gebiet haben. In jedem Paar und jeder Poolanfrage wurde ein Feedback erstellt, das allen Teilnehmern hilft, Testfähigkeiten zu entwickeln. Darüber hinaus gibt es in der gesamten Hierarchie der Ingenieure ernsthafte Unterstützung und ein Gefühl des Ellbogens. Alle Manager verstehen die Essenz des Testens und glauben daran. Wenn die Fristen ablaufen, wird die Testdisziplin nicht verworfen, sondern weiterhin eingehalten.

Diejenigen, die das Glück hatten, sich in einer solchen Testkultur zu testen, wie zum Beispiel, hatte ich die Möglichkeit, solche Beobachtungen zu machen. Diese Erfahrung wird uns bei neuen Projekten nützlich sein.

2:

TDD, , xUnit Patterns Effective Unit Testing . , -, , , . .

. , . . , , , … .

3:

: , , . , , ; - , – .

4:

, , TDD . , .

, , : « , ». : , « » — .

– .



XP – , . – , . TDD.

, , . «» , , – , .



XP Explained. , , .

, - .

, – , . , , , .

, , , .

TDD « » , . TDD . TDD .

. TDD , . TDD — , , . , TDD , . , .

 @Test fun `Given software, when we build, then we expect tests`() { build(software) shoudHave tests } 

, TDD – , , . . , , , .

Source: https://habr.com/ru/post/de427853/


All Articles