Nachlese: Generatives Testen in Clojure | Print |
Written by Philip Höfges   
Tuesday, 24 February 2015 12:26

Auch zum ersten Rheinjug-Vortrag im neuen Jahr haben sich etwa 60 Menschen im Hörsaal eingefunden, trotz eines kurzfristigen Redner- und Themenwechsels. Mit dem neuen Thema „Generatives Testen in Clojure“ stellt der Redner Jens Bendisposto sein persönliches Steckenpferd vor. Er hat am Lehrstuhl für Softwaretechnik und Programmiersprachen an der HHU promoviert und ist begeisterter Clojure-Nutzer.

Jens beginnt seinen Vortrag mit einer kurzen Einführung in die Sprache Clojure. Clojure ist ein LISP und gehört in die Kategorie der funktionalen Programmiersprachen. Wie viele andere Programmiersprachen auch, hat Clojure die klassichen Basisdatentypen, Integer und Strings, sowie einige Spezielle, nämlich Keywords und Symbole. Diese fungieren vor allem als Wiedererkennung. Zu dem gibt es in Clojure einige zusammengesetzte Datentypen: Vektoren, Listen, Sets und Maps. Diese unterscheiden sich nur wenig. Sets können beispielsweise keine doppelten Einträge enthalten, während Maps (ähnlich wie die HashMaps in Java) von einem Schlüssel auf einen Wert abbilden. Dabei können hier die Einträge bzw. Schlüssel und Werte jeweils jeder Basisdatentypen sein. Man kann alles frei vermischen und verschachteln. Mehr gibt es zur Grundsyntax nicht zu sagen. Damit kann man bereits alles in Clojure machen, behauptet Jens Bendisposto. Interessanter wird es natürlich bei Funktionen und deren Calls. Ein sehr gutes Feature von Clojure ist die Kompatiblität mit Java. Man kann jede Java-Funktion in Clojure importieren und benutzen. Dabei muss natürlich auf die Infix-Notation geachtet werden. Hier stellt der Redner einen schönen Vergleich an: Es wurde immer behauptet, dass Clojure sehr viele Klammern benutzen würde. Dies stimmt auch, aber Jens Bendisposto zeigt, dass es bei Java noch viel mehr sein können. Mit Hilfe der zwei wichtigten Clojure-Funktionen Map und Reduce zeigt der Redner, wie einfach man auch Funktionen als Parameter und Rückgabewert benutzen kann. Dies macht die Sprache sehr flexibel. Damit beendet Jens die Einführung in die Sprache Clojure.

Im zweiten Teil des Vortrags kommt Jens Bendisposto auf das eigentliche Thema zu sprechen: Testen. Die Basis von Tests in Clojure sind einfache Vergleiche zwischen dem Ergebnis einer Berechnung und deren erwartetes Ergebnis. Dies sind die Unit-Tests. Man kann einzelne Tests durchführen, aber auch mehrere Tests in einen Test-Case integrieren. Dies setzt natürlich voraus, dass das Ergebnis bekannt ist. Man muss sich also für jede einzelne Berechnung das Ergebnis überlegen. Dies ist natürlich sehr umständlich und deckt auch nicht jeden Randfall ab.

Um viele Tests schnell und übersichtlich zu lösen, wurde das generative Testen entwickelt. Dabei erzeugen so genannten „Generatoren“ zufällige Werte, die dann in die Tests integriert werden können. So können kontrolliert Zahlen, String, boolsche Werte uvm. erzeugt werden. Mit diese können dann die Tests bearbeitet werden. Dadurch ist es möglich, eine Vielzahl von Tests durchzuführen, ohne sich im Vorhein viele Gedanke darüber machen zu müssen. Weiterhin bietet diese Art von Tests in Clojure eine sehr übersichtliche Ausgabe, sollten die Tests fehlschlagen. Man sieht genau, welche Tests jeweils das Problem erzeugen. Vor allem interessant ist die Berechnung des Minimums, bei dem der Test nach wie vor fehlschlägt. Das heißt, dass man sehr genau sehen kann, welches Teilbereich das Problem erzeugt und kann diesen damit effizient beheben. Für diese Tests sind Monotonie und Persistenz sehr wichtig. Um dies zu beweisen, zeigt Jens Bendisposto die Arbeitsweise der Funktion apply. Mit ihr können Funktionen auf beispielsweise Listen und Vektoren angewendet werden. Dies gestattet die Benutzung von so genannten „Quick-Checks“. Hier werden die Generatoren dazu genutzt, eine Vielzahl von automatischen Tests zu erzeugen. Hier kann man die Monotonie der Werte jeweils sehr schön sehen. Bleibt noch, die Persistenz zu beweisen. Um die Performance zu verbessern, wird das Schlüsselwort transient benutzt. Dieses Kontrukt ist jedoch veraltet und wird heutzutage kaum noch eingesetzt. Allerdings wird es hier noch benötigt, um die Persistenz zu zeigen. Wenn eine Funktion mit und ohne diese Optimierung das gleiche Ergebnis liefert, sind die Daten immer korrekt.

Zum Abschluss zeigt Jens noch ein bemerkenswertes Beispiel eines Bugs, welcher mit Hilfe des generativen Testens gefunden worden ist. Zwei Berechnung liefern das selbe Ergebnis. Dennoch schlagen die Tests fehl. Wie kommt es? Durch generatives Testen wurde dieser Fehler zufällig gefunden und konnte behoben werden. Diesen per Hand zu finden, erklärt Jens sowohl mit Clojure, als auch mit Java für unmöglich. Findet allgemeine Zustimmung.

Das Fazit des Tages: Generatives Testen in Kombination mit Unit-Tests ist eine sinnvollen und sehr nützliche Sache!