05. Nov 2013

Einbindung von PL/SQL Code in den Continuous Integration Prozess

Teil 2 der Artikelserie welche im März 2013 begonnen wurde. Anhand einer Oracle Datenbank wird vorgestellt, wie sich analog zu JUnit-Tests PL/SQL Code im Rahmen der CI automatisiert testen lässt.
1060 x 710 Schubert Stefan

Author

Stefan Schubert

2024 Blogbeitrag Accso

Einleitung

Dies ist der versprochene zweite Teil der Artikelserie, welche ich im März 2013 begonnen hatte. Im ersten Teil ging es im wesentlichen um die Einbindung von DDL und DML Skripte in den CI-Prozess. In diesem Artikel stelle ich anhand einer Oracle Datenbank vor, wie sich analog zu JUnit-Tests PL/SQL Code im Rahmen der CI automatisiert testen lässt.

In vielen Java-Projekten erlebe ich immer wieder, dass der Bereich Datenbank bzgl. Tests eher stiefmütterlich behandelt wird. Bei einer guten Projektinfrastruktur findet man zwar eine CI Umgebung (bamboo/jenkins) vor, in der der aktuelle Entwicklungsstand automatisiert “vertestet” wird und den Entwicklern als Fangnetz dient, jedoch deckt diese meist nur den Java-Teil der Applikation ab. In vielen Fällen wird zudem auf das DB-Backend in der CI Umgebung ganz verzichtet, dabei bedeutet CI doch gerade contineous integration. Der Verzicht hat häufig den Grund, dass man die Einbindung des DB-Backends eher als langläufige Integrationstests ansieht und man lieber schneller Feedback von der CI Umgebung haben möchte, aber auch dafür gibt es Lösungen (z.B. mit einer H2 in memory DB, die den gewünschten DB-Dialekt emuliert).

Teil II: Automatisiertes Testen von PL/SQL Code

Vorab noch mal ein paar grundsätzliche Überlegungen. Während bei der Java-Entwicklung Code häufig von mehreren Entwicklern bearbeitet wird, und es in der Java-Welt daher umso wichtiger ist schnell Feedback über entsprechende JUnit-Tests zu bekommen, wird in der Datenbank-Entwicklung nicht selten der Code von einer Person betreut. Dies erkennt man häufig auch schon am Projektsetup, z.B. bei fünf Java Entwickler zu einem DB-Entwickler. Man könnte also auch argumentieren, die Investition in die CI Einbindung lohnt sich hier nicht, da es sowieso ein Experte ist, der den Code kontrolliert und sich somit weniger Fehler einschleichen können. Diese ist meiner Erfahrung nach eine zu kurzsichtige Auffassung. Was ist, wenn der Kollege in der letzten Projektphase ungeplant wegfällt (Urlaub, Lottogewinn etc..) und der Code übernommen werden muss, oder das Projekt in einer späteren Wartungsphase von einem ganz anderen Team betreut wird? Es lohnt sich bei der Entscheidung für ein solches Vorgehen eher mittelfristig bis langfristig zu denken. Durch das Bereitstellen von Tests für die Datenbank-Entwicklung gewinnt man:

  • das frühzeitige Erkennen von Fehlern und damit
  • mehr Qualität in der Entwicklung
  • Regressionstest-Fähigkeit des Backend-Codes in der Wartungsphase
  • und die Möglichkeit für den DB-Entwickler Test-Driven vorzugehen (TDD)

Wahl der Waffen

Beschäftigt man sich mit der Recherche nach entsprechender Testunterstützung für PL/SQL-Entwickler so landet man in der Regel bei folgenden Möglichkeiten:

20131105 plsql

Links: [1] = http://www.oracle.com / [2] = http://www.quest.com / [3] = http://dbunit.sourceforge.net/ / [4] = / [5] = http://utplsql.sourceforge.net/

Obige Aufzählung erhebt kein Anspruch auf Vollständigkeit, im Rahmen des Artikels belasse ich es jedoch bei aufgeführten Auswahl.
Unter der Prämisse, dass ich das gesamte Software-Projekt in einer CI-Umgebung wie Jenkins oder Bamboo sie zur Verfügung stellen möchte, fällt die Wahl durch ein Ausschlussverfahren auf utPLSQL, da:

  • Die UI gestützten Systeme die Einbindung in die Testautomatisierung nicht unterstützen.
  • DBUnit vom Datenbank Entwickler erfordern würde, zusätzlich etwas Java und eine Java-IDE zu erlernen. (Allerdings, würde ich diesem Framework den Vorzug geben, wenn die DB-Engineers über die Zusatzqualifikation verfügen).
  • PLUnit letztlich nur ein SubSet von utPLSQL darstellt, das Projekt brach liegt, und man hier nicht mit Unterstützung rechnen kann.

Setup von utPLSQL

Nach dem Download und Entpacken der aktuellen Version (22) von der Projekt-Seite folgt man zunächst der Anleitung und erzeugt einen eigenen DB-Benutzter der die Framework-Tabellen aufnimmt:

connect system/manager
create user utp identified by utp default tablespace
  users temporary tablespace temp;

