Wie man mit den Bausteinen von utPLSQL Tests erstellt

In den ersten beiden Blogbeiträgen haben wir erklärt, was utPLSQL ist und wie man dies installiert.

utPLSQL bietet die Möglichkeit mittels relativ geringen Aufwands Unit Tests in PLSQL zu erstellen. Dabei basiert utPLSQL auf PLSQL und orientiert sich zu dem an die Syntax bekannter Unit Test Frameworks wie JUnit. Ein erfahrener PLSQL-Entwickler dürfte somit schnell mit utPLSQL vertraut sein. Die komplette Test-Logik und die Konfiguration befinden sich nämlich in Packages und es werden keine zusätzlichen Konfigurationsdateien benötigt. Nachfolgend sollen die wichtigsten Bausteine von utPLSQL näher erläutert werden. Anhand von Beispielen wird gezeigt, wie man Tests erstellt und welche Möglichkeiten mit utPLSQL geboten werden.

Die Grundbausteine Suits und Tests

Wie bei vielen anderen Unit Test Frameworks auch, wird bei utPLSQL zwischen Testsuites und Tests unterschieden. Eine Testsuite beinhaltet die Tests und kann unter Umständen auch weitere Testsuites enthalten. Durch Verschachtelungen kann eine hierarchische Struktur zwischen den Testsuites aufgebaut werden. Zudem können Sie mit weiteren Eigenschaften versehen werden. So kann ein Name oder auch ein Pfad, der sogenannte Suitepath definiert werden.

Ein Test ist in utPLSQL eine Prozedur, welche die Testlogik enthält und sich in einer Testsuite befindet. Eine Suite kann mehrere Tests enthalten. So bietet es sich an, die Tests logisch zu gruppieren und entsprechenden Suites zu zuordnen. Jeder Test überprüft für gewöhnlich genau ein Verhalten, also zum Beispiel das Verhalten einer Funktion bei einer bestimmten Eingabe. Für diesen speziellen Fall wird eine Erwartungshaltung definiert, die dann mit dem tatsächlichen Ergebnis abgeglichen wird.

In utPLSQL findet die Zuteilung als Test oder Testsuite ausschließlich in der Package-Specification statt. Mittels einer sogenannten Annotation wird definiert, welches Package eine Testsuite ist und welche Prozedur einen Test darstellt.

Annotations

Die gerade erwähnten Annotations dienen nicht nur dazu Tests und Testsuites zu definieren. Es gibt noch einige weitere Annotations, die Packages oder Prozeduren eine Eigenschaft zuteilen. Wichtig ist dabei, dass Annotations ausschließlich in der Package-Specification hinterlegt werden. Befinden sie sich im Package-Body, werden sie ignoriert.

Grob können Annotations in zwei Gruppen unterteilt werden, Package-Annotations und Procedure-Annotations. Eine Procedure-Annotation definiert die Eigenschaft einer Prozedur. Die Annotation wird deswegen auch direkt über die Prozedur geschrieben. Die Package-Annotations findet man überall in der Package-Specification, außer direkt oberhalb von Prozeduren. Hier werden Eigenschaften definiert, welche für die komplette Testsuite gelten.

Eine Annotation ist erkennbar an folgender Syntax:

<Kommentarzeile><Prozentzeichen><Annotationname>(<optionaler Annotationtext>)

Beispiel: –%suite(Das ist eine Testsuite)

Hier sollte beachtet werden, dass alles, was sich zwischen der ersten und der letzten Klammer befindet, als Annotationstext interpretiert wird. Zudem sind Annotationen case-insensitive, allerdings sollten sie nach Möglichkeit in lower-case verwendet werden.

Die nachfolgende Tabelle an Annotationen stellt eine Auswahl an wichtigen und häufig verwendeten Annotationen dar. Eine komplette Liste der Annotationen ist auf der offiziellen Seite von utPLSQL in der Dokumentation zu finden.

