Always use the front door

Es ist jetzt schon eine ganze Weile her, dass ich mit meine it-agile-Kollegen Sebastian Sanitz und Bernd Schiffer in einem Grails-Projekt zusammengearbeitet habe. In diesem Projekt hatten wir uns entschieden, Oberflächentests mit Canoo Webtest zu schreiben.

Bereits nach kurzer Zeit stießen wir auf Probleme. Wie kann man mit Canoo Webtest direkt auf fachliche Funktionen oder die Datenbank zugreifen, um zu prüfen, dass die Daten korrekt erzeugt wurden?

Bernd vertrat die Ansicht, dass unsere Schwierigkeiten keine Schwäche von Canoo Webtest seien, sondern ein Feature. Es sei keine gute Idee, direkt auf die Fachlogik oder die Datenbank zuzugreifen. Stattdessen solle man die Tests nur gegen die Oberfläche schreiben. Er zitierte dazu ein Prinzip namens “Always use the front door”. Der Zugriff auf Fachlogik oder DB wäre quasi die Hintertür und die Oberläche die Haustür. Sebastian und ich hatten von diesem Prinzip noch nie etwas gehört, aber Bernd hat standhaft behauptet, das sei ein etabliertes Prinzip. (Spätere Internet-Recherchen zeigten, dass man mit dem Suchbegriff in Google nur einen Blogeintrag von Bernd findet.)

Wie auch immer. Wir einigten uns darauf, es mit “Always use the front door” zu versuchen. Das ging eine ganze Weile gut. Dann trat das nächste Problem auf. Wir mussten die Zeit im System nicht nur manipulieren, sondern auch einfrieren, um deterministische Tests zu bekommen. Bei Unittests ist klar, dass man sich dafür einen Zeitgeber baut mit einem passenden Interface. So kann man den Zeitgeber dann mocken und hat damit volle Kontrolle über die Zeit. Aber dieses Vorgehen war ja jetzt verboten. Wir durften nicht einfach eine Funktion im Systen aufrufen, um einen Zeitgeber durch einen Mock zu ersetzen.

Die Lösung für das Problem kam mind. mir etwas eigenartig vor. Wir haben einen TimeController programmiert, so dass man über eine REST-Schnittstelle die Zeit manipulieren und einfrieren kann. Wir haben das System also mit einer Benutzungsschnittstelle versehen, die man fachlich gar nicht braucht. Und natürlich hatten wir den Zusatzaufwand, den Controller so zu entwickeln, dass er im Produktivbetrieb nicht ansprechbar ist – wir wollten ja nicht, dass irgendein Schlauberger die Zeit des Systems manipuliert oder einfriert.

Gut, das funktionierte. Wir konnten den Test formulieren und haben uns an das Haustür-Prinzip gehalten. Aber war das vernünftig? In diesem Fall würde ich die Frage mit “ja” beantworten. Zum einen war der Aufwand für den zusätzlichen Controller in Grails auch nicht größer als hätten wir einen Unittest-artigen Mock programmiert. Und außerdem war der Controller auch beim manuellen Testen des Systems sehr nützlich – klassisch hätte jemand auf dem Server die Systemzeit umgesetzt – und wahrscheinlich vergessen, sie nach dem Test wieder korrekt einzustellen.

