In einem Projekt auf der Arbeit verwenden wir gerade mal wieder Java (meh). Und Spring (uääh). Und Hibernate (uääääääääh). Und wir schreiben integration tests, immerhin. Das System verwendet am Ende eine Postgres-Datenbank, aber für die integration tests verwenden wir eine in-memory Datenbank, H2. Das ist keine gute Idee, und ich werde hier schreiben, warum.
Ich musste für ein Feature einen etwas komplizierteren unique constraint aufbauen. Einen, der nur griff, wenn eine bestimmte Spalte einen bestimmten Wert hatte. So ungefähr (Beispielcode, nicht mein tatsächlicher Code):
create unique index open_request_unique
on requests
where status = 'awaiting_approval';
Diese Datenbankänderung funktioniert auf Postgres super. Dummerweise wurden für die Integrationstests exakt die gleichen Migrationsscripte verwendet. Nun ist aber jeder SQL-Dialekt ein bisschen unterschiedlich. H2 hat zwar einen Kompatibilitätsmodus für Postgres, aber der ist alles andere als perfekt. Ich bekam einen Fehler, wenn ich die Tests laufen lies, weil die Migration nicht funktionierte.
Hier kommt Spring (uääh) ins Spiel. Anstatt mir nämlich direkt zu sagen, dass die Datenbankmigration kaputt ist, wälzt mir Spring Seiten über Seiten mir immer der gleichen Fehlermeldung über den Bildschirm, darüber noch mehr Seiten an völlig nutzlosen Stacktraces (nutzlos, weil sie nur Spring-Klassen enthalten und mit meinem Code nichts zu tun haben) und erst darüber ein paar SQL-Fehlermeldungen. Ich musste wirklich eine halbe Minute lang nach oben scrollen, bis ich den relevanten Teil erreicht habe. Aber warum einfach, wenn es auch kompliziert geht? Fuck you, Spring.
Die Kollegen auf Kundenseite waren abgeneigt, daran etwas zu ändern, also habe ich das Problem gelöst, indem ich statt dem conditional unique request ein zweite Tabelle angelegt habe, in die nur offene requests kamen, und dort dann einen regulären unique constraint genutzt.
Und es stellt sich heraus: Ich bin nicht der einzige, der dieses Problem hat. Jemand anderes, irgendwo im Internet, hatte genau dasselbe Problem. Dieser Person ist dann auch aufgefallen, dass es noch ein paar subtilere Unterschiede gab, wo z.B. die H2-Datenbank constraints nicht durchgesetzt hat, während Postgres es getan hat.
Was mich hier ärgert ist weniger, dass sich die Testdatenbank anders verhält als die Prod-Datenbank. Das ist kacke, aber ich könnte mich damit abfinden, auch wenn es manche Tests unmöglich macht (aber die meisten Tests laufen sehr gut). Was mich wirklich ärgert ist, dass ich meinen Code schlechter (d.h. komplexer und weniger effizient) machen musste, nur damit die Tests durchlaufen. Ich habe also meinen Code an die Beschränkungen der Tests.
Grundsätzlich halte ich es übrigens nicht für ein Problem, beim Schreiben des Codes auf gute Testbarkeit zu achten. Oft ist gut testbarer Code auch einfacher zu verstehen und ordentlicher aufgebaut. Aber wenn man seinen Code schlechter machen muss, um ihn testen zu können, dann hat man ein Problem. Und in diesem speziellen Fall war allein die Existenz meines Datenbankpatches Grund für fehlschlagende Tests. Ich hätte nicht einmal Tests zu schreiben brauchen, oder auch nur Code, der die Datenbank nutzt!
Wie löst man das Problem? Nun, in diesem Fall kann man sehr gut einfach die integration tests direkt auf Postgres laufen lassen. Man kann Postgres sogar in-memory laufen lassen. Tatsächlich ist Postgres sehr mächtig. Ich würde es nicht für alle Zwecke einsetzen, die der Autor in dem verlinkten Artikel beschreibt, aber viele Vorschläge sind tatsächlich sehr nützlich, insbesondere, wenn man ohnehin schon eine Postgres-Datenbank am Start hat.
Die Kollegen auf Kundenseite sind aber gerade unter Zeitdruck und wollen keine derartigen Änderungen. Also muss ich bis auf Weiteres (und vermutlich dauerhaft) meinen schlechteren Code nutzen.