NameNotationArtBeschreibung
Suite–%suite(<description>)PackageDefiniert ein Package als Testsuite. Wird eine Testsuite aufgerufen, werden alle hier enthaltenen Tests ausgeführt
Suitepath–%suitepath(<path>)PackageErmöglicht, eine hierarchische Struktur von Testsuits zu erstellen. Es wird ein Pfad angegeben, auf welcher hierarchischen Ebene sich die Suite befindet
Test–%test(<description>)ProzedurEine Prozedur wird als Test definiert. Nur in einer Testsuite möglich
Beforeall–%beforeall(<procedure>[,…])Package/ProzedurDefiniert Prozeduren, die in einer Suite vor allen Tests ausgeführt werden Auflistung der Prozeduren nur bei Package-Annotation
Beforeeach–%beforeeach(<procedure>[,…])Package/ProzedurDefiniert Prozeduren, die in einer Suite vor jedem Test ausgeführt werden Auflistung der Prozeduren nur bei Package-Annotation
Afterall–%afterall(<procedure>[,…])Package/ProzedurDefiniert Prozeduren, die in einer Suite nach allen Tests ausgeführt werden Auflistung der Prozeduren nur bei Package-Annotation
Aftereach–%aftereach(<procedure>[,…])Package/ProzedurDefiniert Prozeduren, die in einer Suite nach jedem Test ausgeführt werden Auflistung der Prozeduren nur bei Package-Annotation
Rollback–%rollbackProzedurFür gewöhnlich wird ein Savepoint vor jedem Test gesetzt, zu dem ein Rollback stattfindet. Ein Rollback kann auch manuell erfolgen, wenn hier der Wert ‚manual‘ gesetzt wird

Beispiel:

Listing 1
Listing 1

In diesem Beispiel sähe die Ausführungsreihenfolge dann folgendermaßen aus:

Initial_config – test_abc – finish_test – analyse – test_xyz – finish_test – analyse – clean_complete

Eine Testsuite kann somit nicht nur Tests beinhalten, sondern unter anderem auch Prozeduren, die Vorarbeiten oder Nacharbeiten beinhalten. So kann in einer Testsuite eine relativ komplexe Struktur entstehen, welche den Ablauf der einzelnen Schritte beschreibt. Dabei sollten einige Dinge beachtet werden, die sonst zu unerwünschten Nebeneffekten führen können:

Wirft ein Test in einer Testsuite eine Exception, so wird dieser Test abgebrochen und erhält den Status ‚Error‘. Alle nachfolgenden Tests werden aber weiterhin ausgeführt. Verursacht hingegen eine Prozedur eine Exception, die als beforeall definiert wurde, so werden alle weiteren beforeall-Prozeduren nicht mehr ausgeführt. Auch die Tests in dieser Suite werden nicht ausgeführt und bekommen sofort den Status ‚failed‘. Anschließend werden noch die afterall-Prozeduren ausgeführt.

Auch wenn eine beforeeach-Prozedur eine Exception wirft, wird der dazugehörige Test nicht ausgeführt und bekommt den Status ‚Error‘. Alle weiteren Tests laufen danach wie gewohnt weiter. Das gleiche gilt auch für die aftereach-Prozeduren.

Eine Exception in einer afterall-Prozedur hat keine Auswirkung. Es wird lediglich eine Warnung ausgegeben.

Eine Prozedur kann immer nur eine Eigenschaft erhalten, so kann sie zum Beispiel nicht Test und eine beforeall-Prozedur gleichzeitig sein.

Bei beforeall, beforeeach, afterall und aftereach gilt, dass die Reihenfolge, wie sie in dem Package aufgelistet werden auch über die Ausführungsreihenfolge entscheidet. Bei den Tests hingegen kann nicht garantiert werden, dass sie auch in der Reihenfolge ausgeführt werden, in der sie in der Testsuite aufgelistet sind.

Da Testsuites unter Umständen sehr groß werden können, wird relativ viel Zeit benötigt, um alle Annotations zu analysieren. Dies kann dazu führen, dass die Tests langsam werden. Es gibt allerdings eine integrierte Funktion in utPLSQL, die dies automatisch verhindert. In einem Cache werden die Informationen zu den Annotations gesammelt. Eine Tabelle beinhaltet eine Übersicht zu den Annotations. Nur wenn sich eine Annotation ändert oder eine Annotation neu hinzugefügt wird, findet hier eine Aktualisierung statt.

Die Struktur von Tests

Nachdem der Aufbau von Testsuits detailliert erläutert wurde, soll der Aufbau der Tests genauer betrachtet werden. Die Logik der Tests befindet sich in dem Body eines Packages, welches als Testsuite definiert wurde. Genauer gesagt befindet sich die Testlogik eines Tests in der jeweiligen Prozedur. Hierbei gilt, dass ein Test, also eine Prozedur genau einen Testfall abdecken sollte.

Wurde eine Prozedur mittels entsprechender Annotation als Test definiert, kann hier die Testlogik definiert werden. Ein Test führt für gewöhnlich eine Funktion mit vordefinierten Parametern aus. Anschließend wird das Ergebnis mit einer Erwartungshaltung verglichen und anschließend ein Report verfasst, welcher das Testergebnis beinhaltet.

