TDD and BDD - Pros and Сons - Redwerk

In den letzten 12 Jahren hat unser Unternehmen Dutzende von großen und kleinen Projekten erfolgreich realisiert. In dieser Zeit hat sich der Entwicklungsprozess erheblich revolutioniert. Vor fünf Jahren sah der Arbeitsablauf so aus: Wir schrieben zuerst den Code und erstellten dann, wenn wir genug Zeit hatten, eine Reihe von Unit-Tests für den bestehenden Code. Die Testabdeckung für den Code lag damals bei weniger als 50 %. Wie wir jetzt sehen können, hatte ein solcher Ansatz eine Reihe von großen Mängeln. Früher konnten die Entwickler die Inkonsistenz der Logik oft nicht erkennen, und fehlerhafter Code wurde an den Testserver geschickt. Es ist erwähnenswert, dass dieser minderwertige Code nie in die Produktion gelangte, da das Fehlen computergestützter Tests durch die qualitativ hochwertige Arbeit der QA-Abteilung kompensiert wurde. Die Codeüberprüfung war ebenfalls sehr hilfreich, da sie nur von erfahrenen Entwicklern mit jahrzehntelanger Erfahrung in diesem Bereich durchgeführt wurde. Die Kunden waren mit dem Endprodukt, das sie in der Produktion sahen, immer zufrieden, aber die Tickets entsprachen oft nicht den Erwartungen der QS-Abteilung und wurden an die Entwickler zurückgeschickt. In der Zwischenzeit trat eine neue Code-Anforderung in Kraft. Sie besagt, dass ein hoher Prozentsatz des Codes durch Unit-Tests abgedeckt werden muss. Vor diesem Hintergrund hat unser Unternehmen vor einigen Jahren damit begonnen, eine Reihe von computergestützten Testtechniken und Dienstprogrammen wie Selenium zu implementieren. Außerdem verwenden wir für fast alle Projekte Tools zur kontinuierlichen Integration wie Jenkins. Wenn der übermittelte Code die Unit-Tests nicht bestanden hat, wird er nicht auf dem Testserver bereitgestellt, und alle Entwickler erhalten einen entsprechenden Bericht per E-Mail. Schauen wir uns also die verschiedenen Methoden des computergestützten Testens, ihre Vor- und Nachteile genauer an.

Unit testing and TDD

Unit-Tests und TDD Pro & Kontra

Unit-Tests sind eine Methode zum Testen separater Codeblöcke (Klassen, Komponenten), die auf der Logik ihrer Funktionsweise basieren. In der Realität werden Unit-Tests automatisch ausgeführt und stellen einen kleinen Code-Block dar, um die Genauigkeit der erwarteten Ausgabe einer einzelnen oder einer Reihe von Komponenten zu testen. Diese Komponenten werden getrennt von ihren Abhängigkeiten in einem Isolationstest getestet: DB, Speicher, Ablagesysteme, Netzwerke, usw. Test Driven Development (TDD) ist eine Entwicklungsmethodik, die auf dem Schreiben kleiner computergestützter Tests für Code (Unit-Tests) basiert. Das Ergebnis dieser Methodik ist ein vollständiger Satz von Unit-Tests, die jederzeit ausgeführt werden können, um zu überprüfen, ob der Anwendungscode korrekt funktioniert. TDD wird fast durchgängig von Unternehmen eingesetzt, die agile Entwicklungsmethoden verwenden. Beachten Sie, dass wir bei TDD zuerst den Test schreiben (und nicht die Umsetzung in Code!). Nach dem ersten Start erfüllt dieser Test nicht alle technischen Anforderungen. Dann muss ein Entwickler die Mindestfunktionalität in den Code implementieren, um sicherzustellen, dass der Test erfolgreich durchgeführt werden kann. Danach finden Refactoring und Code-Verbesserungen statt.

Das kurze Motto der testgetriebenen Entwicklung kann als „Red-Green-Refactor“ bezeichnet werden:

  • „Rot”: Erstellen Sie einen Unit-Test und führen Sie ihn aus, um zu sehen, ob er fehlschlägt
  • „Grün”: Implementiere die Logik in einen Code, um den Test zu vervollständigen
  • „Refactor”: Verbesserung des Codes, um Duplikate zu vermeiden, Verbesserung der Architektur, damit der Test erfolgreich abgeschlossen werden kann

