Menguji dengan Hamcrest

1. Gambaran keseluruhan

Hamcrest adalah kerangka yang terkenal yang digunakan untuk pengujian unit dalam ekosistem Jawa. Ia digabungkan dalam JUnit dan secara sederhana, ia menggunakan predikat yang ada - dipanggil kelas matcher - untuk membuat penegasan.

Dalam tutorial ini, kami akan meneroka Hamcrest API dan belajar bagaimana memanfaatkannya untuk menulis ujian unit yang lebih kemas dan intuitif untuk perisian kami.

2. Persediaan Hamcrest

Kita boleh menggunakan Hamcrest dengan maven dengan menambahkan kebergantungan berikut ke fail pom.xml kami :

 org.hamcrest hamcrest-all 1.3 

Versi terbaru perpustakaan ini selalu terdapat di sini.

3. Ujian Contoh

Hamcrest biasanya digunakan dengan kerangka kerja junit dan ujian lain untuk membuat penegasan. Secara khusus, dan bukannya menggunakan junit 's banyak ceritakan kaedah, kita hanya menggunakan single API assertThat kenyataan dengan matchers sesuai.

Mari kita lihat contoh yang menguji dua String untuk kesamaan tanpa mengira kes. Ini akan memberi kita idea yang jelas tentang bagaimana Hamcrest sesuai dengan kaedah ujian:

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

Dalam bahagian berikut, kita akan melihat beberapa tawaran Hamcrest matcher biasa yang lain .

4. Pemadan Objek

Hamcrest menyediakan pemadan untuk membuat pernyataan mengenai objek Java yang sewenang-wenangnya.

Untuk menegaskan bahawa kaedah toString 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 bahawa satu kelas adalah sub-kelas yang lain:

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

5. Pemadan Kacang

Kita boleh menggunakan Hamcrest 's Bean matcher untuk memeriksa sifat kacang Jawa.

Anggap kacang Orang berikut :

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

Kami dapat memeriksa sama ada kacang mempunyai harta tanah, namanya seperti:

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

Kami juga dapat memeriksa sama ada Orang mempunyai harta alamat , yang dimulakan 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 sama ada objek dua Orang dibina 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. Pemadan Koleksi

Hamcrest menyediakan pemadan untuk memeriksa Koleksi s.

Pemeriksaan mudah untuk mengetahui sama ada 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)); }

Kami juga dapat menggunakannya untuk menegaskan bahawa array mempunyai ukuran yang diperlukan:

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

Untuk memeriksa sama ada Koleksi mengandungi ahli yang diberikan, tanpa mengira pesanan:

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

Untuk lebih menegaskan bahawa ahli Koleksi mengikut urutan tertentu:

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

Untuk memeriksa sama ada array mempunyai satu elemen tertentu:

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

Kami juga boleh menggunakan pencocokan alternatif untuk ujian yang sama:

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

Atau masih kita boleh melakukan perkara yang sama dengan pemadan yang berbeza 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 mengandungi elemen yang diberikan tanpa mengira urutan:

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

Untuk memeriksa apakah array mengandungi elemen yang diberikan tetapi mengikut urutan yang diberikan:

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

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

Untuk memeriksa sama ada ia mengandungi 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 (kunci, nilai):

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

7. Pemadan Nombor

The Nombor matchers digunakan untuk melakukan dakwaan pada pembolehubah daripada Nombor kelas.

Untuk memeriksa keadaan Than yang lebih besar :

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

Untuk memeriksa keadaan yang lebih besar atau sama Untuk keadaan:

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

Untuk memeriksa keadaan Than yang kurang :

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

Untuk memeriksa kurangThan atau samaKeadaan :

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

Untuk memeriksa keadaan tutup :

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

Pelaksanaan penuh semua contoh dan coretan kod ini boleh didapati di projek github Hamcrest saya.