Montag, März 03, 2008

Junit 4.4: assertThat

JUnit 4.4
Junit 4.4 ist bereits seit über einem halben Jahr verfügbar. Von den Projekten, die ich kenne, arbeiten viele aber noch mit JUnit 4.0 oder noch älter. Grund genug, einmal ein unscheinbar daher kommendes neues Feature zu betrachten.

assertThat
Zunächst gibt es eine neue assert-Methode namens assertThat. assertThat bekommt zwei Parameter übergeben, den tatsächlichen Wert (im Gegensatz zu assertEquals als ersten Parameter) und einen Matcher. Über die Matcher wird letztlich die zu testende Bedingung ausgedrückt.

Aus

assertEquals(10, list.size());

wird

import static org.hamcrest.core.Is.is;
...
assertThat(list.size(), is(10));

Diese Art der Notation geht in Richtung Fluent-Interface und ist aus meiner Sicht etwas besser lesbar als die klassische Notation.

Zusätzlich zu is() sind weitere Matcher verfügbar, so dass man im Wesentlichen mit assertThat auskommen sollte und die meisten anderen assert-Methoden nicht mehr braucht.

Ausdrucksstärker mit Hancrest
Die assertThat-Notation geht auf Hamcrest zurück. In JUnit 4.4 ist ein Teil davon enthalten - warum man da so halbe Sachen gemacht hat, erschließt sich mir nicht. Wenn man assertThat verwenden möchte, empfiehlt es sich aus meiner Sicht, Hamcrest komplett mit einzubinden. Dann hat man mächtige Matcher zur Verfügung, die die Tests nicht nur lesbarer, sondern auch kürzer gestalten. Möchte ich z.B. den Inhalt einer Liste testen, ist das mit Hamcrest ein Einzeiler:

import static org.hamcrest.Matchers.*;)
...
assertThat(list, hasItems("a", "b", "c"));


Bessere Fehlermeldungen
Nicht zuletzt generiert Hancrest bei fehlschlagenden Tests Fehlermeldungen, die häufig aussagekräftiger sind als bei JUnit-Classic. Bei JUnit-Classic war man außerhalb von assertEquals auf assertTrue oder assertFalse angewiesen und hat dann nur die Meldung bekommen, dass der Test fehlgeschlagen war. Die Gründe dafür konnte man in der Meldung aber nicht erkennen. Hamcrest hingegen generiert aus dem kompletten Matcher eine aussagekräftige Fehlermeldung:

assertThat(foo, anyOf(is(1), is(3)));

prüft, dass das foo den Wert 1 oder 3 hat (man möge mir das schwache Beispiel verzeihen). Wenn das nicht der Fall ist, erhalten wir in JUnit eine aussagekräftige Fehlermeldung:

java.lang.AssertionError:
Expected: (is <1> or is <3>)
got: <2>



Fazit
Das assertThat-Feature kommt etwas unscheinbar daher. Ich finde, dass dem Feature mehr Ehre gebührt und dass es in der Praxis einen größeren positiven Effekt hat, als man zunächst meinen möchte.

Post bewerten

3 Kommentare:

Matthias Luebken hat gesagt…

Das sieht tatsächlich nett aus. Voraussetzung hierfür ist die Verwendung des Fluent-Interface. Und davon bin ich noch nicht überzeugt. Die Erstellung eines nicht trivialen Fluent-Interfaces, welches von anderen Entwicklern leicht zu lesen ist, ist nicht leicht. Ich habe ein solches Interface noch nicht gesehen.

hipe.philipp hat gesagt…
Dieser Kommentar wurde vom Autor entfernt.
hipe.philipp hat gesagt…

Hallo Steilpass,

genau weil die Erstellung von Fluent Interfaces so umständlich ist habe ich mich mit meiner Bachelorarbeit dazu beschäftigt. Ich habe versucht mit Hilfe Modellgetriebener Softwareentwicklung (MDA) das ganze auf eine höhere, viel verständlichere Ebene zu heben. Damit ist es nun möglich auf Modellebene ähnlich wie in einem UML-Daigramm ein Fluent Interface zu beschreiben. Aus diesem Modell wird dann einfach nur noch der nötige Code automatisch generiert. Somit konnte ich für ein Test-Projekt fast 95% des Codes automatisch generieren lassen und ein ziemlich komplexes Fluent Interface erstellen.

Unter http://www.fluent-interfaces.com findet man einiges an Material zu meiner Lösung.

Ich halte sehr viel von Fluent Interfaces, denn mit Hilfe dieser können wir vieles ganz explizit im Code darstellen.