Menguji dengan Hamcrest

1. Ikhtisar

Hamcrest adalah kerangka kerja terkenal yang digunakan untuk pengujian unit di ekosistem Java. Ini dibundel dalam JUnit dan sederhananya, ia menggunakan predikat yang ada - disebut kelas matcher - untuk membuat pernyataan.

Dalam tutorial ini, kita akan menjelajahi Hamcrest API dan mempelajari bagaimana memanfaatkannya untuk menulis pengujian unit yang lebih rapi dan lebih intuitif untuk perangkat lunak kita.

2. Pengaturan Hamcrest

Kita bisa menggunakan Hamcrest dengan maven dengan menambahkan dependensi berikut ke file pom.xml kita :

 org.hamcrest hamcrest-all 1.3 

Versi terbaru pustaka ini selalu dapat ditemukan di sini.

3. Tes Contoh

Hamcrest biasanya digunakan dengan junit dan kerangka kerja pengujian lainnya untuk membuat pernyataan. Secara khusus, alih-alih menggunakan banyak metode assert dari junit , kami hanya menggunakan pernyataan assertThat tunggal API dengan pencocokan yang sesuai.

Mari kita lihat contoh yang menguji dua String untuk persamaan apa pun kasusnya. Ini akan memberi kita gambaran yang jelas tentang bagaimana Hamcrest cocok dengan metode pengujian:

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

Pada bagian berikut, kita akan melihat beberapa matcher umum lainnya yang ditawarkan Hamcrest .

4. Pencocokan Objek

Hamcrest menyediakan matcher untuk membuat pernyataan pada objek Java yang berubah-ubah.

Untuk menegaskan bahwa metode toString dari sebuah Objek mengembalikan String yang ditentukan :

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

Kami juga dapat memeriksa bahwa satu kelas adalah sub-kelas dari kelas lain:

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

5. Bean Matcher

Kita dapat menggunakan pencocok Bean Hamcrest untuk memeriksa properti kacang Java.

Asumsikan kacang Person berikut :

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

Kami dapat memeriksa apakah kacang memiliki properti, namanya seperti:

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

Kami juga dapat memeriksa apakah Orang memiliki properti alamat , diinisialisasi ke New York:

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

Kami juga dapat memeriksa apakah dua objek Person dibangun dengan nilai yang sama:

@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. Pencocokan Koleksi

Hamcrest menyediakan korek api untuk memeriksa Koleksi .

Pemeriksaan sederhana untuk mengetahui apakah Koleksi kosong:

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

Untuk memeriksa ukuran Koleksi:

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

Kita juga bisa menggunakannya untuk menegaskan bahwa sebuah array memiliki ukuran yang dibutuhkan:

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

Untuk memeriksa apakah Koleksi berisi anggota tertentu, apa pun urutannya:

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

Untuk lebih menegaskan bahwa anggota Collection diberi urutan:

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

Untuk memeriksa apakah sebuah array memiliki satu elemen tertentu:

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

Kami juga dapat menggunakan pencocokan alternatif untuk pengujian yang sama:

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

Atau tetap kita bisa melakukan hal yang sama dengan matcher yang berbeda seperti:

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

Kami juga dapat memeriksa apakah array berisi elemen yang diberikan terlepas dari urutannya:

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

Untuk memeriksa apakah array berisi elemen yang diberikan tetapi dalam urutan yang diberikan:

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

Jika Koleksi kami adalah Peta, kami dapat menggunakan pencocokan berikut dalam fungsi masing-masing:

Untuk memeriksa apakah itu berisi kunci yang diberikan:

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

dan nilai yang diberikan:

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

dan akhirnya entri yang diberikan (key, value):

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

7. Pencocok Nomor

The Number matchers digunakan untuk melakukan pernyataan pada variabel dari Nomor kelas.

Untuk memeriksa greaterThan kondisi:

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

Untuk memeriksa greaterThan atau EqualTo kondisi:

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

Untuk memeriksa kurang dari kondisi:

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

Untuk memeriksa kurang dari atau sama dengan kondisi:

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

Untuk memeriksa kondisi closeTo :

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

Mari kita perhatikan matcher terakhir, closeTo. Argumen pertama, operan, adalah yang mana target dibandingkan dan argumen kedua adalah deviasi yang diijinkan dari operan . Artinya jika targetnya adalah operan + deviasi atau operan-deviasi, maka pengujian akan lolos.

8. Pencocokan Teks

Penegasan pada String menjadi lebih mudah, lebih rapi dan lebih intuitif dengan text matcher Hamcrest . Kami akan melihatnya di bagian ini.

Untuk memeriksa apakah String kosong:

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

Untuk memeriksa apakah String kosong atau null :

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

Untuk memeriksa kesetaraan dua String s sambil mengabaikan spasi:

@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.

Implementasi lengkap dari semua contoh dan cuplikan kode ini dapat ditemukan di proyek github Hamcrest saya.