Noch mehr Besucher

Im vorherigen Beitrag wurde das Visitor Pattern auf ein Enum angewendet, um das Enum nicht mit vielen Methoden zu überfrachten. Wie andere Klassen sollte ein Enum nicht gegen die Prinzipien des objektorientierten Designs verstoßen.

Die vorgestellte Lösung funktioniert zwar sehr gut, aber was soll der Entwickler machen, wenn neue Klassen die Bühne betreten? Nachdem er also ein Enum AccessLevel mit einer Menge interessanter Visitor Klassen verknüpft hat, kommt der Kunde mit neuen SecurityLevel und AnalyseLevel Anforderungen. Wie schön wäre es doch, wenn die bisherigen Visitor Klassen weiterverwendet werden können.

Etwas Generisches muss also her. Zuerst ein Visitor der verschiedene Enums besuchen kann.

public interface Visitor<E extends Enum<E>> {
  void visitEins(E e);
  void visitZwei(E e);
  void visitDrei(E e);
 }

Das Konstrukt sieht etwas seltsam aus, spiegelt aber die Natur der Enum Klassen wieder. Sie sind abgeleitet von der Klasse Enum und werden dieser als generischer Typ übergeben.

Das Enum, das vom Visitor besucht wird, muss sich der neuen Lage anpassen und bekommt eine etwas andere Methodensignatur.