Danach kam ein größeres Problem auf uns zu. Unser System verschickt E-Mails. Das kann man mit Canoo Webtest ganz elegant testen. Dummerweise dauert es vom Versand bis zum Empfang einer E-Mails eine undefinierte Zeitspanne – ist halt asynchron. Also haben wir den Test nach Versand der E-Mail warten lassen. Und jedesmal, wenn der Test wegen Timeout fehlschlug, haben wir die Zeitspanne erhöht. Dadurch wurden die Testlaufzeiten immer länger und das grundsätzliche Problem blieb. Wenn die Webtests fehlschlugen, konnten wir auf Anhieb nie sicher sein, ob wir einen Bug hatten oder nur die Zeitspanne für das Warten auf E-Mails zu kurz war. Diese Situation haben wir erstaunlich lange ertragen. Letztlich machte Sebastian dann soviel Druck, dass wir nochmal nachdachten. Wir hatten also eine Situation, die wir bei Unittests niemals akzeptiert hätten. Also haben wir uns gefragt, ob wir die Situation bei den Webtests nicht verbessern können. Die erste Idee war – wieder mal -, das Abrücken vom Haustür-Prinzip. Wenn wir direkt Funktionen des Systems aufrufen würden, könnten wir das Problem Unittest-like lösen: Einfach einen Mock für das E-Mail-System reinklemmen und fertig.

Und tatsächlich funktioniert dieser Ansatz auch dann, wenn man das Haustür-Prinzip befolgen möchte. Dazu ändern wir zunächst unsere Perspekive auf das System: Wir zerteilen unser System in zwei Systeme, das Kernsystem für den eigentlichen Zweck und eines für das E-Mail-Versenden. Das Kernsystem ist vollständig synchron und daher auch automatisiert gut testbar. Das E-Mail-System ist asynchron und schwer automatisiert testbar. Dafür ist es so klein, dass die so entstehende Testlücke risikoarm wird.

Diese Unterteilung kann man ganz unterschiedlich durchführen. Eine Möglichkeit besteht darin, dass das Kernsystem die E-Mails nicht direkt versendet, sondern die E-Mails erstmal in eine Tabelle schreibt. Ein E-Mail-Versende-Job des E-Mail-Systems fragt diese Tabelle regelmäßig ab und versendet die E-Mails. Wurde die E-Mail erfolgreich versendet, wird der entsprechende Datensatz markiert. Für den Test existiert ein spezieller Controller, der den Inhalt dieser E-Mail-Tabelle anzeigt. Der Webtest arbeitet mit dieser Tabelle und wird daher wieder deterministisch und schnell. Und auch in diesem Fall bringt uns diese Herangehensweise einen positiven Seiteneffekt. Man kann den Controller zum Monitoring der Anwendung verwenden und so im Produktivbetrieb besser kontrollieren, was im System passiert.

Eine andere (von Sebastian vorgeschlagene) Variante wäre, Kernsystem und E-Mail-System nicht über die DB zu koppeln, sondern z.B. über REST-Aufrufe. Dann könnte man für den Test ein Dummy-E-Mail-System konfigurieren, dass die REST-Aufrufe entgegennimmt und protokolliert und der Test würde dieses Protokoll prüfen. Und dann könnte man sogar soweit gehen, dass man hier einen generischen Protokoll-Service einsetzt, der einfach alle REST-Aufrufe protokolliert und (wie Unittest-Mocks) vordefinierte Antworten liefert. Das hört sich echt cool an. Das sollte man jemand bauen. Sebastian?

Insgesamt kann man also sagen: Wir wissen nicht wirklich, woher das Prinzip stammt und ob Bernd es sich selbst ausgedacht hat, aber es funktioniert auch für Akzeptanztests sehr gut.

Wann auf TDD verzichten?

Gibt es eigentlich Situationen, in denen man nicht testgetrieben arbeiten sollte? Mir fallen drei Stück ein.

  1. Ich exploriere, ob und wie etwas funktioniert (z.B. eine neue Technologie).
  2. Es gibt ein definitives Datum in der nahen Zukunft, ab dem das System nicht mehr weiterentwickelt und irgendwann stillgelegt wird.
  3. Wir haben eine Startup-Situation, in der wir eine Idee sehr schnell am Markt testen wollen.