grant create session, create table, create procedure,
  create sequence, create view, create public synonym,
  drop public synonym to utp;

alter user utp quota unlimited on users;

[stextbox id="info"]

benötigte UTL_FILE Konfiguration

Da die Packages des utPLSQL Framework UTL_FILE verwenden, um ggf. Testpakages einzulesen und vor der Ausführung erneut zu kompilieren, benötigt der User utp die entsprechenden Ausführungsrechte. Dies kann der DBA sicherstellen (init.ora benötigt die Angabe eines utl_file_dir und als sys: grant execute on utl_file to utp).
[/stextbox]
Anschließend verbindet man sich mit dem eben erzeugten User (utp) und führt das Installationsscripts des Frameworks aus:

@ut_i_do install

Damit ist utPLSQL auch schon einsatzbereit. Als nächstes bauen wir unser Beispiel Szenario auf:

Beispiel-Testszenario

Nun zu einem konkreten Testbeispiel: Eine Kundendatenbank in einer heterogenen Anwendungslandschaft, d.h. die Datenbank wird von verschiedenen Applikationen verschiedener Technologien gemeinsam genutzt. Ein Teil der Businesslogik des Kundensystems wurde im Persistenz-Layer in Form von PL/SQL Code abgebildet. Da Datenqualität für den Kunden enorm wichtig ist, wird z.B. auch die Prüfung ob ein Kundendatum eine gültige E-Mail-Adresse enthält durch das Backend abgesichert. Anwendungen, welche es versäumen die E-Mail-Adresse bei der Eingabe korrekt zu validieren sollen dies mit einer DB-Exception quittiert bekommen (Der Einfachheit halber prüfen wir für dieses Beispiel nur auf das Vorhandensein des Klammeraffen @).

Die nachfolgenden Skripte sorgen für die entsprechende Infrastruktur unseres Beispiels:

Anlegen des Applikations Users

connect system/Manager
create user crm identified by crm default tablespace
  users temporary tablespace temp;

grant create session, create table, create procedure,
  create sequence, create view, create public synonym,
  drop public synonym to crm;

alter user crm quota unlimited on users;

GRANT EXECUTE ANY PROCEDURE TO "CRM" ;

Da ich es bevorzuge, das Framework in einen eigenen Schema zu installieren, benötigt der User des Schemas, in dem sich die Unit Tests und die zu testenden Module befinden Ausführungsrechte. In Ermangelung der Möglichkeit any grants auf ein Schema zu vergeben, vergebe ich hier any grants auf die gesamte DB. Wer das nicht möchte, muss diese einzeln vergeben, oder das utPLSQL Framework direkt in das zu testende „Schema" injizieren.

PL/SQL Beispiel

create or replace
PROCEDURE ADD_CUSTOMER_PRC
(
  P_NAME IN VARCHAR2
, P_EMAIL IN VARCHAR2
) AS
BEGIN
  -- demo check logic
  IF (instr(p_email, '@') < 1) THEN
    raise_application_error(-20000, 'no valid email adress');
  END IF;

   -- Perform some real business logic

END ADD_CUSTOMER_PRC;


Testausführung mittels utPLSQL

Als nächstes schreibt der DB-Entwickler das Äquivalent zu einer JUnit-Testklasse, nur mit dem Unterschied, dass er im gewohnten PL/SQL Code bleibt. Das Package legen wir im gleichen Schema ab, wo auch das zu testende Objekt residiert – hier also im crm-Schema:

Package Body der Testklasse

CREATE OR REPLACE
PACKAGE BODY UT_ADD_CONSUMER_PRC AS

  PROCEDURE ut_setup AS
  BEGIN
    /* TODO Implementierung */
    NULL;
  END ut_setup;

 PROCEDURE ut_teardown AS
  BEGIN
    /* TODO Implementierung */
    NULL;
  END ut_teardown;

  PROCEDURE ut_add_customer_validation AS
  v_result NUMBER := 0;
  BEGIN
    -- Invalid Email should lead to exception
    BEGIN
      add_customer_prc(p_name => 'Mustermann',
                       p_email => 'No.Valid.Mail.Adress');
    EXCEPTION
     WHEN OTHERS THEN
       v_result := 1;
    END;
    UTP.utassert.eq('Expected Exception',v_result,1);

  END ut_add_customer_validation;
END UT_ADD_CONSUMER_PRC;

Schön zu sehen ist, dass das Framework hier analog zu JUnit set-up

und tear-down Callbacks anbietet. Die API verfügt ebenfalls über

umfangreiche asserts, für den Artikel greifen wir auf einen einzigen

equal zurück.

[stextbox id="info"]

Achtung Fallstrick

Das utPLSQL Framework hängt bei der Testausführung zwar ein „ut_" prefix dran, prüft aber, ob es das Objekt ohne Präfix im data dictionary gibt. Das bedeutet die Testklasse muss (bis auf dem Präfix) genauso heißen, wie das zu testende Objekt.
[/stextbox]

So vorbereitet können wir nun aus dem crm-Schema heraus den Testfall aufrufen:

Testaufruf

