• IT-Karriere:
  • Services:

Ein TDD-Beispiel

Um zu veranschaulichen, warum das nicht stimmt, und um die Vorteile von mutationsgetriebenem Testen zu demonstrieren, habe ich das folgende Beispiel zusammengestellt. Die Quelle des Beispiels ist das oberste Google-Ergebnis, wenn Sie nach "TDD Example" suchen.

Stellenmarkt
  1. über Hays AG, München
  2. STEMMER IMAGING AG, Puchheim

Man könnte argumentieren, dass der Autor dieses Artikels kein gutes Vorbild für TDD ist und dass "kein echter TDD-Praktiker" Tests wie diese schreiben würde. Aber so zu argumentieren, ist nur verletzter Stolz. Der Autor hat sich bemüht, umfassende Tests zu schreiben, hat gute Arbeit geleistet - und das hier gewählte Beispiel ist sehr einfach. Die Realität ist, dass die meisten Praktiker nicht perfekt sind und Dinge übersehen werden. Solche Fehler können mit mutationsgetriebenem Testen erkannt und behoben werden.

Lassen Sie uns also das Beispiel ansehen, es geht um das Erstellen eines einfachen String-basierten Rechners. Der Einfachheit halber betrachten wir nur die ersten drei Anforderungen des Beispiels, zusammen mit ihren Implementierungen und Tests.

Die Anforderungen:

Golem Akademie
  1. Advanced Python - Fortgeschrittene Programmierthemen
    17./18. Juni 2021, online
  2. Einführung in die Programmierung mit Rust
    21.-24. September 2021, online
Weitere IT-Trainings

  • Die Methode kann 0, 1 oder 2 durch ein Komma getrennte Zahlen annehmen.
  • Bei einer leeren Zeichenkette gibt die Methode 0 zurück.
  • Die Methode gibt die Summe der Zahlen zurück.

Die Tests:

  1. private static final TddExample EXAMPLE = new TddExample();
  2.  
  3. @Test(expected = RuntimeException.class)
  4. public final void whenMoreThan2NumbersAreUsedThenExceptionIsThrown() {
  5. EXAMPLE.add("1,2,3");
  6. }
  7.  
  8. @Test
  9. public final void when2NumbersAreUsedThenNoExceptionIsThrown() {
  10. EXAMPLE.add("1,2");
  11. Assert.assertTrue(true);
  12. }
  13.  
  14. @Test(expected = RuntimeException.class)
  15. public final void whenNonNumberIsUsedThenExceptionIsThrown() {
  16. EXAMPLE.add("1,X");
  17. }
  18.  
  19. @Test
  20. public final void whenEmptyStringIsUsedThenReturnValueIs0() {
  21. Assert.assertEquals(0, EXAMPLE.add(""));
  22. }
  23.  
  24. @Test
  25. public final void whenOneNumberIsUsedThenReturnValueIsThatSameNumber() {
  26. Assert.assertEquals(3, EXAMPLE.add("3"));
  27. }
  28.  
  29. @Test
  30. public final void whenTwoNumbersAreUsedThenReturnValueIsTheirSum() {
  31. Assert.assertEquals(3+6, EXAMPLE.add("3,6"));
  32. }

Die Implementierung:

  1. public int add(final String numbers) {
  2. int returnValue = 0;
  3. String[] numbersArray = numbers.split(",");
  4. if (numbersArray.length > 2) {
  5. throw new RuntimeException("Up to 2 numbers separated by comma (,) are allowed");
  6. }
  7. for (String number : numbersArray) {
  8. if (!number.trim().isEmpty()) { // After refactoring
  9. returnValue += Integer.parseInt(number);
  10. }
  11. }
  12. return returnValue;
  13. }

Das scheint eine gute Testsuite zu sein, die alle Funktionen abdeckt. Aber wie gut ist sie im Vergleich zu mutationsgetriebenen Tests? In der realen Welt würde ich jetzt einen Fehler nach dem anderen einsetzen und die Tests nach jedem einzelnen ausführen. Der Einfachheit halber setzen wir hier jedoch alle relevanten Bugs auf einmal ein.

Mutation 1: Leer vs. Leerzeichen

  1. if (!number.trim().isEmpty())

Das scheint zu einfach, aber genau deswegen lohnt es sich hinzuschauen. Denn: Was wäre, wenn wir den trim()-Aufruf aus Versehen aus unserer Implementierung entfernen würden? So was kann passieren.

  1. if (!number.isEmpty())

Mutation 2: Rückgabe von 0 für einen leeren String

  1. if (!number.trim().isEmpty()) { // After refactoring
  2. returnValue += Integer.parseInt(number);
  3. }

Die Anforderung besagt, dass bei einer leeren Zeichenkette 0 zurückgegeben werden soll. Basierend auf der Implementierung des Autors bedeutet das vermutlich, dass jeder leere Substring als 0 behandelt werden soll, während vorangegangene nicht-leere Substrings weiterhin summiert werden sollen. Was aber, wenn die Implementierung etwas anderes macht und 0 zurückgibt, sobald sie eine leere Zeichenkette sieht?

  1. if (number.trim().isEmpty()) { return 0; }
  2. returnValue += Double.parseDouble(number);

Handbuch für Softwareentwickler: Das Standardwerk für professionelles Software Engineering

