Blog

Unit-Testing in Oracle PL-SQL: Warum & wie realisiert man das?

03 Feb, 2016
Xebia Background Header Wave

Gemäss SwissQ Trends & Benchmarks gehört Oracle PL/SQL weiterhin zu den 10 meist verwendeten Programmiersprachen in der Schweiz und wird vor allem noch im Finanzsektor verwendet. Dabei handelt es sich oft um über die Jahre recht gewachsene Anwendungen. Unit Testing bei Datenbanken, speziell in dem schon etwas in die Jahre gekommenen PL/SQL, ist im Vergleich zu neueren Technologien wie Java schwierig umzusetzen, aufgrund von z.B. Tabellen, Triggern und Constraints. Da Datenbanken viel mit Datenmanipulation zu tun haben, besteht zudem meist ein grosser Aufwand im Setup der Testdaten um eine gute Testabdeckung zu erreichen.

Was ist ein Unit Test?

Im Grunde beschreibt das Wort bereits was gemeint ist. Mit Unit-Test testet man eine Unit. Die Frage ist meistens, was ist eine Unit. Im Unit-Test selber ist es die kleinste als Einheit betrachtete und testbare Komponente. In der Software Entwicklung ist dies z.B. eine Methode (Aktion) in einer Klasse. Nun wird aber nicht 1 Test je Methode geschrieben. Als Ansatz geht man von der Funktionalität aus, die getestet werden muss, und zwar durch einen Testfall oder mehreren Testfällen. Das kann bei einer Methode bedeuten, dass sie ein oder mehrere Funktionalitäten beinhaltet und diese entsprechend durch mehrere Testfälle abgedeckt wird. Hier habe ich gleich Abdeckung genannt, was im Fachjargon auch als Test Coverage bezeichnet wird. Dieses und weitere Themen zum Unit Test sollen aber nicht Inhalt dieses Beitrags werden, sondern das Thema Unit Test und PL/SQL. Besonders hervorzuheben ist, das Unit Tests isoliert ablaufen sollen, ohne dass sie andere Testfällen beeinflussen. Jeder Testfall sollte für sich selbst lauffähig sein, ohne eine Reihenfolge oder Abhängigkeit zu anderen Testfällen zu haben. Wie kann dies nun im Datenbankumfeld realisiert werden?
UnitTest

Probleme bei Unit-Test mit Funktionalität in der Datenbank

Der Ansatz von Unit Test ist auch in PL/SQL anwendbar, nur muss an bestimmte Sachen etwas anders herangegangen werden, um diese entsprechend umzusetzen. Unit Test bei Datenbanken wird erst relevant, wenn auch Funktionalität und Geschäftsprozess (Logik) dort hinterlegt wird. Ansonsten würde man die Datenbank selbst testen, was aber bereits vom Hersteller hätte durchgeführt werden sollen. Der Unit Test kann durch Methoden und Objekten erfolgen, bei PL/SQL speziell durch Functions, Procedures, Types und Packages. Die Problematik für Unit Test mit PL/SQL ist zum einen die bereits recht veraltete Technologie selbst, da sie eher prozedural anstatt objektorientiert ist. Zum anderen sind es die Eigenarten von Oracle in Zusammenhang mit deren der Entwickler, die recht komplexe und riesige Konstrukte entstehen lassen. Oracle PL/SQL ist weiterhin noch viel verbreitet und in Verwendung, z.B. bei Bankensoftware. Dabei ist der Produktcode enorm gewachsen, wenn nicht sogar zu sagen „gewuchert“. So entstehen z.B. Konstrukte mit mehreren tausend Zeilen von Code, die mit Datenbank Abfragen bestückt sind und mehrere Funktionalitäten in einer Komponente enthalten. Diese Komponente dann mit Unit Test zu testen wird eine riesen Herausforderung. Wie man das bewältigen kann, wird der Blog mit einer Variante beschreiben.

Datenbank Elemente und Abhängigkeiten

Abhängigkeiten sind das A und O für Unit Tests. Denn Unit Tests, wie zu Beginn bereits erwähnt, sollten isoliert stattfinden, so dass nur die gewählte Komponente getestet wird und nicht mitsamt deren Abhängigkeiten. Ein Package im PL/SQL z.B. hat die Möglichkeit auf verschiedene Tabellen zuzugreifen, Oracle- und Benutzer-Typen zu verwenden, andere Packages und deren Komponenten aufzurufen und natürlich auch die Methoden in der Datenbank selbst. Durch diese Verbindung zwischen dem Testobjekt (die Komponente, die getestet werden soll) und die Komponenten (die aufgerufen werden), besteht keine Kontrolle über den Datenfluss (z.B. Rückgabewerte). Bei einem Unit Test, wie auch bei anderen Testvarianten, sollte stets bei jedem Aufruf des Testfalls das Erwartete erfolgen. Um das zu gewährleisten, müssen die abhängigen Komponenten simuliert bzw. gemockt werden. So kann der Datenfluss zu und von den umliegenden Komponenten zum Testobjekt definiert gesetzt bzw. kontrolliert werden.

Mocking der Abhängigkeiten

Für das Mocking von abhängigen Komponenten gibt es in der Regel Mock Frameworks, welche z.B. unter der objektorientierten Programmiersprache JAVA bereits vorhanden sind (Junit) und einfach erweitert werden können. Dies ist auch dort einfacher zu realisieren, als unter PL/SQL, wegen der vorgängig erwähnten Eigenheiten.. Der nur bedingt objektorientierte Ansatz, die Verwendung von Tabellen und die speziellen Mechanismen unter Oracle, sind einige Punkte, die zu erwähnen sind. Dazu zählen z.B. PL/SQL Abfragen im Code selbst, die Verwendung von verschiedenen Schemata und die Verwendung einer doch recht komplizierten Ablage der Metainformation über die Datenbankobjekte.
Mock

