• IT-Karriere:
  • Services:

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. Hays AG, Bonn
  2. Deloitte, Leipzig

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.

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


Anzeige
Top-Angebote
  1. mit Rabattcode "PKAUFEN"
  2. (u. a. Battlefield- & Star-Wars-Spiele von EA günstiger (u. a. Star Wars Battlefront 2 für 7...
  3. (u. a. Fortnite - The Last Laugh Bundle DLC (PS4 Download Code) für 19,90€, ARK: Survival...

xUser 20. Feb 2021 / Themenstart

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

NoGoodNicks 13. Feb 2021 / Themenstart

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

freebyte 09. Feb 2021 / Themenstart

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

Steffo 09. Feb 2021 / Themenstart

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

Netzweltler 09. Feb 2021 / Themenstart

Daher wird man auch in Zukunft Tests der Software so weit wie möglich beschränken. Daher...

Kommentieren


Folgen Sie uns
       


Librem Mini - Fazit

Der Librem Mini punktet mit guter Linux-Unterstützung, freier Firmware und einem abgesicherten Bootprozess.

Librem Mini - Fazit Video aufrufen
Open-Source-Mediaplayer: Die Deutschen werden VLC wohl zerstören
Open-Source-Mediaplayer
"Die Deutschen werden VLC wohl zerstören"

Der VideoLAN-Gründer Jean-Baptiste Kempf spricht im Golem.de Interview über Softwarepatente und die Idee, einen Verkehrskegel als Symbol zu verwenden.
Ein Interview von Martin Wolf

  1. 20 Jahre VLC Die beste freie Software begleitet mich seit meiner Kindheit

No-Regret-Infrastruktur: Wasserstoffnetze für Stahl und Chemie
No-Regret-Infrastruktur
Wasserstoffnetze für Stahl und Chemie

Die Organisation Agora Energiewende schlägt vor, sich beim Bau von Wasserstoffleitungen und Speichern zunächst auf wenige Regionen zu konzentrieren.
Von Hanno Böck

  1. Brennstoffzellenfahrzeug Fraunhofer IFAM entwickelt wasserstoffspeichernde Paste
  2. Wasserstoff Lavo entwickelt Wasserstoffspeicher fürs Eigenheim
  3. Energiewende EWE baut einen Wasserstoffspeicher bei Berlin

Programmiersprachen: Weniger Frust mit Rust
Programmiersprachen
Weniger Frust mit Rust

Die Programmiersprache Rust macht nicht nur weniger Fehler, sie findet sie auch früher.
Von Florian Gilcher

  1. Linux Rust wandert ins System
  2. Open Source Rust verabschiedet sich endgültig von Mozilla
  3. Google und ISRG Apache-Webserver bekommt Rust-Modul

    •  /