exec utp.utplsql.test('ADD_CUSTOMER_PRC', recompile_in => FALSE);

Folgender Screenshot zeigt den erfolgreichen Beispiellauf (mit set serveroutput on):

20130322 ut PLSQL Success

Einbindung der utPLSQL Tests in die CI

Der letzte Schritt besteht nun in der Automatisierung. Hierzu bedienen wir uns dem utPLSQL Maven Plugin, welches in der Lage ist die utPLSQL Tests auszuführen. Folgende pom.xml zeigt das hierzu nötige setup:

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                             http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>utPLSQL_Example</groupId>
    <artifactId>utPLSQL_Example</artifactId>
    <version>1.0-SNAPSHOT</version>

    <name>Sample Maven utplsq Test Setup</name>

    <properties>
        <oracle.version>11.2.0</oracle.version>
        <db.host>localhost</db.host>
        <db.instance>XE</db.instance>
        <db.username>crm</db.username>
        <db.password>crm</db.password>
    </properties>

    <build>
        <plugins>
            <!--
               TODO: Install Test-Packages with sql-maven-plugin
            -->
            <plugin>
                <groupId>com.theserverlabs.maven.utplsql</groupId>
                <artifactId>maven-utplsql-plugin</artifactId>
                <version>1.31</version>

                <dependencies>
                    <dependency>
                        <groupId>com.oracle</groupId>
                        <artifactId>ojdbc6</artifactId>
                        <version>${oracle.version}</version>
                    </dependency>
                </dependencies>

                <executions>
                    <execution>
                        <id>run-plsql-test-packages</id>
                        <phase>test</phase>
                        <goals>
                            <goal>execute</goal>
                        </goals>
                    </execution>
                </executions>

                <configuration>
                    <driver>oracle.jdbc.driver.OracleDriver</driver>
                    <url>jdbc:oracle:thin:@${db.host}:1521:${db.instance}</url>
                    <username>${db.username}</username>
                    <password>${db.password}</password>
                    <packages>
                        <!-- Anmelden von Testpackages -->
                        <param>ADD_CUSTOMER_PRC</param>
                    </packages>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

[stextbox id="info"]
Installation der benötigten Maven-Artefakte

Das Maven-Artefakt für utPLSQL gibt es derzeit nicht auf den öffentlichen Repository-Servern. Zur Installation ins lokale repository lädt man es von der Plugin-Homepage http://code.google.com/p/maven-utplsql-plugin/downloads/list herunter und installiert es z.B. wie folgt ins lokale Repository:

mvn install:install-file -Dfile=maven-utplsql-plugin-1.31.jar
            -DpomFile=maven-utplsql-plugin-1.31-pom.xml

Ähnlich wird mit dem ojdbc verfahren. Hierzu stellt man das Artefakt einer lokalen oracleXE Installation wie folgt dem lokalen Repository zur Verfügung:

<em>C:devoraclexeapporacleproduct11.2.0serverjdbclib>mvn
         install:install-file -Dfile=ojdbc6.jar -DgroupId=com.oracle
         -DartifactId=ojdbc6 -Dversion=11.2.0 -Dpackaging=jar</em>

[/stextbox]

Anschließend lassen sich die Tests über maven ausführen, hier ein Ausschnitt aus dem Testlauf:

mvn test

------------------------------------
TESTS
------------------------------------
utplsql:Testing package ADD_CUSTOMER_PRC
Successes: 1, Failures: 0
Results:
Tests run: 1, Failures: 0
[INFO] ADD_CUSTOMER_PRC, failed=0, passed=1, total=1
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.142s
[INFO] Finished at: Sun Oct 06 21:35:30 CEST 2013
[INFO] Final Memory: 5M/120M
[INFO] ------------------------------------------------------------------------

Damit haben wir alles was wir brauchen, um die Tests über die CI Umgebung (jenkins/bamboo) automatisiert auszuführen.

In einem vollständigen Setup würden wir die PL/SQL Artefakte über den maven build aus SVN bzw. GIT ziehen, in der CI Datenbank kompileren und die Tests ausführen. Somit ist gewährleistet, dass auch immer der aktuelle Entwicklungsstand gestestet wird.

Fazit

Über diese Art und Weise lässt sich mit vertretbaren Aufwand die PL/SQL Entwicklung in den CI Prozess integrieren. Etwas schade an utPLSQL ist, dass das ursprüngliche User-Forum nicht mehr existiert. Aus Sicht des reinen DB-Entwicklers bietet es jedoch ausreichend Möglichkeiten entsprechende Tests für eine Automatisierung bereitzustellen. Verfügen die DB-Entwickler zusätzlich über Java-KnowHow, würde ich persönlich den Weg über DBUnit einschlagen, da das Handling bzgl. Schreiben der Testklassen hier etwas weniger Overhead hat und man nicht so sehr mit dem Rechtesetup innerhalb der Datenbank „kämpfen" muss.

Damit endet meine kleine DB-CI Reihe. Ich hoffe, der ein oder andere konnte etwas mitnehmen und ich wünsche viel Erfolg und Spaß im Einsatz der Techniken.