Ein wichtiger Baustein sind hier die Expectations. Sie werden eingesetzt, um die Ausgabe einer Operation mit einem erwarteten Wert zu vergleichen. Hierbei gibt es eine große Auswahl an Expectations, die unterschiedliche Vergleichsoperationen bieten. Es wird hier zwischen unären und binären Expectations unterschieden. Binäre Expectations vergleichen die Ausgabe des Tests mit einem vordefinierten Wert oder einer vordefinierten Menge, dem sogenannten Matcher. Bei einer unären Expectation findet die Überprüfung nur auf dem Ergebnis mit entsprechender Expectation statt.

Wenn ein Matcher null ist, so ergeben sowohl der positive Vergleich als auch der negative Vergleich mit not den Status ‚failure‘. Zu jeder Expectation existiert zu dem auch der negative Fall. Dieser wird mit einem vorangestellten not gebildet.

Eine Liste mit allen Expectations und deren negatives Pendant:

ExpectationBeschreibungBeispiel
To_be_between Not_to_be_betweenÜberprüft, ob der Wert zwischen den beiden erwarteten Werten liegtUt.expect(4).to_be_between(1,10)
To_be_empty Not_to_be_emptyÜberprüft, ob die übergebene Menge leer istUt.expect(l_blob()).to_be_empty()
To_be_false Not_to_be_falseÜberprüft, ob das Ergebnis ‚false‘ zurück liefertUt.expect((1=0).to_be_false()
To_be_greater_or_equal Not_to_be_greater_or_equalÜberprüft, ob der Wert größer oder gleich dem erwarteten Wert istUt.expect(10).to_be_greater_or_equal(9)
To_be_greater_than Not_to_be_greater_thanÜberprüft, ob der Wert größer als ein erwarteter Wert istUt.expect(20) .to_be_greater_than(10)
To_be_less_or_equal Not_to_be_less_or_equalÜberprüft, ob der Wert kleiner oder gleich dem erwarteten Wert istUt.expect(5).to_be_less_or_equal(10)
To_be_less_than Not_to_be_less_thanÜberprüft, ob der Wert kleiner ist, als der erwartete WertUt.expect(10).to_be_less_than(20)
To_be_like Not_to_be_likeVergleich des Werts mit einem erwarteten Ausdruck (wie der LIKE-Operator)Ut.expect(‚abcde‘).to_be_like(%cde‘)
To_be_not_null Not_to_be_not_nullÜberprüft, ob der Wert nicht null istUt.expect(‚abc‘).to_be_not_null()
To_be_null Not_to_be_nullÜberprüft, ob der Wert null istUt.expect(null).to_be_null()
To_be_true Not_to_be_trueÜberprüft, ob das Ergebnis true zurück gibtUt.expect((1=1)).to_be_true()
To_have_count Not_to_have_countÜberprüft, ob ein Counter einen erwarteten Wert hatUt.expect(lcursor).to_have_count(3)
To_match Not_to_matchÜberprüft, ob der Wert mit dem erwarteten Regexp übereinstimmtUt.expect(‚abc12de‘).to_match(‚[a-z]{3}d{2}[a-z]{2}‘)
To_equal Not_to_equalÜberprüft, ob der Wert identisch ist, mit dem, was erwartet wurde (auch Datentyp)Ut.expect(1).to_be_equal(1)
To_contain Not_to_containÜberprüft, ob der Wert in die definierte Menge passtUt.expect(3).to_contain(1,2,3,4,5)

Beispiel:

Listing 2
Listing 2

Zusammenfassen ist zu sagen, dass es sich durchaus lohnt, etwas mehr Zeit zu investieren, um seinen Code mit utPLSQL zu testen. Man kann somit viele Fälle abdecken und sich so sicher sein, dass die programmierte Funktion das tut, wofür sie vorgesehen war. Der Aufwand der Installation und das Aneignen der Syntax hält sich in Grenzen, so dass man schnell eine Testumgebung mit entsprechenden Tests in utPLSQ erstellen kann. Trotz der Einfachheit bietet utPLSQL eine große Auswahl an Expectations und Annotations, so dass man seine Tests vielseitig aufbauen kann. Für jemanden, der in PL/SQL entwickelt und diesen Code gerne testen möchte, ist utPLSQL also eine gute Möglichkeit, schnelle Ergebnisse zu erzielen.


Quellen:


utPLSQL – Blogserie Teil 1:

utPLSQL – Blogserie Teil 2:

Tags:

Keine Kommentare vorhanden.

    Schreibe einen Kommentar

    Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.