public enum AccessLevel {
  ONE {
    public void accept(Visitor<AccessLevel> visitor) {
      visitOne(this);
    }
  },
  TWO {
    public void accept(Visitor<AccessLevel> visitor) {
      visitTwo(this);
    },
  THREE  {
    public void accept(Visitor<AccessLevel> visitor) {
      visitThree(this);
    }
}

Das Enum bekommt einen Visitor, der genau auf seine Bedürfnisse zugeschnitten ist. Die Parameter der Methoden sind vom Type AccessLevel. Dazu noch ein generischer PrintVisitor, der auf beliebige Enums arbeitet.

public class PrintVisitor<E extends Enum<E> extends Visitor<E> {
  private String print;
  public void visitOne(E level) {
    print = "LEVEL-" + this;
  }
  public void visitTwo(E level) {
    print = "LEVEL-" + this;
  }
  public void visitThree(E level) {
    print = "LEVEL-" + this;
  }
  public String printLevel(E level) {
    level.accept(this);
    return print;
  }
}

Dieser PrintVisitor kann nun auf beliebige Enums angewendet werden, die einen solchen generischen Visitor akzeptieren.

PrintVisitor<AccessLevel>().printLevel(AccessLevel.ONE);
PrintVisitor<SecurityLevel>().printLevel(SecurityLevel.A);
 

Zu Besuch bei den Enums

Die Enums in Java sind eine typsichere Alternative zu den früher verwendeten Konstanten.

public class Test {
  private static final String LEVEL_ONE = "one";
  private static final String LEVEL_TWO = "two";
  ...
}

Mit solchen Konstanten hat man die üblichen Probleme, dass man sie überall fehlerhafter Weise einsetzen oder anstelle der Konstanten andere Werte des Typs verwenden kann. Besser also der Einsatz von Enums, die als echter Typ nur die definierten Konstanten zulassen.

public class Test {
  private enum Level { ONE, TWO }
  ...
}

Enums erlauben aber mehr als nur die Definition von Konstanten, sie können auch zusätzliche Methoden enthalten, die entweder für alle Elemente gelten oder aber für einzelne Elemente überschrieben werden können.

public enum Level {
  ONE {
    public String print() {
    return "LEVEL-A";
    }
  },
  TWO,
  THREE;

  public String print() {
    return "LEVEL-" + this;
  }
}

Selbstverständlich kann man auch mit diesen Sprachmittel seinen Schindluder treiben. Kein Sprachkonstrukt ist so sicher, dass nicht ein unfähiger Entwickler eine Zeitbombe daraus bauen kann. Beliebt sind bei den Enums insbesondere Membervariablen zur Speicherung von Zwischenergebnissen oder Zuständen. Dabei wird dann leicht vergessen, dass andere Software Entwickler von Enums eine gewisse Konstants erwarten.

Erkennt ein Novize die Möglichkeiten der Enums, dann ist Vorsicht geboten, denn sehr schnell expandiert das obige Beispiel und erhält zusätzliche Methoden die XML, HTML und JSON Darstellungen zurückliefern. Außerdem werden neue Konstanten FOUR und FIVE hinzugefügt und die Utility-Methoden next() und previous() ergänzt, um von einer Enum Konstante zur anderen zu wandern.

Ein schönes Pattern, um der wachsenden Zahl von Methoden entgegenzuwirken, ist das Visitor Pattern. Die spezielle Logik ist in Klassen ausgegliedert, die für unterschiedliche Besucher eigene Methoden bereitstellen. Eine Visitor-Variante für das obige Beispiel:

public enum Level {
  ONE {
    public void accept(Visitor visitor) {
      visitOne(this);
    }
  },
  TWO {
    public void accept(Visitor visitor) {
      visitTwo(this);
    },
  THREE {
    public void accept(Visitor visitor) {
      visitThree(this);
    }
  };

  public abstract void accept(Visitor visitor);
}

Der Visitor ist ein Interface, das für die Konstanten Methoden bereitstellt, die diese aufrufen können. Dabei geben sie sich selber als Parameter der Methode an. Im Falle einfacher Enums kann man dies natürlich auch vernachlässigen, da die Konstante der Methode keine zusätzliche Information liefert.

public interface Visitor {
  void visitOne(Level level);
  void visitTwo(Level level);
  void visitThree(Level level);
}

Um eine unser Beispiel zu vervollständigen fehlt nur noch die Implementierung eines Visitors.

public class PrintVisitor extends Visitor {
  private String print;
  public void visitOne(Level level) {
    print = "LEVEL-A";
  }
  public void visitTwo(Level level) {
    print = "LEVEL-" + this;
  }
  public void visitThree(Level level) {
    print = "LEVEL-THREE";
  }
  public String printLevel(Level level) {
    level.accept(this);
    return print;
  }
}

Für dieses Beispiel eine Menge Code, aber schon die Erweiterung für XML, HTML und JSON bedeutet keine Veränderung mehr am Enum, da ja die neue Funktionalität in den Visitor Implementierungen XmlVisitor, HtmlVisitor und JsonVisitor gebündelt wird.

 

Conway’s law

Any piece of software reflects the organizational structure that produced it.

 

Zauberei mit Wahrheiten

Haben Sie manchmal auch das Gefühl, dass ihre Kollegen Angst vor boolschen Ausdrücken haben? So mancher scheint die De Morganschen Gesetze wie die dunklen Zauberkünste einer Morgan le Fay zu fürchten.

So bleiben dann auch Ausdrücke, wie der folgende, für Ewigkeiten im Programmcode.

!(color != BLACK && color != WHITE) || color == WHITE

Häufige verweisen die Entwicklern auf den Optimierungen des Compilers, wenn sie ihre boolschen Ausdrücke nicht vereinfachen. Warum etwas händisch versuchen, was der Compiler sehr viel besser kann?

In erster Linie geht es natürlich um die Wartbarbeit und Erweiterbarkeit des Sourcecodes, denn nicht jeder Entwickler mag logische Labyrinthe in Form von verschachtelten If Anweisungen und großer Ausdrücke in den Bedingungen. Und vielen Entwicklern fehlt auch einfach diese autistische Freude eines Sodoku Spielers beim Ausfüllen von Wahrheitstafeln um herauszufinden, was der Autor einer Methode eigentlich bewirken wollte.

Findet ein Entwickler nicht schnell heraus, was ein Konstrukt bewirkt, schaltet er sofort auf Plan B um und installiert seinen Code neben den ursprünglichen, hängt weitere If-Else-Kaskaden an die schon existierenden und etabliert damit das Lavafluß Anti-Platten in der Software.  Der Sourcecode bläht sich auf und niemand weiß mehr, was eigentlich alles dort drinnen passiert.

Regeln

Knapp formuliert lauten die De Morganschen Gesetze, eine negierte Und-Verknüpfung entspricht der Oder-Verknüpfung der negierten Werte und umgekehrt. Man kann boolsche Ausdrücke mit diesen Gesetzen mechanisch umformen und mit Hilfe weiterer boolscher Rechenregeln vereinfachen.

Die Wahrheit in der boolschen Algebra ist, wie der boolsche Ausdruck oben.

color == BLACK || color == WHITE

 

 

Die Impediments mit Aber

Die Theorie hinter Scrum ist recht einfach, dennoch scheinen einige Konsequenzen den Beteiligten nicht klar zu sein. Impediments sind in Scrum Störungen aller Art, die ein Team behindern. Behindert wird das Team, wenn es seine geplanten Aufgaben nicht erledigen kann.

Die Impediments sind zahlreich und sehr unterschiedlich, fehlende Lizenzen, kaputte Klimaanlage, geschwätzige Kollegen. Manche Impediments werden aber nicht als solche erkannt, weil man sie im ersten Augenblick  für notwendig erachtet. Morgens schnell KPIs für den PO zusammenstellen, weil er sie für ein Gespräch mit der Geschäftsleitung benötigt,  ein neuer Mitarbeiter in der Abteilung braucht eine Entwicklungsumgebung, ein Abteilungsleiter benötigt eine Liste von Servern für die morgige  Inventur. Dies sind die berüchtigten Impediments mit dem Aber, weil auch immer gleich die Entschuldigung folgt. Als ob es nicht auch für alle anderen Impediments eine Begründung gibt.

Eine Gemeinsamkeit haben sie aber alle, fehlende Organisation und Planung. Benötige ich KPIs, dann veranlassen ich ihre regelmäßige (automatisierte) Erhebung, benötige ich Inventurlisten, dann organisiere ich deren Pflege, habe ich neue Mitarbeiter, dann plane ich ihre Einarbeitung.  In Kaizen werden solche Impediments als Verschwendung bezeichnet und durch kontinuierliche Verbesserung beseitigt.  Hier müssen sich die Verursacher der Impediments besser organisieren und strukturieren und Aufgaben für die Teams besser planen.

Aber was viel wichtiger ist, jede einzelne Aufgabe für ein Team muss auf den Prüfstand. Die Scrum Teams sind das produktive Herz der Organisation, sie erstellen die Werte für den Kunden. Daher müssen Aufgaben außerhalb der Wertschöpfung auf jeden Fall vom Team ferngehalten werden. Manche dieser Aufgaben entstammen der Zeit bevor agile Methoden eingeführt wurden und die Entwickler alle anfallenden Aufgaben erledigen mussten.  Da die Entwickler nun aber die wertvollste Ressource sind, sollten Aufgaben anders organisiert, automatisiert oder als Verschwendung erkannt, gänzlich abgeschafft werden.

Denn ein Impediment bleibt ein Impediments, ohne Wenn und Aber!

 

 

 

Lasst uns mal rausgehen!

Was hat ein Familienausflug mit agilen Teams zu tun? Vielleicht schauen wir einmal zwei Familien dabei zu. Die erste Familie spaziert durch den Wald, die begeisterten Kinder laufen vor und fallen zurück, aber der gesamte Tross ist stetig und  zügig unterwegs. Die Stimmung locker und alle gut gelaunt. Obwohl die Kinder auch immer wieder hinter den Eltern stehen bleiben, gehen diese unverwandt weiter. Sie wissen, dass ihre Kinder sie bald wieder eingeholt haben. Die zweite Familie spaziert missmutig durch den Wald, die Mutter ruft die voraus laufenden Kinder immer wieder zurück und wenn die Kinder zurückbleiben, weil sie etwas interessantes entdeckt haben, schimpft der Vater. Die Stimmung der Kinder sinkt auf einen Tiefpunkt und sie trotten missgelaunt den Eltern hinterher.

Agile Firmen entsprechen natürlich der ersten Familie. Die Teams haben die Freiheit zu experimentieren und müssen dadurch nicht immer mit Höchstgeschwindigkeit durch die Sprints rasen.  Das Tempo der Spaziergänger ist die Velocity der Teams, mal schneller und mal langsamer,  aber im Durchschnitt  immer sehr zufriedenstellend.

Funktioniert Scrum nicht in einer Firma, weil das agile Denken nicht in allen Köpfen verankert ist, dann versucht das Management die Arbeit der Teams zu optimieren.  Es greift ein, wenn scheinbar Zeit vergeudet wird und tadelt, wenn Ergebnisse nicht gleich perfekt sind. Beides ist ein Verkennen der Tatsachen, denn diese vergeudeten Zeiten sind in Wirklichkeit häufig fruchtbare Diskussionen im Team und die Optimierungen von bekannten Schwachstellen.  Fehler die durch direktes Feedback entdeckt werden, sind positive Zeichen einer intakten Qualitätskultur und vermeiden das aufwendiges Debuggen der ausgelieferten Software.  Das häufige Eingreifen des Management empfindet das Team als negativ. Das Team fühlt sich überwacht und unterschätzt, wodurch Motivation und Leistung sinken.

Wer sich über mangelnde Leistungsfähigkeit seiner Teams sorgt, sollte daher besser seine Führungskräfte im Auge behalten.

Bei einem Spaziergang ist es nicht immer so einfach, wie oben beschrieben. Manchmal ist das Ausflugsziel langweilig und manche Kinder sind einfach für rein gar nichts zu begeistern.

 

 

Brooks‘ law

Adding manpower to a late software project makes it later.

Neue Mitarbeiter in Projekte hineinzustecken bedeutet immer zusätzliche Kommunikation und die Produktivität des Teams wird reduziert, weil es die neuen Mitarbeiter einarbeiten muss. Wenn das Team dann schon dem Zeitplan hinterherläuft, wird die Situation nur schlimmer. Außerdem ist oft das Team schon groß genug und die Entlastung durch neue Mitarbeiter geringer als der Einarbeitungsaufwand. Für ausreichend große Teams gibt es daher folgende lustige Variante dieser Regel „Eine Frau braucht neun Monate um ein Baby zu bekommen; neun Frauen auch“

 

 

Hierarchien

Schon bemerkt, dass auf jeder Konferenz folgender Vortrag erscheint?

  • Scrum eingeführt
  • Enttäuschung wegen fehlender positiver Effekte
  • Externer Berater streicht die Hierarchien
  • Die Firma nimmt fahrt auf

Verfolgt man diese Vorträge, dann ist es offensichtlich was falsch läuft. Die alten Hierarchien regieren weiter und die positiven Effekte von Scrum können nicht zum Tragen kommen.

Verwunderlich ist nur, warum die Verantwortlichen dieses eklantante Problem nicht sehen. Wollen sie Besitzstände nicht anrühren, verstehen sie den Prozess nicht oder vertrauen sie nur ihren Führungskräften?

 

Tolle Tests

schaut man in die Ansammlungen von Unit Tests, die sich im Laufe der Zeit in Projekten anhäufen, stellt man häufig folgendes fest:

  • Es ist nicht klar, was alles getestet wird. Weder der Name noch der Inhalt der Methode lassen direkt auf den Zweck schließen
  • Die Konstruktion der Testdaten ist in andere Klassen ausgelagert
  • Der Abgleich der Testausgabe gegen die Erwartungswerte ist spartanisch
  • Die Tests verwenden den zu testenden Code

Was zeichnet einen guten Test aus?

Eine Testmethode baut zuerst die Vorbedingung für den Test auf, indem die notwendigen Datenstrukturen initialisiert oder durch Mock-Objekte simuliert werden. Danach erfolgt der Aufruf der zu testenden Methoden um dann den erreichten Zustand oder die Ergebnisse der Berechnung zu prüfen.

Die Tests sollen kurz und kompakt sein, weil sonst die Verständlichkeit leidet. Testmethoden werden sehr selten angeschaut, daher ist die schnelle Verständlichkeit eine der wichtigsten Anforderungen.

Viele kleine Tests, die jeweils eine einzelne Eigenschaft prüfen, sind einigen großen Tests vorzuziehen, weil sich so das Problem für den Entwickler sehr viel schneller analysieren lässt. Schlagen mehrere Tests fehl, gibt das dem Entwickler mehr Anhaltspunkte für eine gezielte Fehlersuche.

Für die Voraussetzungen werden nur die notwendigsten Daten erzeugt, denn alle weiteren Daten sind unnötig und ihre Erzeugung kostet Zeit. Wenn mehrere Tests die gleiche Funktionalität testen und auf den gleichen Daten arbeiten, dann kann man diese in einer Tests Klasse gruppieren und eine gemeinsame Initialisierungsmethode nutzen. In JUnit sind dafür z.B. Methoden vorgesehen, die mit @Before und @BeforeClass annotiert sind.

Die Tests sollten vor der Entwicklung geschrieben werden, um die Anforderungen an den Produktionscode zu beschreiben. Eine Implementierung unter Beachtung dieser Tests, garantiert zum einem die Testbarkeit, aber man gewinnt dadurch gleichzeitig auch eine compilierbare Spezifikation des Produktionscodes.

Test Anti-Pattern

Auch bei der Entwicklung von Tests kann man eine Menge falsch machen. Ein paar gängige Fehler sind hier mal unter dem Label Test Anti-Pattern aufgelistet.

Code Reuse

Da die Unit Test meist von den Entwicklern der Software geschrieben werden, greifen diese auch immer wieder auf existierenden Programmcode zurück.  Dies kann fatale Folgen haben, wenn der Erwartungswert im Test identisch zum Programmergebnis erzeugt wird. Der Test ist erfolgreich, aber das Programm arbeitet dennoch fehlerhaft.

assertThat(document.getElementById(Constant.ID).getLocalName(), equalTo(Constant.START_TAG));

In diesem Beispiel wird geprüft, ob ein Element mit der ID aus Constant.ID in einem XML Dokument den Namen hat, der in der Konstanten Constant.START_TAG definiert wurde. Obwohl der Test erfolgreich ist, kann die XML Datei fehlerhaft sein, wenn die Konstanten fehlerhaft definiert wurden. Besser ist hier immer:

assertThat(document.getElementById("id").getLocalName(), equalTo("start"));

Uneindeutige Prüfungen

Mancher Entwickler freut sich schon, wenn seine Methoden ein Ergebnis liefern, das nicht null ist, oder die Anzahl der Ergebnisse einen bestimmten Wert hat. Im ersten Moment scheint dies meist ausreichend, bei späteren Erweiterungen tauchen dann aber die Probleme auf. In der Regel schlagen die Test bei Anpassungen fehl, weil es mehr oder weniger Einträge gibt. Aber wie lange bleibt ein Fehler unentdeckt, wenn die Liste die gleiche Länge behält, aber falschen Einträge beinhaltet?

assertEquals(3, getAdminUsers().size());

Liefert diese Methode nun die falschen drei User, dann fällt der Fehler im Produktionscode nicht auf. Hier sollte man besser die tatsächlich zurückgelieferten User prüfen. Der Einsatz der Hamcrest Bibliothek liefert hier das Handwerkszeug für sehr kompakte Prüfungen.

assertThat(getAdminUsers(), contains(hasName("Tick"), hasName("Trick"), hasName("Track")));

Tests in Schleifen und bedingenten Anweisungen

Manche Prüfungen in Testmethoden sind kompliziert und beinhalten Schleifen und Bedingungen. Sie erschweren nicht nur das Verständnis, sie sorgen häufig auch für fehlerhafte Prüfungen.

for (User user : getAdminUsers()) {
  assertTrue(user.isAdmin());
  assertEquals("T", user.getName().substring(0, 1));
}

Hier wird geprüft, ob die Namen der User in der Admin Liste alle mit einem „T“ beginnen und ob das Admin Flag gesetzt ist. Leider wird nicht geprüft, ob überhaupt ein User in der Liste ist. Bei einer leeren Liste wird keine einzige Prüfung vorgenommen. Solche Prüfungen schreibt man besser um, damit keine Schleifen und Bedingen mehr vorkommen.

assertThat(getAdminUsers(), everyItem(both(isAdmin()).and(hasName(startsWith("T")))));

Testdaten Generatoren

Wenn die Testdaten von verschiedenen Methoden verwendet werden sollen, dann kann man deren Generierung in eine Initialisierung Methode auslagern oder sich ein paar einfache Utility Methoden schreiben. Manches Mal mutiert aber dieser Ansatz wie ein Krebsgeschwür und aus den Hilfsmethoden erwächst ein ganzer Zoo von Hilfsklassen, der einem eigenen Framework gleichkommt. Dabei entgleitet den Teams der Überblick über die tatsächlich notwendigen Daten für einen einzelnen Tests und ein übergroßer universeller Satz wird für alle Tests verwendet. Das Team kann zwar immer sicher sein, dass für jeden Test die notwendigen Daten vorhanden sind, aber es verliert unnötig Zeit bei der Durchführung der Tests. Dadurch wird das Team ausgebremst und es ist außerdem ein Zeichen, dass ein Team die Kontrolle über die Software verliert.

Nur für die Tests geschrieben

Der Testcode ruft Methoden im Produktcode auf und prüft Ergebnisse die der Produktcode liefert. Dafür greift er an Stellen ein, die sich durch die lose Kopplung zwischen den Produktkomponenten ergeben. Bei Test Driven Development, also dem Schreiben von Test bevor der Produktcode entwickelt wird, verschränkt sich dieses Miteinander noch mehr. Den hier ergibt sich die lose Kopplung auch durch die Test.

Manche Entwickler fügen jedoch in den Programmcode öffentliche Konstanten, Methoden oder Konstruktoren ein, die Anfangs nur im Testcode verwendet werden. Meistens aus falsch verstandener Optimierung des Testcodes. Die Tests sind so zwar einfacher zu schreiben, aber es entsteht Produktcode, der nur im Testumfeld korrekt funktioniert.  Wird der Code nicht mehr benötigt, dann entsteht so schwer zu entdeckender toter Code oder dieser Code wird fälschlicherweise im Produktcode verwendet. Manchmal findet man ganze Klassen, die nur für die Tests im Produktcode vorgehalten werden.

 

 

Nur noch assertThat in Unit Tests

Mit JUnit 4.4 wurde die Hamcrest Bibliothek über die unscheinbare Methode assertThat Bestandteil des Test Frameworks. Obwohl schon einige Zeit vergangen ist, haben viele Entwickler das Potential dieser Ergänzung noch nicht entdeckt.

Bislang werden in den Unit Test die aktuellen Werte gegen die zu erwartenden Werte geprüft. Am beliebtesten sind da die Methoden assertEquals und assertTrue.

assertEquals("Hello World", object.toString());
assertTrue(file.exists());

In der ersten Zeile wird geprüft ob das Resultat der Methode toString mit dem Erwartungswert übereinstimmt und in der zweiten Zeile wird geprüft ob der übergebene Ausdruck true zurückliefert. Neben diesen beiden Methoden gibt es noch eine lange Liste weiterer Methoden, die unter anderen Arrays und Collections auf Gleichheit, Objekte auf Identität und reelle Zahlen auf einen Näherungswert testen. Zur besseren Lesbarkeit werden die assert Methoden über einen statischen Import eingefügt und der Klassenname Assert kann entfallen.

Der Ansatz bei der assertThat Methode ist ein anderer, hier wird ein Matcher  gegen das aktuelle Objekt ausgewertet. Die Schnittstelle ist sehr flexibel und erlaub eine Vielzahl von Prüfungen auf dem Objekt.

assertThat(object.toString(), is(equalTo("Hello World"));
assertThat(file, exists());

Bei dieser Methode steht das zu testende Objekt links und der Ausdruck, der erfüllt werden muss auf der linken Seite.  In der ersten Zeile wird mit dem equalTo Matcher geprüft, ob die Methode den Ausdruck „Hello World“ liefert und in den zweiten Zeile wird mit einem selbstgeschriebenen Matcher die Existenz der Datei geprüft.  Wenn man sich eine bessere Lesbarkeit wünscht, kann man wie oben geschehen den Matcher is einfügen.

Neben der guten Lesbarkeit und der Erweiterbarkeit trumpft der Matcher Ansatz aber noch mit einigen weiteren Vorteilen auf. Die Matcher sind typsicher und miteinander kombinierbar. Im folgenden sind die assertEquals und die assertThat im fehlerhaften Einsatz. Während  assertEquals unerkannt einen falschen Vergleich durchführt, kann der assertThat Ausdruck erst gar nicht kompilieren.

assertEquals(object.hashCode(), "HelloWorld");
assertThat(object.hashCode(), equalTo("Hello World"));

Die Matcher können auf viele Arten miteinander kombiniert werden. Eine sehr einfache Kombination ist die Verwendung des not Matchers oder die Anwendung eines Matchers auf eine Liste.

assertThat(file, not(exists()));
assertThat(listOfFiles, everyItem(exists());
assertThat(listOfFiles, hasItem(exists());

In der ersten Zeile wird geprüft, ob die Datei nicht existiert und in den beiden anderen Zeilen, ob jede Datei in der Liste oder mindestens eine Datei existiert. Häufig müssen mehrere Bedingungen auf einem Object geprüft werden, auch solche Prüfungen sind mit den bereitgestellten Matchern leicht formulierbar:

assertThat(marxBrother, anyOf(chico, harpo, groucho, gummo, zeppo));
assertThat(subject, either(pinky).or(brain));

Und wem die vorhandenen Matcher nicht ausreichen, der kann sich recht einfach eigene schreiben. Der oben vorgestellte exists Matcher könnte wie folgt formuliert werden.

public static Matcher<Path> exists() {
  return new FeatureMatcher<Path, Boolean>(Matchers.is(true), "exists", "exists") {
    @Override
    protected Boolean featureValueOf(Path actual) {
      return Files.exists(actual);
    }
  };
}

Die statische Methode exists liefert einen FeatureMatcher, der für ein Path Objekt true zurückliefert, wenn die Datei existiert, ansonsten false.

Abschließend kann man allen Entwicklern die Verwendung der assertThat Methode und dem Hamcrest Framework nur wärmsten ans Herz legen. Die Kombinierbarkeit und Erweiterbarkeit der Matcher führt zu übersichtlichen, kompakt formulierten Unit Test, die Entwicklungs- und Einarbeitungszeit sparen.