Testen mit Hamcrest

1. Übersicht

Hamcrest ist das bekannte Framework für Unit-Tests im Java-Ökosystem. Es ist in JUnit gebündelt und verwendet einfach vorhandene Prädikate - Matcher-Klassen genannt -, um Aussagen zu treffen.

In diesem Tutorial werden wir die Hamcrest-API untersuchen und lernen, wie Sie sie nutzen können, um übersichtlichere und intuitivere Komponententests für unsere Software zu schreiben.

2. Hamcrest-Setup

Wir können Hamcrest mit maven verwenden, indem wir unserer Datei pom.xml die folgende Abhängigkeit hinzufügen :

 org.hamcrest hamcrest-all 1.3 

Die neueste Version dieser Bibliothek finden Sie immer hier.

3. Ein Beispieltest

Hamcrest wird häufig mit Junit und anderen Test-Frameworks verwendet, um Aussagen zu treffen . Insbesondere anstelle der Verwendung von JUnit ‚s zahlreichen assert Methoden verwenden wir nur die einzelne API assertThat Aussage mit entsprechenden Matcher.

Schauen wir uns ein Beispiel an, in dem zwei Zeichenfolgen unabhängig vom Fall auf Gleichheit getestet werden. Dies sollte uns eine klare Vorstellung davon geben, wie Hamcrest in eine Testmethode passt:

public class StringMatcherTest { @Test public void given2Strings_whenEqual_thenCorrect() { String a = "foo"; String b = "FOO"; assertThat(a, equalToIgnoringCase(b)); } }

In den folgenden Abschnitten werden wir uns einige andere gemeinsame Matcher ansehen, die Hamcrest anbietet.

4. Der Objekt- Matcher

Hamcrest bietet Matcher, um Aussagen zu beliebigen Java-Objekten zu treffen .

So behaupten Sie, dass die toString- Methode eines Objekts einen angegebenen String zurückgibt :

@Test public void givenBean_whenToStringReturnsRequiredString_thenCorrect(){ Person person=new Person("Barrack", "Washington"); String str=person.toString(); assertThat(person,hasToString(str)); }

Wir können auch überprüfen, ob eine Klasse eine Unterklasse einer anderen ist:

@Test public void given2Classes_whenOneInheritsFromOther_thenCorrect(){ assertThat(Cat.class,typeCompatibleWith(Animal.class)); } }

5. Der Bean Matcher

Wir können Hamcrests Bean Matcher verwenden, um die Eigenschaften einer Java Bean zu untersuchen.

Nehmen Sie die folgende Person Bean an:

public class Person { String name; String address; public Person(String personName, String personAddress) { name = personName; address = personAddress; } }

Wir können überprüfen, ob die Bohne die Eigenschaft hat, Name wie folgt:

@Test public void givenBean_whenHasValue_thenCorrect() { Person person = new Person("Baeldung", 25); assertThat(person, hasProperty("name")); }

Wir können auch überprüfen, ob Person die in New York initialisierte Adresseigenschaft hat :

@Test public void givenBean_whenHasCorrectValue_thenCorrect() { Person person = new Person("Baeldung", "New York"); assertThat(person, hasProperty("address", equalTo("New York"))); }

Wir können auch überprüfen, ob zwei Personenobjekte mit denselben Werten erstellt wurden:

@Test public void given2Beans_whenHavingSameValues_thenCorrect() { Person person1 = new Person("Baeldung", "New York"); Person person2 = new Person("Baeldung", "New York"); assertThat(person1, samePropertyValuesAs(person2)); } 

6. Der Collection Matcher

Hamcrest bietet Matcher für die Inspektion von Sammlungen .

Einfache Überprüfung, um festzustellen, ob eine Sammlung leer ist:

@Test public void givenCollection_whenEmpty_thenCorrect() { List emptyList = new ArrayList(); assertThat(emptyList, empty()); }

So überprüfen Sie die Größe einer Sammlung:

@Test public void givenAList_whenChecksSize_thenCorrect() { List hamcrestMatchers = Arrays.asList( "collections", "beans", "text", "number"); assertThat(hamcrestMatchers, hasSize(4)); }

Wir können es auch verwenden, um zu behaupten, dass ein Array eine erforderliche Größe hat:

@Test public void givenArray_whenChecksSize_thenCorrect() { String[] hamcrestMatchers = { "collections", "beans", "text", "number" }; assertThat(hamcrestMatchers, arrayWithSize(4)); }

So überprüfen Sie, ob eine Sammlung bestimmte Mitglieder enthält, unabhängig von der Reihenfolge:

@Test public void givenAListAndValues_whenChecksListForGivenValues_thenCorrect() { List hamcrestMatchers = Arrays.asList( "collections", "beans", "text", "number"); assertThat(hamcrestMatchers, containsInAnyOrder("beans", "text", "collections", "number")); }