Zusammen mit Tests, die vor der eigentlichen Ausführung der Logik im Code geschrieben werden, gewährleistet TDD die höchste Qualität eines Produkts und hilft dem gesamten Entwicklungsteam, sich auf die Erstellung eines einfachen und verständlichen Codes zu konzentrieren. Da sich die Unit-Tests jedoch auf das innere Konzept des Anwendungscodes konzentrieren, wird es für externe Entwickler schwierig, das Konzept hinter der Anwendung zu verstehen. Darüber hinaus ergeben sich neue Schwierigkeiten bei der Bewertung des Konzepts und der Codeabdeckung sowie der Qualität der Unit-Tests vor den Integrationstests. Aus diesem Grund liegt die gesamte Verantwortung für Unit-Tests nicht bei der QS-Abteilung, sondern bei den Entwicklern, da Unit-Tests Low-Level-Codeblöcke behandeln und Kenntnisse über die Softwarearchitektur der Anwendung erfordern. Außerdem sollten Tester nicht mit Unit-Tests arbeiten, da die Iterationen zwischen der Erstellung eines Tests durch einen Entwickler und der Implementierung für sein erfolgreiches Bestehen zu groß sind.

TDD Nachteile

  • einige Entwickler betrachten Tests immer noch als reine Zeitverschwendung
  • die Notwendigkeit, zusätzlichen Code in Tests zu erstellen, erhöht den Zeitaufwand für die Entwicklung
  • Tests können leicht falsch implementiert werden, sie überprüfen die Arbeit bestimmter Klassen und ihrer Methoden, aber nicht das System im Allgemeinen

TDD-Profis

  • eine Reihe von Unit-Tests gewährleistet eine ständige Rückmeldung über die Funktionsweise jedes einzelnen Systemelements
  • Unit-Tests sind ein Teil des Projekts und können nicht veralten, im Gegensatz zu spezifischer Dokumentation, die früher oder später in Vergessenheit gerät
  • TDD erfordert ein klares Verständnis der Funktionslogik des Codes, denn ohne ein klares Verständnis der erwarteten Ergebnisse kann man den Test nicht durchführen
  • die Qualität des Projektcodes steigt, da ein Entwickler den Code jederzeit refaktorisieren und die Genauigkeit seiner Leistung überprüfen kann
  • die Anzahl der Tickets, die von der QA an die Entwickler zurückgegeben werden, sinkt, da ein Teil der Fehler im Code durch Unit-Tests überprüft wird
  • eine Reihe von Tests fungiert als Sicherheitsnetz, da die Entwickler bei der Fehlerbehebung Tests erstellen, um zu prüfen, ob sich Probleme wiederholen oder nicht. Auf diese Weise sinkt die Wahrscheinlichkeit des Auftretens ähnlicher Bugs

Schließlich können wir nicht umhin, zwei zentrale praktische Methoden zu erwähnen, die im Code von Integrations-Unit-Tests angewendet werden – Mocking und Stubbing. Wenn Sie diese Wörter in einem Wörterbuch nachschlagen, werden Sie feststellen, dass das Substantiv „mock“ „zur Nachahmung gemacht“ bedeutet. Mocking wird meist in Unit-Tests verwendet, da das Objekt oft Abhängigkeiten in Form von anderen komplexen Objekten hat. Um das Verhalten eines getesteten Objekts zu isolieren, können Sie seine Abhängigkeiten durch Mocks ersetzen, die das Verhalten der echten Abhängigkeiten simulieren. Dies ist eine nützliche Technik, da es unpraktisch ist, den größten Teil der realen Objekte in den Unit-Test einzubeziehen. Kurz gesagt, ist Mocking ein Prozess der Erstellung von Objekten, die das Verhalten von realen Objektabhängigkeiten simulieren. Manchmal kann man Mocking als das Gegenteil von Stubbing betrachten. Stub ist ein Stopper. Oder anders ausgedrückt, ein „minimales“ oder leeres simuliertes Objekt, das oft keine Logik oder kein Verhalten hat. Das bedeutet, dass der Stub erstellt wird, um den Test erfolgreich durchzuführen. Auf diese Weise können Entwickler überprüfen, ob die getesteten Objekte korrekt mit dem Mock interagieren oder es verwenden.

Schauen wir uns ein Beispiel an:

Oft müssen wir in Unit-Tests eine Fake-DB erstellen, um ein Objekt zu testen, das DAO-Abhängigkeiten hat. Indem wir einen Stub erstellen, können wir leicht eine DB imitieren, da wir eine Datenstruktur zum Speichern von Daten erstellen. Jetzt können wir ganz einfach DAO in Form einer Abhängigkeit zu einem getesteten Objekt des Dienstes erstellen, das Einträge in einem gefälschten In-Memory-Datenbank-Stub speichern und entfernen wird. Damit können wir einen Test erfolgreich durchführen. Zuvor können wir jedoch nicht das allgemeine Verhalten eines getesteten Objekts testen, falls nur 3 Einträge mit einem bestimmten Satz von Feldwerten in der DB vorhanden sind. Für diese Zwecke reicht ein normaler Stub nicht aus, also müssen wir einen Mock erstellen und ihm spezifische Daten hinzufügen. Danach werden wir die Assertions in einem Unit-Test spezifizieren, um das Verhalten des Dienstes in einem Testfall mit bestimmten Datensammlungen zu überprüfen. Wir sollten auch darauf hinweisen, dass die Tatsache, dass alle Unit-Tests erfolgreich durchlaufen werden, nicht bedeutet, dass die Anwendung im Allgemeinen gut funktioniert. Um die allgemeine Funktionsweise der Anwendung zu überprüfen, benötigen Sie Tests auf einer höheren Ebene (oder Integrationstests). Funktionale (oder integrative) Tests betrachten die Logik des Projekts als einen einzigen funktionalen Thread. Sie stellen eine umfassende Interaktion von internen und externen Objekten (Komponenten) dar, die darauf abzielen, das erwartete Verhalten einer getesteten Anwendung zu erreichen. Funktionstests sind High-Level-Tests, und wenn der Code sie erfolgreich durchläuft, bedeutet dies, dass eine Anwendung gut funktioniert. Ein Fehlschlag wiederum bedeutet, dass der Code nicht die behauptete Funktionalität bietet.
Behavior Driven Development advantages and disadvantages - Redwerk

Verhaltensgetriebene Entwicklung Pro & Contra

Behavior-Driven Development (BDD) basiert auf TDD, aber TDD konzentriert sich auf die internen Prozesse der Software und die Präzision der Code-Performance (Unit-Tests), während BDD die Anforderungen und den Geschäftswert der Software an die Spitze der Software-Prioritäten stellt (Akzeptanztests). Akzeptanztests werden häufig anhand der User Stories und Akzeptanzkriterien modelliert. Diese Tests werden normalerweise in einfachen Worten beschrieben, damit Personen außerhalb der IT-Branche (wie Aktionäre, Geschäftsanalytiker, QA-Ingenieure und Projektmanager) sie besser verstehen. BDD sorgt dafür, dass im Prozess der Produktentwicklung überhaupt erst Tests erstellt werden. Diese ersten Tests müssen die erwartete Funktionalität eines Produkts und das Softwareverhalten beschreiben. Anschließend wird die Produktfunktionalität auf der Grundlage dieser Tests implementiert. Da dies sehr praktisch ist, verwenden wir BDD für Akzeptanztests. Wir können also davon ausgehen, dass BDD und TDD sich gegenseitig ergänzen, da sie unterschiedliche Ansätze zur Lösung ähnlicher Probleme darstellen. Da das Entwicklungsmanagement über einen Test erfolgt, geht jede Komponente im Prozess „von rot zu grün“, was bedeutet, dass sie zunächst fehlschlägt (keine Funktionalität), dann aber erfolgreich ist (ihre Funktionalität entspricht einer Spezifikation). Dank der Tatsache, dass die User Stories bei der Erstellung von Tests in BDD an erster Stelle stehen, entspricht das Endergebnis den Erwartungen des Kunden, denn es ist besser, das Verhaltensmodell des Benutzers, seine Aktionen und die Funktionen, die er möglicherweise benötigt, vor Beginn der Entwicklung aufzuschreiben. Unser Team verwendet Python Cucumber und Gherkin, um unsere User Stories zu schreiben und auszuführen. Gherkin ist eine Business Readable, Domain Specific Language. Sie kann verwendet werden, um das Verhaltensmuster des Benutzers laut und deutlich zu beschreiben, indem es in viele verschiedene Skripte aufgeteilt wird. Jedes Skript stellt eine separate User Story dar. Jede Zeile in einem solchen Skript ist eine Anforderung an die Software, die auf die Funktion abgebildet wird, die in der Sprache Python ausgeführt wird. Dank dieses Integrationstyps können wir BDD nutzen, um funktionale Aspekte der Benutzererfahrung mit einer Browseranwendung zu überprüfen. Wir verwenden beispielsweise das Selenium Browser Automation Tool zusammen mit Python Bindings, um unseren Akzeptanztest für die Benutzeroberfläche von Gherkin auszuführen. Jede User Story wird in der neuesten Version einer Anwendung im Browser überprüft, und Python sendet Anweisungen an den Browser für die Emulation verschiedener Benutzeraktivitäten (z. B. Klicks), wobei das Ergebnis dieser Aktion anhand einer Reihe von Kriterien überprüft wird (Meldung über erfolgreiche Ausführung, Validierungsfehler usw.) Wie bereits erwähnt, erfordert BDD in erster Linie die Erstellung eines Skripts für Benutzeraktionen. Deshalb erstellen wir zunächst ein Skript, in dem wir Beispiele für Situationen für jede unserer Komponenten beschreiben. Die Erstellung der Software erfolgt als letztes.