Zu Punkt 1: Exploration
Charakteristisch ist, dass der Code ständig massiv umgebaut wird. Jedesmal Tests anpassen zu müssen, kann sehr aufwändig sein. Mitunter ist man schneller, wenn man dann ganz auf das Testen verzichtet. Man muss dann eben auch nur den Mumm haben, nach der Exploration den ganzen ungetesteten Code wegzuwerfen und vernünftig neu zu schreiben.
Natürlich gibt es bei der Exploration auch Situationen, in denen Tests helfen. Das ist vor allem dann der Fall, wenn man ein neues API ausprobiert. Dann ist der Unit- oder FIT-Test häufig ein angenehm leichtgewichtiger Client für den Test.

Zu Punkt 2: Stilllegung
Wenn das System nicht mehr weiterentwickelt wird, gibt es auch keinen Grund, Aufwand in die Renovierung des Legacy-Codes zu stecken. Allerdings muss man sich auch sicher sein, dass die Stilllegung auch kommt. Viel zu häufig wird es nicht präziser als “das System läuft bestimmt nicht mehr so lange”. Was von solchen Prognosen zu halten ist, wissen wir spätestens nach dem Jahr-2000-Problem: Die ursprünglichen Entwickler der Systeme haben auch gedacht, dass ihre Systeme bestimmt nicht so lange laufen. Man muss sich also wirklich, wirklich sicher sein!

Zu Punkt 3: Startup
Bei Startups ist charakteristisch, dass ein schneller Markteintritt essenziell ist. Wenn es gut läuft, verdient man nach dem Markteintritt soviel Geld, dass man davon das System neu und vernünftig getestet nochmal schreiben kann. Wenn man also schneller an dem Markt kommt, wenn man die Tests weglässt, ist das ein valides Argument, auf TDD zu verzichten.
Natürlich gibt es irgendwann eine kritische Masse, ab der man bei der Entwicklung mit Tests schneller ist als ohne. Aber wo liegt diese Grenze? Ich hätte sie aus dem Bauch heraus bei 20-50 Personentagen Programmieraufwand verortet.
Also prüfen wir mal diese Hypothese. Ich habe Clipboard2Web vor diesem Hintergrund ohne Tests programmiert. Und bis zum ersten Release hat sich das nach meiner Einschätzung auch gelohnt. Jetzt ist aber der Zeitpunkt gekommen, ab dem ich mit TDD schneller vorankomme. Wieviel Aufwand habe ich bis zu diesem Punkt investiert? 12 Personenstunden! Ergo: Vergesst das mit dem Weglassen der Tests in Startup-Situationen. Es lohnt sich nicht.

Technical Debt und das geistige Taskboard

Entwickler wollen ihren eigenen Ansprüchen genauso gerecht werden wie den Ansprüchen anderer. Daher tun sich viele Scrum-Teams am Anfang schwer, sich einzugestehen, dass sie sich für den Sprint zuviel vorgenommen haben (Over Commitment). Für Product Owner ist es anfänglich ebenso schwer. Schließlich hat man auch als Product Owner seine Ziele und möglicherweise sogar bestimmte Features zu einem festen Zeitpunkt versprochen.
Beides zusammen kann ausreichend Druck erzeugen, dass die Entwickler in den Zug einsteigen, der direkt in die Hölle fährt: Sie opfern Qualität, um den geplanten Sprintinhalt zum Sprintende zu schaffen. Sie gehen eine technische Schuld ein (Technical Debt), die ihnen später sehr schmerzhaft auf die eigenen Füße fällt – in Form einer steilen Aufwandskurve.
Dabei ist das erste Problem aus meiner Sicht gar nicht, dass man die technische Schuld eingegangen ist. Das erste Problem ist, dass das implizit passiert. Die Entwickler machen sich beim Programmieren geistige Tasks in der Art “Hier müsste man noch mal das Refactoring XYZ durchführen.”, “Hier müssten noch die Tests nachgeschrieben werden.” etc.
Diese Tasks gehören nicht in den “Hinterkopf”. Diese Aufräum-Tasks gehören explizit und für alle sichtbar ins Sprint-Backlog (und damit auf das Taskboard). Damit hat man nicht nur die Merker, was man noch erledigen muss. Es wird auch sofort klar, dass man das Sprint-Commitment trotzdem NICHT erreicht hat. Man hat nur die übrig gebliebenen Tasks getauscht. Es sind keine fachlichen Features mehr offen, sondern technische Aufräumarbeiten.
Dieser Fakt ist erstmal zu akzeptieren und in der Sprint-Retrospektive zu beleuchten. Warum konnten wir unser Commitment nicht halten und wie machen wir es nächstes Mal besser? Und dann sollte man sich auch gleich noch die Frage stellen: Warum haben wir Qualität geopfert anstatt Funktionalität zu reduzieren und ist das Opfern der Qualität wirklich der bessere Weg?