Um weiter zu behaupten, dass die Mitglieder der Sammlung in einer bestimmten Reihenfolge sind:

@Test public void givenAListAndValues_whenChecksListForGivenValuesWithOrder_thenCorrect() { List hamcrestMatchers = Arrays.asList( "collections", "beans", "text", "number"); assertThat(hamcrestMatchers, contains("collections", "beans", "text", "number")); }

So überprüfen Sie, ob ein Array ein einzelnes Element enthält:

@Test public void givenArrayAndValue_whenValueFoundInArray_thenCorrect() { String[] hamcrestMatchers = { "collections", "beans", "text", "number" }; assertThat(hamcrestMatchers, hasItemInArray("text")); }

Wir können für denselben Test auch einen alternativen Matcher verwenden:

@Test public void givenValueAndArray_whenValueIsOneOfArrayElements_thenCorrect() { String[] hamcrestMatchers = { "collections", "beans", "text", "number" }; assertThat("text", isOneOf(hamcrestMatchers)); }

Oder wir können dasselbe mit einem anderen Matcher wie folgt machen:

@Test public void givenValueAndArray_whenValueFoundInArray_thenCorrect() { String[] array = new String[] { "collections", "beans", "text", "number" }; assertThat("beans", isIn(array)); }

Wir können auch überprüfen, ob das Array bestimmte Elemente enthält, unabhängig von der Reihenfolge:

@Test public void givenArrayAndValues_whenValuesFoundInArray_thenCorrect() { String[] hamcrestMatchers = { "collections", "beans", "text", "number" }; assertThat(hamcrestMatchers, arrayContainingInAnyOrder("beans", "collections", "number", "text")); }

So überprüfen Sie, ob das Array bestimmte Elemente in der angegebenen Reihenfolge enthält:

@Test public void givenArrayAndValues_whenValuesFoundInArrayInOrder_thenCorrect() { String[] hamcrestMatchers = { "collections", "beans", "text", "number" }; assertThat(hamcrestMatchers, arrayContaining("collections", "beans", "text", "number")); }

Wenn unsere Sammlung eine Karte ist, können wir die folgenden Matcher in diesen jeweiligen Funktionen verwenden:

So überprüfen Sie, ob ein bestimmter Schlüssel enthalten ist:

@Test public void givenMapAndKey_whenKeyFoundInMap_thenCorrect() { Map map = new HashMap(); map.put("blogname", "baeldung"); assertThat(map, hasKey("blogname")); }

und ein gegebener Wert:

@Test public void givenMapAndValue_whenValueFoundInMap_thenCorrect() { Map map = new HashMap(); map.put("blogname", "baeldung"); assertThat(map, hasValue("baeldung")); }

und schließlich einen bestimmten Eintrag (Schlüssel, Wert):

@Test public void givenMapAndEntry_whenEntryFoundInMap_thenCorrect() { Map map = new HashMap(); map.put("blogname", "baeldung"); assertThat(map, hasEntry("blogname", "baeldung")); }

7. Der Number Matcher

Die Number Matcher werden verwendet, um Zusicherungen für Variablen der Number- Klasse durchzuführen .

So überprüfen Sie den Zustand größer als :

@Test public void givenAnInteger_whenGreaterThan0_thenCorrect() { assertThat(1, greaterThan(0)); }

So überprüfen Sie die Bedingung größer als oder gleich :

@Test public void givenAnInteger_whenGreaterThanOrEqTo5_thenCorrect() { assertThat(5, greaterThanOrEqualTo(5)); }

So überprüfen Sie weniger als den Zustand:

@Test public void givenAnInteger_whenLessThan0_thenCorrect() { assertThat(-1, lessThan(0)); }

So überprüfen Sie die Bedingung " weniger als oder gleich" :

@Test public void givenAnInteger_whenLessThanOrEqTo5_thenCorrect() { assertThat(-1, lessThanOrEqualTo(5)); }

So überprüfen Sie den Zustand closeTo :

@Test public void givenADouble_whenCloseTo_thenCorrect() { assertThat(1.2, closeTo(1, 0.5)); }

Let's pay close attention to the last matcher, closeTo. The first argument, the operand, is the one to which the target is compared and the second argument is the allowable deviation from the operand . This means that if the target is operand+deviation or operand-deviation, then the test will pass.

8. The Text Matcher

Assertion on Strings is made easier, neater and more intuitive with Hamcrest‘s text matchers. We are going to take a look at them in this section.

To check if a String is empty:

@Test public void givenString_whenEmpty_thenCorrect() { String str = ""; assertThat(str, isEmptyString()); }

To check if a String is empty or null:

@Test public void givenString_whenEmptyOrNull_thenCorrect() { String str = null; assertThat(str, isEmptyOrNullString()); }

To check for equality of two Strings while ignoring white space:

@Test public void given2Strings_whenEqualRegardlessWhiteSpace_thenCorrect() { String str1 = "text"; String str2 = " text "; assertThat(str1, equalToIgnoringWhiteSpace(str2)); }

