Dienstag, 24. Februar 2015

Test-driven SQL

Die Migration

Eine Änderung des Datenmodells ist durchzuführen und es sind Daten aus der bestehenden Struktur in eine Neue zu überführen. Es gibt verschiedene Möglichkeiten eine Datenbank zu migrieren. Werkzeuge wie LiquiBase sollen die Arbeit erleichtern und helfen sogar dabei einen Bogen um SQL zu machen. Mir persönlich begegnet eine Migration mittels SQL Statements trotzdem immer wieder. Auch wenn eines der erwähnten Werkzeuge im Einsatz ist. 

Bei einer Migration mittels SQL gehe ich für gewöhnlich in folgenden Schritten vor:

1.) Erstellen der SELECT Anweisungen zur Identifizierung der Datensätze die zu migrieren sind.
2.) Erstellen der DDL SQL (falls notwendig).
3.) Erstellen der UPDATE oder INSERT Anweisungen zum Migrieren der selektierten Daten.

Bei diesem Vorgehen werden zur Selektion der Datenmengen typischer Weise Joins und Where-Klauseln verwendet, die den Ist-Zustand der Datenbank interpretieren (1). Nach erstellen der neuen Struktur (2), wird die eigentliche Migration durchgeführt (3). Wenn kein Fehler bei der Ausführung der Statements auftritt, ist die Migration erfolgreich durchgeführt. 

Ist das wirklich so? Ein erfolgreicher Durchlauf sagt doch nicht, dass die Daten aus fachlicher Sicht den richtigen Zustand angenommen haben. Man könnte nun den Migrationskripten vertrauen, schließlich wurden sie mit größter Sorgfalt erstellt. 

TDD

Doch wenn dieses Vorgehen richtig ist, warum vetrauen TDDler bei der Entwicklung von Software nicht einfach dem produktiven Code? Warum das Schreiben von Testfällen? Ist die Migration von Daten weniger kritisch, als das Schreiben einer Applikation?

Gehen wir einmal davon aus, beides muss den gleichen Kriterien der Fehlerfreiheit genügen. Führe ich als TDDler diesen Gedanken zu Ende, drängt sich mir ein testgetriebenes Vorgehen bei der Erstellung von Migrationsscripts auf. Wie würde ein solches Vorgehen wohl aussehen?

TDD und SQL

Nach TDD definiert man die Erwartung an die zu implementierende Funktion in einem Testfall. Dieser Testfall wird geschrieben, noch bevor die eigentliche Funktion realisiert wird. Bei einer Datenbankmigration müsste man zunächst das erwartete Ergebnis einer Migration in einem Test-SQL-Statement definieren. 

Ein Testfall unterscheidet sich für gewöhnlich von der Logik der zu testenden Fachlichkeit. Sie ist möglichst einfach oder gar nicht vorhanden. Schließlich möchte man die zu testende Fachlichkeit nicht ein weiteres Mal in dem Test programmieren. Das SELECT Statement des Tests darf demnach keine Statements aus der Migration wiederverwenden.

Ein Testfall verwendet zudem möglichst simple Daten, um den erwarteten Zustand zu definieren, den das System nach Ausführung der getesteten Funktion angenommen haben soll. Das spricht für ein SELECT Statement mit möglichst wenigen JOINS und wenigen Kriterien in der WHERE Klausel. 

Bei einem Unit Test erhält der Entwickler ein eindeutiges Feedback, ob der Test erfolgreich war oder fehlgeschlagen ist. Eine komplexe Ausgabe, muss von dem Entwickler interpretiert werden und ermöglicht somit eine Fehlinterpretation. Das Test-SQL Statement sollte ein ebenso eindeutiges Feedback liefern.

Man kann nun eine Test SQL so schreiben, dass es gleich in einem Rutsch die gesamte Migration auf Erfolg überprüft. Oder, man geht in möglichst kleinen Schritten vor und zerlegt die Migration. Diese kann dann von den Testfällen iterativ vorangetrieben werden.

Fazit

Aus den obigen Gedanken ergibt sich für mich folgendes Vorgehen...

1.) Erstellen der DDL SQL (falls notwendig)

In möglichst kleinen Schritten und Zyklen...

2.) Erstellen / erweitern der Test SQL.
3.) Erstellen / erweitern der UPDATE oder INSERT Anweisungen.
4.) Zurück zu Schritt 2.

Das Vorgehen kann also auch beim Erstellen einer Datenbankmigration funktionieren. Voraussetzung hierfür ist allerdings, dass es geeignete Datensätze in der Datenbank gibt. Ohne eine sinnvolle Ausgabe der Test SQL, ist kein test-getriebener Ansatz möglich.