Testframework Variante in PL/SQL – utPLSQL

Für den Unit Test in PL/SQL gibt es nur wenige Testframeworks. Für Unit Tests ist es Good Practise mit der Technologie zu arbeiten, in der auch der Produktiv-Code geschrieben ist. Was sich bisher bewährt hat ist utPLSQL von Steven Feuerstein. Es baut auf den xUnit Architektur auf und ist selbst in PL/SQL geschrieben. Es bietet Packages an, die beim Unit Test unterstützen sollen, u.a. Assert Methoden, für den Vergleich des Resultats zum erwarteten Wert, wie auch Objekte für das Testfall-Handling, z.B. TestSuite und TestPackage. Die Testfälle werden mit gleichem Namen wie das Testobjekt abgelegt, nur dass ein Präfix vorangestellt wird.

Mocking Tool

Für das Auflösen der Abhängigkeiten habe ich bisher kein brauchbares Mock Framework gefunden, weshalb ein eigener Ansatz gewählt worden ist. Zwei Varianten bieten sich an, zum einen anhand der Oracle Metadaten Mockobjekte zu erstellen und zum anderen kann ein SQLParser verwendet werden, der die Sourcefiles einliest und daraus Mockobjekte erstellen kann. Mit der 2. Variante ist die Abhängigkeit von Oracle geringer, aber es ist ein komplexeres Thema, da viele Konstellationen im PLSQL Code selber berücksichtig werden müssen.

Einfacher Mock – von Oracle Metadata

Wie zuvor bereits erwähnt, werden die Oracle Metadaten verwendet, wie z.B. die Informationen aus der Sicht (View) „ALL_TYPES“. Aus diesen Sichten können die Headerinformationen ausgelesen und die entsprechenden Mockobjekte erstellt werden. Diese entsprechen dem Original an der Schnittstelle, sind aber im Inhalt der Methode (Body) quasi leer. Eine Besonderheit bei einem Mockobjekt ist, dass für jeden Rückgabewert eine Variable erstellt worden ist, welche dann vom Unit Test selbst definiert werden kann. So werden die Rückgabewerte kontrolliert.
OracleMetadata

Parsing Source Files

Anstatt die Metadaten aus einer bestehenden Datenbank zu lesen, ist es möglich mit einem Parser die benötigten Elemente aus den Quelldatein auszulesen und daraus die Mockobjekte zu erstellen. Durch den Parser wird u.a. die Validierung der Quelldateien und gleichzeitig auch das Extrahieren von Metainformationen ermöglicht. Ein weiterer Vorteil ist die Fähigkeit die Quelldateien einzulesen und bestimmte Ergänzungen einzufügen. Zum Beispiel werden, für das Testen von privaten Methoden in Oracle Packages, die Definitionen der Methoden im Header des Packages benötigt. Dies wird durch das Neuschreiben der Quelldatei mit zusätzlichen Informationen ermöglicht wird.
SQLParser

Unittest in CI

Die hier beschriebene Verwendung von Unit Test unterscheidet sich nicht vom Grundsatz des xUnit Frameworks, wie z.B. JUnit. Die Ausführung der Unit Tests kann manuell gestartet werden, sollte jedoch automatisiert werden. Für das automatische Starten, besonders in einem CI (Continous Integration) Prozess, bietet sich die Verwendung eines CI Servers an. Wie Jenkins, Atlassian Bamboo oder Teamserver, um nur einige zu nennen. Dabei wird bei jedem Code-Change der passende Unit Test gestartet und auf einem Dashboard das Resultat stetig angezeigt. Der Unit Test ist ein grosser Bestandteil bei der Testabdeckung und bei der Business Relevanz
Testpyramide

Reporting, Results, Dashboard

Das Reporting ist ein wichtiger Faktor beim CI Prozess mit Unit Tests. Die Automatisierungspyramide offenbart die grosse Menge und die Wichtigkeit an möglichen Unit Tests. Um der Masse Herr zu werden, sind Reports der Resultate in geeigneter Form unumgänglich. Diese sind nicht nur für Manager wichtig, sondern auch für das Developer Team selbst, da es in Kürze die Testresultate einsehen kann, um sogleich darauf reagieren zu können. Die Testresultate werden während des Unit Tests durch das Testframework erstellt und gegebenenfalls durch eine Erweiterung in verschiedene Formate umgewandelt, wie z.B. HTML. Die Resultat werden anschliessend an den CI Server weitergeleitet, wo sie zentral zugänglich sind.

Fazit

Unit Test mit PL/SQL ist machbar, wenn auch nur mit viel Eigenleistung. Wie bei Unit Test üblich, zahlt sich der Aufwand aber aus, der zu Beginn hineingesteckt wird, wenn es sorgfältig gemacht ist. Die Fehlerfindung wird durch die Unit Tests und der Mockobjekte entsprechend unterstützt und zeigt zugleich, dass bei Änderungen eines Codeteils die anderen Codeteile sich noch so verhalten wie erwartet.

Wir von SwissQ sind stets daran Automatisierung mit entsprechender Anforderungsanalyse zu realisieren. Dabei sind wir gemeinsam mit Agile, RE und Testing unterwegs, unsere Ziele zu setzen und zu erreichen. Weitere Mitdenker und Mitgestalter sind gerne willkommen. Mehr...

Questions?

Get in touch with us to learn more about the subject and related solutions