Verwendung von Referenzmodellen

Eine der größten Fragen, die sich stellen, wenn Sie mehr und mehr zufällige Tests durchführen, ist: Wie kann der Test herausfinden, wie die richtige Antwort lauten sollte?

Stellenmarkt
  1. DevOps Engineer (m/w/d)
    STEMMER IMAGING AG, Puchheim bei München
  2. Agile Master (m/w/d)
    intersoft AG, Hamburg
Detailsuche

Zurück zu dem Divisionsbeispiel: Wenn wir einen gerichteten Test für Divider.divide (27.0, 3.0) geschrieben haben, können wir manuell ableiten und prüfen, ob die Antwort 9.0 lautet. Wenn wir aber zufällige Eingaben verwenden, wie können wir dann herausfinden, was die richtige Antwort wäre? Mit einem dynamisch generierten/aktualisierten Referenzmodell.

Ich habe viele Testrichtlinien gesehen, die sich strikt gegen ein dynamisches Referenzmodell aussprechen, das das erwartete Ergebnis liefert. In einem Punkt sind sie sicherlich richtig: Wenn Ihr Testreferenzmodell dem tatsächlichen Produktionscode ähnelt, enthält es genau die gleichen Fehler wie Ihr Produktionscode und führt zu falsch-positiven Ergebnissen.

Die Lösung besteht jedoch nicht darin, Referenzmodelle vollständig abzulehnen, sondern welche zu verwenden, die sich ausreichend von Ihrem Produktionscode unterscheiden, um zu vermeiden, dass Fehler repliziert werden.

Golem Karrierewelt
  1. Deep-Dive Kubernetes – Observability, Monitoring & Alerting: virtueller Ein-Tages-Workshop
    10.11.2022, Virtuell
  2. Adobe Photoshop Aufbaukurs: virtueller Zwei-Tage-Workshop
    06./07.10.2022, Virtuell
Weitere IT-Trainings

Angenommen, Sie möchten eine CRUD-API testen, um ein Ereignis zu erstellen, andere Benutzer zu dem Ereignis einzuladen und alle RSVPs zu bekommen. Die reale API führt all dies mithilfe von Datenbankerstellungen/-aktualisierungen/-suchen sowie verschiedenen Analysen der Ergebnisse durch. Hier gibt es viel Raum für Fehler. Ein Referenzmodell kann hingegen einfache POJOs und In-Memory-Speicher wie Hashmaps verwenden. Dieses Referenzmodell kann dann mit den tatsächlichen Daten verglichen werden, die vom Integrationstest zurückgegeben wurden.

Es ist richtig, dass die Verwendung von Referenzmodellen die Komplexität Ihrer Tests erhöht. Leider ist das oft ein notwendiges Übel. Die Vorteile bei der Abdeckung, die wir durch zufällige Tests mit Referenzmodellen erhalten, sind viel zu groß, um ein allgemeines Verbot zu rechtfertigen. Bei großen Hardwareprojekten wird das häufig genau so gemacht - eben wegen der großen Vorteile.

Testen Sie einen gesamten Pfad, nicht nur eine einzelne Ausgabe

Angenommen, Sie haben ein System, in dem die folgende Kette gekoppelter Ereignisse auftreten kann: Ai -> Bi -> Ci -> Di -> Ei

Und Sie möchten testen, ob die obige, spezifische Sequenz von Eingaben die folgende Kette von Ausgaben erzeugt: Ao -> Bo -> Co -> Do -> Eo

Sie können entweder die folgende Testsequenz schreiben:

  1. @Test
  2. public void testA() {
  3. System system = new System();
  4. Output output = system.apply(A_I);
  5. Truth.assertThat(output).isEqualTo(A_O);
  6. }
  7.  
  8. @Test
  9. public void testB() {
  10. System system = new System();
  11. Output output = system.apply(A_I);
  12. output = system.apply(B_I);
  13. Truth.assertThat(output).isEqualTo(B_O);
  14. }
  15.  
  16. @Test
  17. public void testC() {
  18. System system = new System();
  19. Output output = system.apply(A_I);
  20. output = system.apply(B_I);
  21. output = system.apply(C_I);
  22. Truth.assertThat(output).isEqualTo(C_O);
  23. }
  24.  
  25. @Test
  26. public void testD() {
  27. System system = new System();
  28. Output output = system.apply(A_I);
  29. output = system.apply(B_I);
  30. output = system.apply(C_I);
  31. output = system.apply(D_I);
  32. Truth.assertThat(output).isEqualTo(D_O);
  33. }
  34.  
  35. @Test
  36. public void testE() {
  37. System system = new System();
  38. Output output = system.apply(A_I);
  39. output = system.apply(B_I);
  40. output = system.apply(C_I);
  41. output = system.apply(D_I);
  42. output = system.apply(E_I);
  43. Truth.assertThat(output).isEqualTo(E_O);
  44. }

