Einführung in das Testen
Veröffentlicht: 2021-06-16Willkommen zu unserer neuen Blogserie über alles rund ums Testen . Hoffentlich geben Ihnen diese Blog-Posts eine allgemeine Vorstellung davon, wie wir sie hier bei Mediatoolkit schreiben und warum. Bevor wir uns mit Beispielen befassen, beginnen wir mit einigen grundlegenden Definitionen, Ideen und Vorteilen des Schreibens von Tests.
Welche Art von Tests gibt es?
Nicht alle Tests sind gleich. Es gibt verschiedene Arten von Tests für unterschiedliche Zwecke. Während sich dieser Beitrag hauptsächlich auf Unit-Tests konzentriert, sollten wir uns der Unterschiede bewusst sein und die Vorteile der einzelnen verstehen.
1. Manuelle Tests
Beim manuellen Testen handelt es sich um Tests, die manuell von einem Tester ausgeführt werden. Diese werden normalerweise von der Qualitätssicherung oder von den Entwicklern selbst durchgeführt. QA sollte als letzte Verteidigungslinie angesehen werden, nicht als Hauptanwendungstester (QA, die manuelles Testen als Hauptmethode für UI-Tests durchführt, ist eine gültige Ausnahme von diesem Fall).
Entwickler schreiben normalerweise manuelle Tests, wenn sie einige Dinge lokal ausführen und sehen möchten, wie sich das System verhält , ohne Code in die Staging-Phase oder, Gott schütze dich, die Produktion zu schieben. Das Ziel dieser Tests ist jedoch nicht die Robustheit des Codes. Die einzige Rolle dieser Tests besteht darin , Fehler zu finden und die Qualität Ihres Produkts sicherzustellen .
2. Integrationstests
Systeme bestehen aus mehreren Komponenten, oder sollten es zumindest sein. Integrationstests prüfen die öffentliche API dieser Komponenten. Nicht nur die REST-API, sondern jede öffentlich zugängliche API, wie z. B. Ihre Kafka-Themenkommunikation.
Wenn eine Komponente „sagt“, gibt sie eine Nachricht zu einem Thema in einem bestimmten Format aus, das ist ihre öffentliche API oder ihr Vertrag. Integrationstests prüfen, ob mehrere individuell entwickelte Komponenten kommunizieren können und ob ihre Verträge als Gruppe übereinstimmen.
Wenn Sie diese Komponenten nur einzeln testen, funktioniert ihr individuelles Verhalten möglicherweise korrekt, aber wenn Sie versuchen, sie in einer größeren Gruppe zu verbinden, stellen Sie fest, dass ihre Verträge unterschiedlich sind.
3. End-to-End (E2E)-Tests
End-to-End-Tests stellen den Benutzererfahrungsfluss des Endprodukts sicher . Die Komplexität heutiger Systeme lässt sich nur schwer mit Tests abdecken. Viele Systeme verlassen sich aufeinander und E2E-Tests stellen sicher, dass das Produkt das tut, was erwartet wird . Die QA validiert die Korrektheit Ihres Produkts, indem sie den Endbenutzerfluss durchläuft und prüft, ob sich alle Systeme wie erwartet verhalten.
4. Einheitentests
Komponententests sind das Rückgrat jeder zuverlässigen Software . Sie bilden die Grundlage für weitere Tests. Sie testen einzelne Einheiten.
Entwickler könnten die Definition einer Einheit mit einer einzelnen Methode verwechseln. Komponententests sollten das Verhalten einer Komponente testen, die möglicherweise aus mehreren verschiedenen Klassen besteht. Öffentliche Methoden, auf die andere Komponenten zugreifen können, müssen getestet werden, testen Sie keine geschützten Methoden oder Klassen. Sie sind Implementierungsdetails und nicht Teil Ihrer öffentlichen API .
Warum sollten wir überhaupt testen?
Wenn wir gute Tests schreiben und sie oft schreiben, können wir die Qualität unseres Produkts sicherstellen, bevor es das Licht der Welt erblickt.
Mit zunehmendem Alter unseres Systems werden die Vorteile des Testens immer offensichtlicher. Unsere Umgebungen werden zuverlässig, sie sparen Entwicklungszeit und viel Ärger, wenn unvermeidlich etwas schief geht. Ihre Kollegen werden dankbar sein, wenn sie einen Blick auf Ihre Tests werfen und verstehen können, was Ihr Code tun soll und was nicht, ohne Dinge manuell ausführen zu müssen .
Robuster Code
Bevor wir anfangen, über „robusten Code“ zu sprechen, wie sollten wir ihn definieren? Was macht Code robust? Bedeutet das, dass wir defensiv programmieren und darüber nachdenken sollten, wie andere Entwickler unseren Code missbrauchen könnten?
Robuster Code ist immer einfach und sauber. Einfachheit ist robust, Komplexität ist zerbrechlich . Wir sollten mit ungültigen Eingaben umgehen, aber das bedeutet nicht, dass wir defensiv programmieren und unserem Team nicht vertrauen müssen.
Gallsches Gesetz : Ein komplexes System, das funktioniert, hat sich immer aus einem einfachen System entwickelt, das funktioniert hat.
Auch die umgekehrte Behauptung scheint wahr zu sein: Ein komplexes System, das von Grund auf neu entwickelt wurde, funktioniert niemals und kann nicht zum Laufen gebracht werden. Sie müssen von vorne beginnen, beginnend mit einem funktionierenden einfachen System.
Sicher umgestalten
Wenn Ihr Code durch Tests abgedeckt ist, haben Sie keine Angst mehr, bestehenden Code zu ändern . Nach jeder Änderung können Sie Ihre Tests durchführen und sicherstellen, dass Sie nichts beschädigt haben. Wenn Sie Tests haben, müssen Sie nicht defensiv programmieren.
Refactoring ohne Tests geht einen schiefen Abhang hinunter, der in schlaflosen Nächten und Arbeitssonntagen enden wird. Dieses Thema ist zu weit gefasst, um hier behandelt zu werden, und verdient in Zukunft einen eigenen Blogbeitrag.
Wie schreiben wir keine Tests?
Es ist genauso wichtig zu wissen, wie man Unit-Tests NICHT schreibt , wie man sie schreibt.
- Das Schreiben eines Tests, der das Ergebnis eines Methodenaufrufs ausgibt, ist kein Test, da wir das gewünschte Ergebnis nicht validieren .
- Wenn Ihr Test Daten aus einer Datei in Ihrem Ordner „Dokumente“ liest, handelt es sich nicht um einen echten Komponententest, da Tests nicht von der Umgebung abhängen sollten.
- Jeder Entwickler sollte in der Lage sein, Ihren Code zu überprüfen und die Tests erfolgreich auszuführen, ohne etwas anderes zu tun.
- Jeder Unit-Test sollte unabhängig von anderen Tests sein . Das bedeutet, dass die Ausführungsreihenfolge Ihrer Tests ebenfalls keine Rolle spielen sollte.
- Das mehrmalige Ausführen von Tests sollte immer mit den gleichen Ergebnissen enden, wenn wir keinen Code ändern.
- Komponententests sollten Verhaltensweisen testen , nicht einzelne Methodenaufrufe. Nicht jede Klasse und Methode muss ihren Test haben.
Verhalten ist etwas, das einen echten Wert erzeugt, den der Benutzer Ihres Systems benötigt. Muss Ihr Benutzer wissen, ob productFactory.create() bei zweimaligem Aufruf dasselbe Objekt erstellt hat oder ob Ihr Repository mit einigen Parametern aufgerufen wurde? Wahrscheinlich nicht, aber dennoch schreiben viele Entwickler genau solche Tests.
Wenn Ihre Tests so aussehen, sind sie eng mit Ihrer Implementierung gekoppelt. Jedes Mal, wenn Sie die Details Ihrer Implementierung ändern möchten, müssen Sie Ihre Tests aktualisieren, obwohl das Verhalten dasselbe ist. Ihre Tests sollten sich nur ändern, wenn sich das Verhalten ändert, nicht die Implementierungsdetails . Mit anderen Worten: Testen Sie, was Ihr Code tut, nicht wie er es tut.
Wie schreiben wir Tests?
Unsere Tests müssen den besten Code - Praktiken folgen , sie müssen umgebungsunabhängig sein und schnell ausgeführt werden .
Es ist wichtig, die Testdurchführungszeit so kurz wie möglich zu halten. Jeder Test sollte nicht länger als ein paar Millisekunden dauern. Wenn die Ausführung von Tests zu lange dauert, neigen die Leute dazu, sie zu überspringen und sich einfach auf ihren CI-Server wie Jenkins zu verlassen, um Aufhebens zu machen, wenn er ihre ausführbaren Bereitstellungsdateien nicht erstellen kann.
Jeder Test besteht aus 3 A-Abschnitten (das AAA-Muster) :
- Ordnen
- Handlung
- Behaupten
1. Ordnen
Im Arrangement-Abschnitt unseres Tests stellen wir sicher, dass sich unser System in einem bestimmten Zustand befindet, bevor wir das zu testende Verhalten aufrufen . Das „System“ könnte ein Objekt sein, das wir auf eine bestimmte Weise einrichten müssen, um Verhalten zu erzeugen, temporäre Dateien oder ähnliches zu erstellen.

