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.

Name Notation Art Beschreibung
Suite –%suite(<description>) Package Definiert ein Package als Testsuite. Wird eine Testsuite aufgerufen, werden alle hier enthaltenen Tests ausgeführt
Suitepath –%suitepath(<path>) Package Ermöglicht, eine hierarchische Struktur von Testsuits zu erstellen. Es wird ein Pfad angegeben, auf welcher hierarchischen Ebene sich die Suite befindet
Test –%test(<description>) Prozedur Eine Prozedur wird als Test definiert. Nur in einer Testsuite möglich
Beforeall –%beforeall(<procedure>[,…]) Package/Prozedur Definiert Prozeduren, die in einer Suite vor allen Tests ausgeführt werden Auflistung der Prozeduren nur bei Package-Annotation
Beforeeach –%beforeeach(<procedure>[,…]) Package/Prozedur Definiert Prozeduren, die in einer Suite vor jedem Test ausgeführt werden Auflistung der Prozeduren nur bei Package-Annotation
Afterall –%afterall(<procedure>[,…]) Package/Prozedur Definiert Prozeduren, die in einer Suite nach allen Tests ausgeführt werden Auflistung der Prozeduren nur bei Package-Annotation
Aftereach –%aftereach(<procedure>[,…]) Package/Prozedur Definiert Prozeduren, die in einer Suite nach jedem Test ausgeführt werden Auflistung der Prozeduren nur bei Package-Annotation
Rollback –%rollback Prozedur Fü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:

Expectation Beschreibung Beispiel
To_be_between Not_to_be_between Überprüft, ob der Wert zwischen den beiden erwarteten Werten liegt Ut.expect(4).to_be_between(1,10)
To_be_empty Not_to_be_empty Überprüft, ob die übergebene Menge leer ist Ut.expect(l_blob()).to_be_empty()
To_be_false Not_to_be_false Überprüft, ob das Ergebnis ‚false‘ zurück liefert Ut.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 ist Ut.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 ist Ut.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 ist Ut.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 Wert Ut.expect(10).to_be_less_than(20)
To_be_like Not_to_be_like Vergleich 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 ist Ut.expect(‚abc‘).to_be_not_null()
To_be_null Not_to_be_null Überprüft, ob der Wert null ist Ut.expect(null).to_be_null()
To_be_true Not_to_be_true Überprüft, ob das Ergebnis true zurück gibt Ut.expect((1=1)).to_be_true()
To_have_count Not_to_have_count Überprüft, ob ein Counter einen erwarteten Wert hat Ut.expect(lcursor).to_have_count(3)
To_match Not_to_match Überprüft, ob der Wert mit dem erwarteten Regexp übereinstimmt Ut.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 passt Ut.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:

https://www.mt-itsolutions.com/blog/know-how/softwareentwicklung/utplsql-eine-einleitung/

utPLSQL – Blogserie Teil 2:

https://www.mt-itsolutions.com/blog/know-how/softwareentwicklung/utplsql-die-installation/

Jetzt teilen auf:

Jetzt kommentieren