Google-Testing-Blog: Testing Against Interfaces

Auf dem Google-Testing-Blog ist gerade ein Artikel “Testing Against Interfaces” erschienen. Es geht darum, wie man verschiedene Implementation desselben Interfaces testet. Der Artikel schlägt vor, eine abstrakte Testklasse zu dem Interface zu bauen. Im setUp dieser abstrakten Testklasse wird eine abstrakte Methode createXYZ aufgerufen, die das zu testende Objekt liefert. Jetzt leitet man von der abstrakten Testklasse konkrete Testklasse je getesteter Klasse ab. In diesem konkreten Testklassen überschreibt man die createXYZ-Methode und kann dann verkünden: “ich habe fertig” 🙂

Das klingt plausibel, ist nach meiner Erfahrung in vielen Fällen aber nicht praktikabel. Dafür sind zwei Gründe verantwortlich:

  1. Man benötigt in Tests das getestete Objekt mitunter in unterschiedlichen Ausprägungen, und muss mitunter unterschiedliche Konstruktoren aufrufen. Dann muss man die createXYZ-Methode parametrisieren. Das funktioniert aber nur solange gut, wie alle Implementationen des getesteten Interfaces dieselben Parameter bekommen. Ist das nicht der Fall, muss man die Signatur der createXYZ-Methode aufblähen mit Parametern, die nur für einzelne Implementationen einen Sinn ergeben. Hinweis: Das Kochbuch zu JUnit benutzt z.B. unterschiedlich erzeugte Objekte im selben Test (natürlich ohne die Interface-Problematik).
  2. Häufig reicht ein einzelnes Objekt als Fixture nicht aus. Es werden mehrere Objekte benötigt. Jetzt müsste die createXYZ-Methode mehrere Objekte zurück liefern (als Liste?) oder man ruft mehrere createXYZ-Methoden auf, oder… Alles nicht besonders elegant.

Diese Probleme waren bei uns in früheren Jahren in der Praxis so massiv, dass wir diese Konstruktion fallen gelassen haben. Stattdessen haben wir eine andere Lösung konzipiert: Wir bauen ein Test-Center auf. Das ist eine konkrete Klasse, die mir einzelne parametrisierte Testmethoden anbietet. Diese ruft man aus den konkreten Testklassen auf. Damit eliminiert man die Redundanzen in den Tests. Natürlich sind meine Tests etwas länger, weil man die Testmethoden zur Delegation trotzdem alle hinschreiben muss, a la


public void testXYZ() { meinTestCenter.testXYZ(meinObjekt); }

Als weiteren Vorteil bringt die Test-Center-Lösung die Abwesenheit von Vererbung. Die Kopplung zwischen den Testklassen wird also reduziert.

Interessanterweise hatten wir für solche Konstruktionen umso weniger Anwendungsfälle, desto mehr wir uns von technischen Frameworks hin zu konkreten Anwendungssystemen orientiert haben.