We can also check for the presence of one or more sub-strings in a given String in a given order:

@Test public void givenString_whenContainsGivenSubstring_thenCorrect() { String str = "calligraphy"; assertThat(str, stringContainsInOrder(Arrays.asList("call", "graph"))); }

Finally, we can check for equality of two Strings regardless of case:

@Test public void given2Strings_whenEqual_thenCorrect() { String a = "foo"; String b = "FOO"; assertThat(a, equalToIgnoringCase(b)); }

9. The Core API

The Hamcrest core API is to be used by third-party framework providers. However, it offers us some great constructs to make our unit tests more readable and also some core matchers that can be used just as easily.

Readability with the is construct on a matcher:

@Test public void given2Strings_whenIsEqualRegardlessWhiteSpace_thenCorrect() { String str1 = "text"; String str2 = " text "; assertThat(str1, is(equalToIgnoringWhiteSpace(str2))); }

The is construct on a simple data type:

@Test public void given2Strings_whenIsEqual_thenCorrect() { String str1 = "text"; String str2 = "text"; assertThat(str1, is(str2)); }

Negation with the not construct on a matcher:

@Test public void given2Strings_whenIsNotEqualRegardlessWhiteSpace_thenCorrect() { String str1 = "text"; String str2 = " texts "; assertThat(str1, not(equalToIgnoringWhiteSpace(str2))); }

The not construct on a simple data type:

@Test public void given2Strings_whenNotEqual_thenCorrect() { String str1 = "text"; String str2 = "texts"; assertThat(str1, not(str2)); }

Check if a String contains a given sub-string:

@Test public void givenAStrings_whenContainsAnotherGivenString_thenCorrect() { String str1 = "calligraphy"; String str2 = "call"; assertThat(str1, containsString(str2)); }

Check if a String starts with given sub-string:

@Test public void givenAString_whenStartsWithAnotherGivenString_thenCorrect() { String str1 = "calligraphy"; String str2 = "call"; assertThat(str1, startsWith(str2)); }

Check if a String ends with given sub-string:

@Test public void givenAString_whenEndsWithAnotherGivenString_thenCorrect() { String str1 = "calligraphy"; String str2 = "phy"; assertThat(str1, endsWith(str2)); }

Check if two Objects are of the same instance:

@Test public void given2Objects_whenSameInstance_thenCorrect() { Cat cat=new Cat(); assertThat(cat, sameInstance(cat)); }

Check if an Object is an instance of a given class:

@Test public void givenAnObject_whenInstanceOfGivenClass_thenCorrect() { Cat cat=new Cat(); assertThat(cat, instanceOf(Cat.class)); }

Check if all members of a Collection meet a condition:

@Test public void givenList_whenEachElementGreaterThan0_thenCorrect() { List list = Arrays.asList(1, 2, 3); int baseCase = 0; assertThat(list, everyItem(greaterThan(baseCase))); }

Check that a String is not null:

@Test public void givenString_whenNotNull_thenCorrect() { String str = "notnull"; assertThat(str, notNullValue()); }

Chain conditions together, test passes when target meets any of the conditions, similar to logical OR:

@Test public void givenString_whenMeetsAnyOfGivenConditions_thenCorrect() { String str = "calligraphy"; String start = "call"; String end = "foo"; assertThat(str, anyOf(startsWith(start), containsString(end))); }

Chain conditions together, test passes only when target meets all conditions, similar to logical AND:

@Test public void givenString_whenMeetsAllOfGivenConditions_thenCorrect() { String str = "calligraphy"; String start = "call"; String end = "phy"; assertThat(str, allOf(startsWith(start), endsWith(end))); }

10. A Custom Matcher

We can define our own matcher by extending TypeSafeMatcher. In this section, we will create a custom matcher which allows a test to pass only when the target is a positive integer.

public class IsPositiveInteger extends TypeSafeMatcher { public void describeTo(Description description) { description.appendText("a positive integer"); } @Factory public static Matcher isAPositiveInteger() { return new IsPositiveInteger(); } @Override protected boolean matchesSafely(Integer integer) { return integer > 0; } }

We need only to implement the matchSafely method which checks that the target is indeed a positive integer and the describeTo method which produces a failure message in case the test does not pass.

Here is a test that uses our new custom matcher:

@Test public void givenInteger_whenAPositiveValue_thenCorrect() { int num = 1; assertThat(num, isAPositiveInteger()); }

and here is a failure message we get since we have passed in a non-positive integer:

java.lang.AssertionError: Expected: a positive integer but: was 

11. Conclusion

In this tutorial, we have explored the Hamcrest API and learnt how we can write better and more maintainable unit tests with it.

Die vollständige Implementierung all dieser Beispiele und Codefragmente finden Sie in meinem Hamcrest-Github-Projekt.