Unit-Tests mit transienten Daten


Wenn man SQL für mehr als nur CURD verwendet, können Unit-Tests für komplexe Abfragen Sinn machen. Solche Tests erfordern jedoch genau definierte Testdaten, damit die getestete Abfrage das erwartete Ergebnis liefert.

Die Testdaten werden oft in sogenannten Set-up-Funktionen angelegt und nach dem Test in einer Tear-down-Funktion wieder gelöscht. Bei diesem Vorgehen können sich die Testfälle jedoch gegenseitig beeinflussen: zum einen, wenn Tests parallel ausgeführt werden, aber auch wenn wenn die Tear-down-Funktion nicht ordentlich aufräumt.

Beide Probleme können vermieden werden, wenn man die Tests mit transienten Testdaten durchführt, die nach dem Test automatisch wieder verschwinden.

Option 1: Offene Transaktionen

Die einfachste und vor allem flexibelste Möglichkeit ist es, das Set-up und den Test in einer offenen Transaktion durchzuführen, die im Tear-down durch Rollback zurückgenommen wir

Diese Methode bietet mehrere Vorteile:

  • Verhältnismäßig einfach umzusetzen

    Natürlich muss sichergestellt werden, dass Set-up, Test und Tear-down in derselben Datenbank-Session laufen. Weiters müssen ungewollte Commits unterbunden werden. Das betrifft vor allem Commits, die vom getesteten Code selbst ausgeführt werden. Diese könnte man notfalls durch eine Mock-Implementierung der Datenbankverbindung abfangen. Impliziten Commits kann man damit auch nicht verhindern (z. B. durch DDL-Anweisungen).

  • Komplette Abläufe können getestet werden

    Das einzige Limit für die Komplexität des Testfalles ist, dass man mit einer einzigen Transaktion auskommen muss.

  • Paralleles testen ist möglich

    Die Testdaten sind jeweils nur in der offenen Transaktion sichtbar0 und können daher andere Vorgänge in der Datenbank nicht beeinflussen. Constraints können jedoch Verzögerungen verursachen – wenn zum Beispiel zwei Testfälle denselben Primärschlüsselwert einfügen.

    Die Parallele Ausführung kann sogar helfe, falsch gewählte Transaktions-Isolations-Level zu finden.

  • Performancetests sind möglich

    Da die Testabfragen selbst unverändert ablaufen, können die Testfälle auch für Performanceanalysen und Stresstests herangezogen werden.

Option 2: with und values

Diese Methode kombiniert zwei Elemente: (1) die Tabellenzugriffe einer Abfrage werden durch gleichnamige with-Abfagen „gekapert“; (2) die with-Abfagen erzeugten die Testdaten mit der values-Klausel „aus dem nichts“.

WITH cart (product_id, qty) AS (
     VALUES (1, 2)
)
SELECT ...
  FROM cart
   ...

In diesem Beispiel wurde einer zu testenden Abfrage eine with-Klausel vorangestellt. Essenziell ist, dass der Tabellenname cart, auf den die Abfrage zugreift, in der with-Klausel überlagert wird. Diese Überlagerung funktioniert jedoch nicht, wenn der Tabellenzugriff schemaqualifiziert, oder durch einen View erfolgt (Achtung: Kompatibilität).

Wenn die zu testende Abfrage selbst eine with-Klausel hat – zum Beispiel weil es literatisches SQL ist –, kann man sie als Unterabfrage verwenden, und die Testdaten mit einer with-Klausel in der Hauptabfrage zur Verfügung stellen:

WITH cart (product_id, qty) AS (
     VALUES (1, 2)
)
SELECT *
  FROM (  WITH ...
        SELECT ...
          FROM cart
           ...
       ) test_query

Die Testdaten werden mit der values-Klausel erzeugt. Falls die Zieldatenbank values ohne insert nicht unterstützt, muss man auf die in select ohne from aufgezeigten konformen oder proprietären Alternativen zurückgreifen.

Die Geschwindigkeit ist der Hauptvorteil dieser Methode: Ein Datenbank-Aufruf kombiniert die Testdaten mit der Testabfrage. Die Testfälle können auch parallel ausgeführt werden, ohne sich gegenseitig zu beeinflussen – nicht einmal durch Wartezeiten auf Locks (lock contention).

Der schlagende Nachteil ist, dass diese Methode nur funktionale Tests einzelner Abfragen zulässt. Rückschlüsse auf nicht-funktionale Aspekte wie Performance sind nicht möglich. Auch die Umsetzung ist höchst ungewöhnlich: Dem SQL-String wird die with-Klausel vorangestellt oder die Test-Abfrage wird überhaupt als Unterabfrage verwendet.

Kompatibilität

Über den Autor

Foto von Markus Winand

Markus Winand lehrt effizientes SQL – inhouse und online. Er minimiert die Entwicklungszeit durch modernes SQL und optimiert die Laufzeit durch schlaue Indizierung – dazu hat er auch das Buch SQL Performance Explained veröffentlicht.

Sein Buch bei Amazon kaufen

Titelbild von „SQL Performance Explained“: Eichhörnchen läuft durchs Grass

Die Essenz: SQL-Tuning auf 200 Seiten

Bei Amazon kaufen
(Taschenbuch)

Taschenbuch und PDF auch auf Markus' Webseite erhältlich.

Holen Sie sich Markus

…für ein Training ins Büro.

Sein beliebtes Training stimmt Entwickler auf SQL Performance ein.

Erfahren Sie mehr»

Fußnoten

0

Vorausgesetzt der Transaktions-Isolations-Level ist nicht auf READ_UNCOMMITTED gesetzt.

„modern SQL“ von Markus Winand ist unter einer Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 Unported License lizenziert.
Impressum | Kontakt | KEINE GEWÄHR | Handelsmarken | Datenschutz | CC-BY-NC-ND 3.0 Lizenz