Anmerkung: Das Fixtures aus mehreren Objekten bestehen, bedeutet noch nicht automatisch, dass es sich um Integrationstests handelt. Es ist meiner Meinung nach erlaubt und auch notwendig, in Unit-Tests wenige eng zusammenarbeitende Objekte als eine Fixture zu begreifen. Ansonsten neigen die Tests zur Trivialität und in statisch getypten Sprachen wie Java müsste ich überall Interfaces einziehen und in jedem Test Mocks benutzen.

Testen mit C++, EXPECT und ASSERT

Im Google-Testing-Blog ist ein Artikel über das Unit-Testen mit C++ erschienen: EXPECT vs. ASSERT. Der Artikel ist auch deshalb großartig, weil hier die längst vergessen geglaubte Kunst des ASCII-Comics wieder auflebt. Aber auch der Einhalt ist interessant. Für C++-Entwickler scheint ein relevantes Problem adressiert zu werden. Für mich bleibt die Erkenntnis: “Ein Glück, dass ich nicht C++ programmieren muss. Diese Art von Problemen wäre nicht für mich.”

Testen mit C++, EXPECT und ASSERT

Im Google-Testing-Blog ist ein Artikel über das Unit-Testen mit C++ erschienen: EXPECT vs. ASSERT. Der Artikel ist auch deshalb großartig, weil hier die längst vergessen geglaubte Kunst des ASCII-Comics wieder auflebt. Aber auch der Einhalt ist interessant. Für C++-Entwickler scheint ein relevantes Problem adressiert zu werden. Für mich bleibt die Erkenntnis: “Ein Glück, dass ich nicht C++ programmieren muss. Diese Art von Problemen wäre nicht für mich.”

Software-Architektur mit Dependency-Injection

Überblick

Dieser Artikel beschreibt meine Erfahrungen mit Dependency-Injection (DI) in mehrjährigen Großprojekten. Dabei geht es vor allem um die Best-Practices für Entwurf und Architektur.

Wir haben vor allem handprogrammierte DI, Pico-Container und Java-Server-Faces als DI-Container eingesetzt. Der Artikel bezieht sich primär auf Pico-Container. Die „Erkenntnisse“ lassen sich aber auf andere DI-Container übertragen.

In einem Großprojekt haben wir ein System mit vielen Singletons schrittweise auf Pico-Container umgestellt. Wir haben einige größere Refactorings in dem Projekt durchgeführt und bei vielen war der Nutzen letztlich zweifelhaft. Der Umbau auf Pico-Container gehört aber definitiv zu den Umbauten, die sich deutlich gelohnt haben.

Zielsetzungen

Mit DI kann man eine Reihe von Zielen erreichen:

  • Entwurf entkoppeln
  • Abhängigkeiten explizieren
  • Testbarkeit verbessern
  • Singletons und static-Attribute eliminieren

Entwurf entkoppeln

DI führt nicht automatisch zu einer Entkopplung der Systemkomponenten. Man kann sich auch mit DI eigenartige und unwartbare Abhängigkeitsgeflechte zusammenbauen. Ohne DI hingegen ist es in der Praxis fast unmöglich, entkoppelte Systeme zu bauen. In diesem Sinne ist DI eine notwendige, aber keine hinreichende Bedingung für Entkopplung. Wenn man zusätzlich zu DI noch Interfaces und testgetriebene Entwicklung einsetzt, bekommt man aber ohne größere Anstrengungen ein gut entkoppeltes System.

Abhängigkeiten explizieren

DI macht die Abhängigkeiten von Objekten an ihrer Schnittstelle deutlich. Dadurch ist erstmal natürlich nur ein wenig Dokumentation gewonnen. In unseren Projekten hat dieses kleine bisschen Mehr an Dokumentation aber erheblichen Einfluss darauf gehabt, wie gut man Entwürfe verstehen konnte.

Testbarkeit verbessern

Durch die Entkopplung ließen sich die einzelnen Komponenten besser testen. Hier fällt insbesondere die Harmonie von DI mit Mock-Testen[1] positiv auf. Zusammen mit testgetriebener Entwicklung entsteht ein schlagkräftiges Trio.