Mutation 3: Drei Eingaben sind nicht erlaubt

  1. if (numbersArray.length > 2) {
  2. throw new RuntimeException("Up to 2 numbers separated by comma (,) are allowed");
  3. }

Die Anforderung besagt, dass die Methode "0, 1 oder 2 Zahlen" annehmen kann. Sollten wir also vielleicht auch auf drei Zahlen prüfen und dann eine Exception auslösen?

Klar: Das ist ein ziemlich dämlicher Fehler, aber man sollte nie unterschätzen, wie kreativ Menschen beim Fehlermachen sein können.

  1. if (numbersArray.length == 3) {
  2. throw new RuntimeException("Up to 2 numbers separated by comma (,) are allowed");
  3. }

Mutation 4: Double vs. Int

  1. returnValue += Integer.parseInt(number);

Bei einem "String -> Numerisch"-Rechner mit Integer-Endergebnis gibt es viele Möglichkeiten, String-Konvertierungen zu implementieren:

1. String in Int konvertieren, Int-Operationen durchführen, ein Int-Ergebnis zurückgeben. Lösen Sie eine Exception aus, wenn der Eingabestring kein Int ist.

2. String in Double konvertieren, Double in Int umwandeln, Int-Operationen durchführen, Int-Ergebnis zurückgeben.

3. String in Double konvertieren, Double-Operationen ausführen, Endergebnis in Int umwandeln und zurückgeben.

Die drei Ansätze erzeugen völlig unterschiedliche Ausgaben, wenn etwa "1.5, 1.5" eingegeben wird. Im Beispiel hat der Autor Option 1 implementiert. Nehmen wir an, das ist tatsächlich das gewünschte Verhalten. Aber was, wenn er fälschlicherweise Option 3 implementiert hätte?

  1. returnValue += Double.parseDouble(number);

Bitte aktivieren Sie Javascript.
Oder nutzen Sie das Golem-pur-Angebot
und lesen Golem.de
  • ohne Werbung
  • mit ausgeschaltetem Javascript
  • mit RSS-Volltext-Feed
 Softwareentwicklung: Wenn testgetriebene Entwicklung einfach nicht gut genug istUnd jetzt fügen wir alles zusammen 
  1.  
  2. 1
  3. 2
  4. 3
  5.  


Anzeige
Top-Angebote
  1. 169,90€ (Vergleichspreis 204,68€)
  2. 29,99€ (Bestpreis!)
  3. 9,99€ (Vergleichspreis 17,21€)
  4. 44,99€ (Vergleichspreis 56,61€)

Robert.Mas 21. Apr 2021 / Themenstart

Du meinst so wie hier im Artikel: "Es gibt übrigens Werkzeuge(Link: https://pitest.org...

kayozz 21. Apr 2021 / Themenstart

+1 Allein dadurch, dass ich von außen einen Test schreibe, und danach die...

Steffo 20. Apr 2021 / Themenstart

Sagt er doch gar nicht: "1. Erreichen Sie einen Zustand, in dem Sie sowohl Code als auch...

Steffo 20. Apr 2021 / Themenstart

Alles, was du hier ansprichst, behandelt doch der Artikel. Dadurch lassen sich natürlich...

Steffo 20. Apr 2021 / Themenstart

Klar und kurz vor Release soll dann alles nochmal durchgetestet werden, um dann...

Kommentieren


Folgen Sie uns
       


Audi E Tron GT Probe gefahren

Der E-Tron GT ist die Oberklasse-Limousine von Audi. Golem.de ist das Elektroauto Probe gefahren.

Audi E Tron GT Probe gefahren Video aufrufen
Programm für IT-Jobeinstieg: Hoffen auf den Klebeeffekt
Programm für IT-Jobeinstieg
Hoffen auf den Klebeeffekt

Aktuell ist der Jobeinstieg für junge Ingenieure und Informatiker schwer. Um ihnen zu helfen, hat das Land Baden-Württemberg eine interessante Idee: Es macht sich selbst zur Zeitarbeitsfirma.
Ein Bericht von Peter Ilg

  1. Arbeitszeit Das Sechs-Stunden-Experiment bei Sipgate
  2. Neuorientierung im IT-Job Endlich mal machen!
  3. IT-Unternehmen Die richtige Software für ein Projekt finden

Weclapp-CTO Ertan Özdil: Wir dürfen nicht in Schönheit und Perfektion untergehen!
Weclapp-CTO Ertan Özdil
"Wir dürfen nicht in Schönheit und Perfektion untergehen!"

Der CTO von Weclapp träumt von smarter Software, die menschliches Eingreifen in der nächsten ERP-Generation reduziert. Deutschen Perfektionismus hält Ertan Özdil aber für gefährlich.
Ein Interview von Maja Hoock


    Fiat 500 als E-Auto im Test: Kleinstwagen mit großem Potenzial
    Fiat 500 als E-Auto im Test
    Kleinstwagen mit großem Potenzial

    Fiat hat einen neuen 500er entwickelt. Der Kleine fährt elektrisch - und zwar richtig gut.
    Ein Test von Peter Ilg

    1. Vierradlenkung Elektrischer GMC Hummer SUV fährt im Krabbengang seitwärts
    2. MG Cyberster MG B Roadster mit Lasergürtel und Union Jack
    3. Elektroauto E-Auto-Prämie übersteigt in 2021 schon Vorjahressumme

      •  /