Meist sind plötzlich fehlschlagene Tests in einem Projekt auf vorangegangene Codeänderungen zurückzuführen. Manchmal ist der Zusammenhang offensichtlich erkennbar, manchmal liegen zwischen dem geänderten Code und der problematischen Stelle im Test mehrere Aufrufe oder Indirektionen. Für gewöhnlich lässt sich der Grund für einen fehlschlagenen Test aber im letzten Commit bzw. dem Diff zum letzten eingecheckten Stand finden. Kurios sind solche Tests, die völlig ohne Zusammenhang von einem auf den anderen Tag fehlschlagen. Auf einen solchen Test sind wir gestern im einem unserer Projekte gestoßen.
Aufbau des Test-Szenarios
Getestet wird in diesem Fall ein Validator. Dieser prüft eine Datumsangabe auf
ein bestimmtes Format und darauf, dass das Datum nicht in der Zukunft liegt. Um
die relevanten Testwerte abzudecken, wird ein LocalDate
von „heute“ erzeugt und
für die unterschiedlichen Assertions mit minusMonths(1)
und plusMonths(1)
entsprechend manipuliert. Anschließend wird dieses Datum mit Hilfe eines
DateTimeFormatter
in den passenden String formatiert.
Der zugehörige Test-Code sieht etwa folgendermaßen aus:
Ich kann vorweg nehmen, dass der Grund für das Fehlschlagen dieses Tests nicht in der Implementierung des Validators liegt. Das Problem liegt im Test-Code. Wer die Ursache noch nicht erkannt hat, darf sich im Folgenden auf die Erklärung freuen. Wichtig ist jedoch noch zu beachtet, dass der Test lediglich gestern am 31.01.2019 fehl schlug, nicht aber am 30. Januar oder davor.
Analyse und Debugging
Der Code sieht auf den ersten Blick unverdächtig aus. Vom heutigen Datum wird ein
Monat abgezogen, das Ganze wird in einen String formatiert und validiert. Wieso
also ist das Datum nicht in der Vergangenheit? Mein Verdacht lag schnell auf der
Formatierung des Datums, aber wir haben den Debugger bemüht, um dies zu bestätigen.
Die Variable past
enthielt wie erwartet den 31.12.2018, aber dem Validator wurde
der String „12/2019“ übergeben. Seltsam, aber die Dokumentation sollte uns eine
Erklärung liefern. Wir schauten also in die
Docs
des DateTimeFormatter
. Für die Formatierung des Jahres gibt es dort drei mögliche Angaben:
-
uuuu
gibt das Jahr als vierstellige Zahl an -
yyyy
gibt das Jahr seit Beginn einer Ära an -
YYYY
gibt das Jahr basierend auf den Kalenderwochen an
Und genau hier lag das Problem. Die KW1 für 2019 begann bereits am 31.12.2018.
Damit formatierte der Formatter aus dem Datum den String „12/2019“. Im Nachhinein
eine ganz klare Sache. Die Falle an der Stelle lag also daran, dass die
Wochenzählung für 2019 bereits im vergangenen Jahr begann. Hinzu kommt die
Inkonsistenz zwischen verschiedenen Programmiersprachen. Schaut man sich
beispielsweise JavaScript an, entsprechen dort vier große YYYY
dem Jahr mit
Jahrtausend. In Ruby erhalte ich mit %Y
das volle Jahr und in C# verwende ich
dazu yyyy
. Da kann es auch schon mal zu Verwechslungen kommen. Den Kollegen,
die den Test ursprünglich implementiert haben, ist also auch keinesfalls ein
Vorwurf zu machen. Es lohnt sich aber, bei der Formatierung des Datums immer
nochmal die Dokumentation zu prüfen, ob man denn auch die richtigen Zeichen
gewählt hat, um das gewünschte Format zu erhalten.
Übrigens: nächstes Jahr beginnt die KW1 bereits am 30.12.2019. Danach verschiebt sich der Beginn von KW1 durch das Schaltjahr in 2020 erstmal wieder in den Januar.