Nicht zuletzt laufen so entkoppelte Tests viel schneller ab als klassisch erstellte Unit-Tests (in einigen Fällen konnten wir Testlaufzeiten von mehreren Minuten auf unter 10 Sekunden reduzieren).

Singletons und static-Attribute eliminieren

Setzt man DI konsequent ein, braucht man weder Singletons noch irgendeine andere Form statischer Attribute. Dadurch werden Zustandsabhängigkeiten im System reduziert, so dass sich Systemteile besser unabhängig voneinander wieder verwenden und testen lassen.

In unseren Projekten sind durch die Umstellung von Singletons auf DI eine ganze Reihe eigenartiger Phänomene beim Ausführen und beim Testen verschwunden.

Pico-Container im Code

Pico-Container ist ein sehr leichtgewichtiger DI-Container, der programmatisch konfiguriert wird. Daher können programmatisch DI-Container erzeugt und an andere Objekte übergeben werden. Da ist eine Fußangel versteckt: Man sollte Pico-Container minimal-invasiv benutzen. Das bedeutet, dass möglichst wenig Code Kenntnis davon haben soll, dass Pico-Container (oder ein anderer DI-Container) eingesetzt wird. Folglich sollten die Pico-Container nur im Startup erzeugt werden. Weitere Klassen dürfen nicht vom Pico-Container abhängen. In der Schichtung des Systems liegt der Pico-Container also ganz oben.

Pico-Container in Tests

In Unittests hat Pico-Container nichts verloren. Sind die getesteten Klassen so kompliziert miteinander verflochten, dass man sie manuell nicht zusammenstecken kann, ist Refactoring angesagt – und zwar schleunigst.

Welche Klassen registriert man beim DI-Container?

In unseren Projekten haben sich drei Typen von Klassen etabliert, die wir typischerweise beim DI-Container registrieren.

  • Fabriken
  • Services
  • Objekte, die nur einmal existieren dürfen (ehemalige Singletons)

Wofür baut man Fabriken?

Wenn ein Objekt andere Objekte erzeugen will, kann man diese Objekte nicht über DI hineinreichen – man weiß ja noch nicht, wie viele Objekte erzeugt werden. In solchen Fällen reicht man stattdessen Fabriken per DI in das Objekt.

Fabriken werden häufig mit static-Methoden implementiert. Wenn man die Fabriken beim DI-Container registriert, wäre das jedoch einigermaßen witzlos. Also gilt: Fabriken sollten keine static-Methoden enthalten – static-Attribute sowieso nicht.

In vielen Projekten habe ich beobachtet, dass viel zu selten Fabriken benutzt werden! Fabriken (ohne static-Methoden) helfen bei DI und erleichtern das Testen mit Mocks ungemein[2]. Im Zweifel würde ich lieber eine Fabrik zuviel spendieren.

Abschluss

Unsere Erfahrungen mit Pico-Container haben wir in erster Linie in Rich-Clients gesammelt. Wir haben Pico-Container serverseitig nicht verwendet. Es sollte jedoch möglich sein, weil der Pico-Container sehr leichtgewichtig ist. Der Overhead für die Erzeugung bei jedem Request oder EJB-Aufruf sollte in den meisten Anwendungen zu verkraften sein. Wenn der Overhead zu groß wird, kann man den Pico-Container mind. bei Webanwendungen auch in der Session speichern.

Inzwischen kommen viele Server-Infrastrukturen aber bereits mit eigenen DI-Containern (Spring, JSF, EJB 3), so dass man serverseitig i.d.R. wahrscheinlich nicht in die Verlegenheit kommt, Pico-Container einzusetzen.

Referenzen


[1] z.B. mit Easy Mock

[2] Beim Testen gilt: Jedes Testproblem kann durch eine weitere Indirektion gelöst werden.

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 or is )
got:

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.