Unser Entwicklungszyklus sieht folgendermaßen aus:

  1. Schreiben Sie ein Skript auf Gherkin
  2. Das Skript ausführen und feststellen, dass nichts richtig funktioniert

    2.1 Identifiziere Situationen, in denen das Skript funktionieren muss

    2.2 Beginne mit der Überprüfung dieser Situationen und stelle fest, dass nichts richtig funktioniert

    2.3 Identifiziere und implementiere die minimale Funktionalität, die notwendig ist, damit alle Beispiele den Test bestehen

  3. Führen Sie alles ein weiteres Mal aus. Falls neue Fehler auftreten, gehen Sie zurück zu Punkt 2.1
  4. Das Skript funktioniert! Beginnen Sie mit der Erstellung eines neuen Skripts, das den größten Teil der Anforderungen abdeckt

Dan Nort war der erste, der den BDD-Ansatz buchstabierte und behauptete, diese Methode sei dazu da, Probleme mit TDD zu beseitigen

BDD Nachteile:

  • erfordert ein tiefes Verständnis einer größeren Anzahl von Konzepten, was es nicht erlaubt, BDD einem Nachwuchsentwickler zu empfehlen, bevor er das TDD-Konzept vollständig verstanden hat
  • da es sich um ein Konzept handelt, bedeutet die Umwandlung in eine technische Praxis oder die Verknüpfung mit einer Reihe von Werkzeugen, dass es ruiniert wird

BDD-Profis

  • Business-Team und Entwickler demonstrieren echte Teamarbeit und lassen die richtigen Leute die richtigen Dinge zur richtigen Zeit besprechen
  • das Unternehmen muss die Priorität der Funktionalität rechtfertigen, da sie ihren tatsächlichen Wert zeigt
  • Entwicklerteams können sich dank eines besseren Verständnisses auf die vom Unternehmen priorisierten Funktionen konzentrieren
  • Entwickler werden seltener mit dem Geschäftsteam streiten, um einige Funktionen vor den anderen zu schreiben
  • Dank der Sprache, die eine gemeinsame Wissensbasis verwendet, arbeiten das Geschäftsteam und die Entwickler an einem Projekt und bleiben auf derselben Seite darüber
  • Sie hilft Ihnen, sich auf die Bedürfnisse des Benutzers und das erwartete Verhalten zu konzentrieren, anstatt sich sofort in alle Implementierungsdetails zu stürzen.
  • kann den Teams helfen, sich gezielt auf Details der Funktionalität zu konzentrieren und wichtige Dinge zu testen, anstatt einfach Tests für den gesamten Code zu erstellen
  • erfordert ein ständig wachsendes Verständnis der Produktanforderungen, was die Entwicklung von sich ständig ändernden Anwendungen erleichtert
  • bringt Menschen dazu, eng zusammenzuarbeiten, besonders wenn es um Entwickler und Mitglieder von Geschäftsteams geht, was eine Normalisierung des Niveaus des Verständnisses von Problembereichen und der Implementierungsgenauigkeit ermöglicht
  • man kann sich nicht in den Stapeln veralteter Dokumentation und veralteten Codes verlieren
  • Teams haben mehr Vertrauen in ihre Arbeit und neigen dazu, ihre Entwicklung vorauszusehen

Schlussfolgerung

Abschließend möchten wir nur noch hinzufügen, dass es am wichtigsten ist, zu verstehen, warum und wie man verschiedene Methoden und Werkzeuge des computergestützten Testens anwendet, ohne Tests umsonst zu schreiben. Die ordnungsgemäße Umsetzung computergestützter Test- und Entwicklungsmethoden auf der Grundlage laufender Tests hat einen erheblichen Einfluss auf den Entwicklungsprozess im Allgemeinen und die Qualität des Endprodukts.