Der Code in diesem Abschnitt ist normalerweise größer als die anderen beiden zusammen .
Ein Entwurfsmuster, das sich als besonders nützlich erweisen sollte, um diesen Abschnitt klein zu halten, ist Object Mother . Dieses Entwurfsmuster ist Factory sehr ähnlich, verfügt jedoch über spezifischere Methoden, die vorkonfigurierte Objekte für Sie erstellen. Während eine Standardfabrik eine Methode wie createCar(carDescription) Factory könnte, hat eine ObjectMother Methoden wie createRedFerrari() , createBlackTesla() oder createBrokenYugo() .
2. Akt
Dieser Abschnitt Ihres Tests muss aus einer Zeile bestehen . Diese Zeile führt das zu testende Verhalten aus. Wenn Sie mehr als eine Zeile für diesen Abschnitt schreiben, haben Sie wahrscheinlich nicht die richtige Zusammenfassung Ihres Verhaltens . Von Ihren Clients sollte nicht erwartet werden, dass sie mehrere Methoden Ihres Objekts in einer bestimmten Reihenfolge aufrufen, warum also Ihre Tests?
Diese Zeile ist ein Methodenaufruf, den wir testen möchten. Wenn diese Methode ein Ergebnis zurückgibt, sollten Sie diesen Wert in einer Variablen speichern, um zu überprüfen, ob es sich um den erwarteten Wert im Assert-Schritt handelt.
3. Behaupten
Nachdem wir das System im Abschnitt Arrange vorbereitet und unsere zu testende Aktion im Abschnitt Act ausgeführt haben, müssen wir das Ergebnis der Aktion validieren. Normalerweise überprüfen wir hier das Ergebnis der Methode, aber manchmal geben unsere Methoden keine Werte zurück, aber sie erzeugen immer noch Nebenwirkungen. Wenn von unserem Code erwartet wurde, dass er den Zustand eines Objekts ändert, eine Datei erstellt oder etwas aus einer List entfernt, sollten wir prüfen, ob er genau das getan hat.
Stubs gegen Mocks
Die meisten Entwickler verwenden die Begriffe Mock und Stub synonym, aber es gibt Unterschiede.
Ein Stub kann den Test nicht bestehen, ein Mock schon.
Stubs sind zustandsbasiert und geben fest codierte Werte zurück. "Welches Ergebnis habe ich bekommen?"
Mocks sind verhaltensbasiert , Sie verwenden sie, um zu überprüfen, wie Ihr Verhalten sie durchläuft. „Wie bin ich auf das Ergebnis gekommen?“
Wenn Ihre Einheitentests mit Mocks übersät sind, werden Ihre Tests sehr anfällig, was bedeutet, dass Sie jedes Mal, wenn Sie eines Ihrer Implementierungsdetails ändern, alle Ihre Mock-Aufrufe aktualisieren müssen.
Testen Sie nur eine Sache.
Wir müssen in der Lage sein , ein Verhalten zu isolieren und zu beweisen, dass es funktioniert . Wenn dieses Verhalten bei unterschiedlichen Eingaben unterschiedlich funktionieren soll, müssen wir für jedes dieser Verhaltensweisen einen neuen Test schreiben. Es ist schwer zu sagen, warum unser Test fehlgeschlagen ist, wenn wir einen großen Test haben, der mehrere Dinge gleichzeitig testet. Darüber hinaus wird es schwieriger, Funktionen zu entfernen, die wir nicht mehr benötigen, und zu sehen, welche Funktionen beschädigt wurden, wenn wir neuen Code hinzufügen.
Es ist vollkommen in Ordnung, mehrere Assertionen im letzten Abschnitt Ihrer Tests zu haben, solange dies nicht erfordert, dass Sie das zu testende Verhalten mehrmals aufrufen. Wenn dies der Fall ist, wird es schwierig sein, das fehlerhafte Verhalten zu lokalisieren und zu beheben.
Wenn wir mehrere Verhaltensweisen in einem Test geltend machen, erhalten wir kein klares Bild davon, was genau nicht funktioniert, da der Test nur den ersten Fehler meldet und der Rest übersprungen wird. Es wird viel schwieriger zu verstehen, welche Änderungen notwendig sind und wie viele Dinge nicht wie erwartet funktionieren.
Benennungstests
Eine Sache, die gute Tests von großartigen Tests unterscheidet, ist der Testname. Tests sollten uns nicht nur sagen, was sie tun, sondern wann sie es tun.
Es gibt viele gute Benennungsmuster, die Sie verwenden können, also wählen Sie das aus, das Sie am aussagekräftigsten finden, und bleiben Sie dabei. Hier sind einige Beispiele für großartige Testnamen:
- RegistrationServiceShould.createNewAccountWhenEmailIsNotTaken
- RegistrationServiceTest.whenEmailIsFree_createNewAccount
- RegistrationServiceTest.if_freeEmail_when_userCreatesAccount_then_create
Wenn Sie Unit-Tests schreiben, ist es viel wichtiger zu kommunizieren, was Sie testen , als sich an die besten Verfahren zur Benennung von Methoden zu halten. In Java verwenden wir zum Beispiel camelCase beim Schreiben von Methoden, aber es ist absolut zulässig, den Unterstrich (_) zu verwenden, um den Zustand von der Aktion in Ihrem Testnamen zu trennen.
Saubere Tests
Die Tests, die Sie schreiben, sollten allen Clean-Code-Praktiken folgen, die Sie auf Ihren Code anwenden. Tests sind keine Bürger zweiter Klasse, und Sie müssen dieselbe Sorgfalt walten lassen wie den Rest Ihres Codes, um ihn lesbar zu machen.
Die Definition der Codeduplizierung in Tests ist sehr wichtig. Das DRY-Prinzip (Don't Repeat Yourself) gilt für das Extrahieren von Verhaltensweisen, die sich aus demselben Grund ändern. Tests ändern sich aus verschiedenen Gründen, seien Sie also abwechslungsreich beim Extrahieren von Dingen aus Ihren Tests, wenn sie sich wirklich nicht aus demselben Grund ändern. Spoiler-Alarm, sie tun es oft nicht.
if -Anweisungen gehören nicht in Tests. Die if -Anweisung sagt uns, dass unser Test mindestens zwei verschiedene Dinge tut und wir besser dran wären, wenn wir unseren Test in zwei verschiedene Tests umschreiben würden. Bei richtiger Benennung ist es einfacher zu verstehen, was die Tests tun und was all die unterschiedlichen Verhaltensweisen sind.
Wann sollten wir Tests schreiben?
Geleitet von den Prinzipien von TDD sollten wir Tests schreiben, bevor wir neuen Code schreiben .
Wenn wir ein neues Feature hinzufügen müssen, beschreiben wir zunächst das gewünschte Verhalten als neuen Test. Wir nehmen die geringste Menge an Änderungen vor, die erforderlich sind, um diesen Test zu bestehen, ohne andere zu brechen.
Andernfalls gilt: Je mehr Zeit vergeht, desto mehr Code haben wir, der nicht getestet wurde, und die Wahrscheinlichkeit, Fehler oder Overengineering einzuführen, steigt .
Außerdem werden die Tests komplexer, da wir jetzt mehr Code zum Testen haben, und was normalerweise passiert, ist, dass Entwickler die Tests an den Code anpassen. Das bedeutet, dass wir das Verhalten an das anpassen, was unser Code tut, und nicht umgekehrt.
Wenn wir Tests früh schreiben, wird die Definition des Problems kleiner und es ist einfacher, sich mit solchen Problemen zu beschäftigen, als mit allgemeineren und komplexeren Verhaltensweisen.
Abschließende Gedanken
Während das Schreiben von Tests wie eine optionale Sache erscheinen mag, ist es wichtig, mit guten Grundlagen zu beginnen. Codieren ist bereits schwierig, machen Sie es sich und Ihren Teamkollegen leichter, indem Sie testbaren Code schreiben, der einfacher zu lesen, zu verstehen und zu warten ist . Wenn Sie schließlich Schwierigkeiten haben, Ihre Tests Ihren Wünschen anzupassen, liegt das Problem wahrscheinlich eher in Ihrem Code als in Ihren Tests.
Sind Sie daran interessiert, an einem sauberen und testbaren Code zu arbeiten?
Sehen Sie sich unsere offene Stelle als Senior Backend Developer an
oder senden Sie uns eine Initiativbewerbung !