Oder Sie schreiben einen Test, der alles abdeckt:

  1. @Test
  2. public void testABCDE() {
  3. System system = new System();
  4.  
  5. Output output = system.apply(A_I);
  6. Truth.assertThat(output).isEqualTo(A_O);
  7.  
  8. output = system.apply(B_I);
  9. Truth.assertThat(output).isEqualTo(B_O);
  10.  
  11. output = system.apply(C_I);
  12. Truth.assertThat(output).isEqualTo(C_O);
  13.  
  14. output = system.apply(D_I);
  15. Truth.assertThat(output).isEqualTo(D_O);
  16.  
  17. output = system.apply(E_I);
  18. Truth.assertThat(output).isEqualTo(E_O);

Wenn Sie die von vielen Entwicklern gepredigte Regel "Eine Assertion pro Test" befolgen würden, wären Sie gezwungen, die erste Option zu wählen. Ich weiß nicht, was Sie bevorzugen, aber ich finde die zweite Option bei Weitem besser.

Sie ist viel skalierbarer, insbesondere wenn Sie komplexe Systeme mit langen Ereignisketten oder Ähnlichem haben, die in jeder Phase überprüft werden müssen. Bei Regeln wie den oben genannten ist es kein Wunder, dass Entwickler beim Testen Abkürzungen nehmen - das Befolgen aller vorgeschriebenen Regeln ist viel zu aufwändig!

Wenn Sie mit einem Test-Framework arbeiten, bei dem die einzige Information, die zur Verfügung gestellt wird, der Name des fehlgeschlagenen Tests ist, wäre diese Argumentation vielleicht gültig. Glücklicherweise bieten die meisten modernen Test-Frameworks viel mehr Debug-Informationen.

Ein gut geschriebener Test sollte Fehlermeldungen erzeugen, die klar angeben, wo im Test er fehlgeschlagen ist, warum, und was die Unterschiede zwischen den erwarteten und den tatsächlichen Ausgaben sind. Das Debuggen des Testfehlers sollte dann eine einfache Angelegenheit sein - nämlich indem man sich schlicht die Fehlermeldung ansieht.

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

Prägnanz in Ihrer Test-Codebasis ist extrem wertvoll, hauptsächlich aus den gleichen Gründen wie bei der Produktions-Codebasis. Daher beinhalten Hardwaretests oft Hunderte verschiedene Prüfungen, die alle zu unterschiedlichen Zeiten ausgeführt werden und unterschiedliche Dinge überprüfen, in einem einzigen Test.

Angesichts der riesigen Menge an Abdeckung, die benötigt wird, ist es unrealistisch, für jede einzelne Ereignis-Ergebnis-Kombination dedizierte Tests zu schreiben. Verzichten Sie auf dogmatische Regeln, die zu einer ausufernden Menge an Tests führen. Es ist in Ordnung, wenn ein einzelner Test mehrere Dinge entlang eines einzelnen Codepfades überprüft.

Wie viele 9en streben Sie an?

"Automatisierte Integrationstests für alle Funktionen schreiben? Jeden möglichen Eckfall testen? Zufällige Eingaben und Referenzmodelle? Ist das alles wirklich notwendig?"

Das ist eine gute Frage und die Antwort lautet: Es kommt darauf an.

Beim Systemdesign ist die erste Frage, die wir uns stellen, wie viele 9en der Zuverlässigkeit wir anstreben. Und wenn die Antwort hoch genug ist, entwerfen wir fantastisch komplexe Systeme, um diese Ziele zu erreichen. Das gleiche Prinzip gilt für das Testen. Je zuverlässiger Ihre Testsuite sein soll, desto komplexere Techniken müssen Sie einsetzen, um diese Ziele zu erreichen.

Wenn es für Ihr Projekt kein großes Problem ist, dass regelmäßig Sonderfall-Fehler in die Produktion kommen, können Sie mit den gleichen Testmethoden auskommen, die von den meisten Softwareprojekten verwendet werden. Wenn Sie aber eine wirklich sichere Testsuite erstellen wollen, eine, die Fehler in der Produktion extrem selten werden lässt, müssen Sie auf hohe Zuverlässigkeitswerte abzielen. Sie müssen Integrationstests, zufällige Eingaben und Referenzmodelle einbeziehen. Sie müssen paranoid sein, wenn es darum geht, alles und jedes zu testen, was schiefgehen könnte.

In vielen Fällen werden Sie feststellen, dass die Verwendung von Integrationstests und randomisierten Eingaben die Testabdeckung tatsächlich verbessert und gleichzeitig die Entwicklungszeit und den Umfang der Tests reduziert.

In anderen Fällen, wenn Sie versuchen, das letzte Quäntchen Zuverlässigkeit herauszuquetschen, wird Ihre Testsuite immer komplexer werden. Auf der anderen Seite werden Sie jedoch so viel Vertrauen in Ihre Testsuite haben, dass Sie größere Code-Änderungen machen können - und zwar mit minimalen manuellen Tests und Problemen.

Es gibt hier kein Richtig oder Falsch. Je nachdem, wie viel Zuverlässigkeit Sie anstreben, können Sie von Fall zu Fall Kompromisse zwischen Komplexität, Verbosität und Abdeckung eingehen, indem Sie viele der oben beschriebenen Techniken anwenden. Seien Sie ehrlich zu sich selbst, was die Prioritäten Ihres Projekts angeht, und entscheiden Sie dann, welche Opfer Sie zu bringen bereit sind, um diese zu erreichen.

Weiterführende Links:

Der Einsatz von randomisierten Tests bei Dropbox, um die Abdeckung ihrer Sync-Funktionalität zu verbessern

jqwik - Eigenschaftsbasierte Testbibliothek für Java (Dank an Dan Turner für die Empfehlung)

QuickTheories - Eine weitere eigenschaftsbasierte Testbibliothek für Java

Online-Diskussionsforen:

/r/programming - 2019/05

/r/programming - 2019/11

Bitte aktivieren Sie Javascript.
Oder nutzen Sie das Golem-pur-Angebot
und lesen Golem.de
  • ohne Werbung
  • mit ausgeschaltetem Javascript
  • mit RSS-Volltext-Feed
 Zufallstests: Was die Amateure von den Profis unterscheidet
  1.  
  2. 1
  3. 2
  4. 3
  5. 4
  6. 5


xUser 20. Feb 2021

UI Tests sind prinzipbedingt langsam. Außerdem sind ihre Failures eher unspezifisch zum...

NoGoodNicks 13. Feb 2021

Korrekt. Und wenn es nicht der Tester ist, dann ist es der Integrator. Der braucht immer...

freebyte 09. Feb 2021

Dann muss sich in den letzten Monaten etwas geändert haben, was ich nicht mitbekommen...

Steffo 09. Feb 2021

Kann mich dem nur anschließen. Ich teste auch ziemlich ausführlich und bin für jeden...



Aktuell auf der Startseite von Golem.de
Ukrainekrieg
Meta stoppt ausgefeilte russische Desinformationskampagne

Gefakte Webseiten deutscher Medien machen Stimmung gegen die Russland-Sanktionen. Die falschen Artikel wurden über soziale Medien verbreitet.

Ukrainekrieg: Meta stoppt ausgefeilte russische Desinformationskampagne
Artikel
  1. Berufsschule für die IT-Branche: Leider nicht mal ausreichend
    Berufsschule für die IT-Branche
    Leider nicht mal "ausreichend"

    Lehrmaterial wie aus einem Schüleralbtraum, ein veralteter Rahmenlehrplan und nette Lehrer, denen aber die Praxis fehlt - mein Fazit aus drei Jahren als Berufsschullehrer.
    Ein Erfahrungsbericht von Rene Koch

  2. Alice Eviation Aircraft: Elektrisches Flugzeug hebt zu seinem Jungfernflug ab
    Alice Eviation Aircraft
    Elektrisches Flugzeug hebt zu seinem Jungfernflug ab

    Das erste vollelektrische Pendlerflugzeug hat erfolgreich seinen Jungfernflug in den USA absolviert. DHL hat die Cargo-Version bestellt.

  3. iPadOS 16: Apple bringt Stage Manager auf alte iPads
    iPadOS 16
    Apple bringt Stage Manager auf alte iPads

    Zuerst wollte Apple das wichtigste iPadOS-16-Feature nur auf M1-iPads bringen, doch nun kommt der Stage Manager auch für alte iPads.

Du willst dich mit Golem.de beruflich verändern oder weiterbilden?
Zum Stellenmarkt
Zur Akademie
Zum Coaching
  • Schnäppchen, Rabatte und Top-Angebote
    Die besten Deals des Tages
    Daily Deals • Jetzt PS5-Verkauf bei Amazon • Viewsonic Curved 27" FHD 240 Hz günstig wie nie: 179,90€ • MindStar (Gigabyte RTX 3060 Ti 499€, ASRock RX 6800 579€) • AMD Ryzen 7000 jetzt bestellbar • Alternate (Kingston Fury DDR5-5600 16GB 96,90€) [Werbung]
    •  /