Hamcrest测试

Testing with Hamcrest

1.概述

Hamcrest是Java生态系统中用于单元测试的著名框架。 它捆绑在JUnit中,简而言之,它使用现有谓词(称为Matcher类)进行断言。

在本教程中,我们将探索Hamcrest API,并学习如何利用它为我们的软件编写更整洁,更直观的单元测试。

2. Hamcrest设置

通过将以下依赖项添加到我们的pom.xml文件中,我们可以将Hamcrest与maven结合使用:

1
2
3
4
5
<dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest-all</artifactId>
    <version>1.3</version>
</dependency>

始终可以在这里找到该库的最新版本。

3.示例测试

Hamcrest通常与junit和其他测试框架一起使用以进行断言。 具体而言,我们仅使用API的单个assertThat语句和适当的匹配器,而不使用junit的大量assert方法。

让我们看一个示例,该示例测试两个String是否相等。 这应该使我们对Hamcrest如何适合测试方法有一个清晰的认识:

1
2
3
4
5
6
7
8
9
public class StringMatcherTest {
   
    @Test
    public void given2Strings_whenEqual_thenCorrect() {
        String a ="foo";
        String b ="FOO";
        assertThat(a, equalToIgnoringCase(b));
    }
}

在以下各节中,我们将介绍Hamcrest提供的其他几种常见的匹配器。

4.对象匹配器

Hamcrest提供了用于对任意Java对象进行断言的匹配器。

要断言一个对象的toString方法返回一个指定的String:

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

我们还可以检查一个类是否是另一个类的子类:

1
2
3
4
5
@Test
public void given2Classes_whenOneInheritsFromOther_thenCorrect(){
        assertThat(Cat.class,typeCompatibleWith(Animal.class));
    }
}

5.豆类比赛

我们可以使用Hamcrest的Bean匹配器来检查Java Bean的属性。

假定以下Person Bean:

1
2
3
4
5
6
7
8
9
public class Person {
    String name;
    String address;

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

我们可以检查bean是否具有属性,名称如下:

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

我们还可以检查Person是否具有初始化为New York的address属性:

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

我们还可以检查两个Person对象是否构造为具有相同的值:

1
2
3
4
5
6
@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.集合匹配器

Hamcrest提供了用于检查集合的匹配器。

简单检查以找出Collection是否为空:

1
2
3
4
5
@Test
public void givenCollection_whenEmpty_thenCorrect() {
    List<String> emptyList = new ArrayList<>();
    assertThat(emptyList, empty());
}

要检查集合的大小:

1
2
3
4
5
6
@Test
public void givenAList_whenChecksSize_thenCorrect() {
    List<String> hamcrestMatchers = Arrays.asList(
     "collections","beans","text","number");
    assertThat(hamcrestMatchers, hasSize(4));
}

我们还可以使用它来断言数组具有所需的大小:

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

要检查集合是否包含给定的成员,而不考虑顺序:

1
2
3
4
5
6
7
@Test
public void givenAListAndValues_whenChecksListForGivenValues_thenCorrect() {
    List<String> hamcrestMatchers = Arrays.asList(
     "collections","beans","text","number");
    assertThat(hamcrestMatchers,
    containsInAnyOrder("beans","text","collections","number"));
}

要进一步断言Collection成员是给定顺序的,请执行以下操作:

1
2
3
4
5
6
7
@Test
public void givenAListAndValues_whenChecksListForGivenValuesWithOrder_thenCorrect() {
    List<String> hamcrestMatchers = Arrays.asList(
     "collections","beans","text","number");
    assertThat(hamcrestMatchers,
    contains("collections","beans","text","number"));
}

要检查数组是否具有单个给定元素:

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

我们还可以将替代匹配器用于同一测试:

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

或者,我们仍然可以对其他匹配器执行相同的操作,如下所示:

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

我们还可以检查数组是否包含给定元素,而与顺序无关:

1
2
3
4
5
6
7
@Test
public void givenArrayAndValues_whenValuesFoundInArray_thenCorrect() {
    String[] hamcrestMatchers = {"collections","beans","text","number" };
      assertThat(hamcrestMatchers,
    arrayContainingInAnyOrder("beans","collections","number",
     "text"));
}

要检查数组是否包含给定元素但按给定顺序排列:

1
2
3
4
5
6
@Test
public void givenArrayAndValues_whenValuesFoundInArrayInOrder_thenCorrect() {
    String[] hamcrestMatchers = {"collections","beans","text","number" };
    assertThat(hamcrestMatchers,
    arrayContaining("collections","beans","text","number"));
}

当我们的Collection是Map时,我们可以在这些函数中使用以下匹配器:

要检查它是否包含给定密钥:

1
2
3
4
5
6
@Test
public void givenMapAndKey_whenKeyFoundInMap_thenCorrect() {
    Map<String, String> map = new HashMap<>();
    map.put("blogname","baeldung");
    assertThat(map, hasKey("blogname"));
}

和给定的值:

1
2
3
4
5
6
@Test
public void givenMapAndValue_whenValueFoundInMap_thenCorrect() {
    Map<String, String> map = new HashMap<>();
    map.put("blogname","baeldung");
    assertThat(map, hasValue("baeldung"));
}

最后是给定的条目(键,值):

1
2
3
4
5
6
@Test
public void givenMapAndEntry_whenEntryFoundInMap_thenCorrect() {
    Map<String, String> map = new HashMap<>();
    map.put("blogname","baeldung");
    assertThat(map, hasEntry("blogname","baeldung"));
}

7.号码匹配器

Number匹配器用于对Number类的变量执行断言。

要检查大于条件:

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

检查大于或等于条件:

1
2
3
4
@Test
public void givenAnInteger_whenGreaterThanOrEqTo5_thenCorrect() {
    assertThat(5, greaterThanOrEqualTo(5));
}

要检查少于状况:

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

检查小于或等于条件:

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

要检查closeTo条件:

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

让我们密切注意最后一个匹配器closeTo。 第一个参数是操作数,是与目标进行比较的参数,第二个参数是与操作数的允许偏差。 这意味着如果目标是操作数+偏差或操作数偏差,则测试将通过。

8.文字匹配器

Hamcrest的文本匹配器使对字符串的断言更加轻松,整洁和直观。 我们将在本节中对其进行研究。

要检查字符串是否为空:

1
2
3
4
5
@Test
public void givenString_whenEmpty_thenCorrect() {
    String str ="";
    assertThat(str, isEmptyString());
}

要检查字符串是否为空或空:

1
2
3
4
5
@Test
public void givenString_whenEmptyOrNull_thenCorrect() {
    String str = null;
    assertThat(str, isEmptyOrNullString());
}

要在忽略空格的情况下检查两个字符串是否相等:

1
2
3
4
5
6
@Test
public void given2Strings_whenEqualRegardlessWhiteSpace_thenCorrect() {
    String str1 ="text";
    String str2 =" text";
    assertThat(str1, equalToIgnoringWhiteSpace(str2));
}

我们还可以按照给定的顺序检查给定String中是否存在一个或多个子字符串:

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

最后,无论大小写如何,我们都可以检查两个字符串是否相等:

1
2
3
4
5
6
@Test
 public void given2Strings_whenEqual_thenCorrect() {
    String a ="foo";
    String b ="FOO";
    assertThat(a, equalToIgnoringCase(b));
}

9.核心API

Hamcrest核心API将由第三方框架提供程序使用。 但是,它为我们提供了一些很棒的结构,以使我们的单元测试更具可读性,还提供了一些可以轻松使用的核心匹配器。

匹配器上的is结构的可读性:

1
2
3
4
5
6
@Test
public void given2Strings_whenIsEqualRegardlessWhiteSpace_thenCorrect() {
    String str1 ="text";
    String str2 =" text";
    assertThat(str1, is(equalToIgnoringWhiteSpace(str2)));
}

is结构基于简单的数据类型:

1
2
3
4
5
6
@Test
public void given2Strings_whenIsEqual_thenCorrect() {
    String str1 ="text";
    String str2 ="text";
    assertThat(str1, is(str2));
}

否定匹配器上的not构造:

1
2
3
4
5
6
@Test
public void given2Strings_whenIsNotEqualRegardlessWhiteSpace_thenCorrect() {
    String str1 ="text";
    String str2 =" texts";
    assertThat(str1, not(equalToIgnoringWhiteSpace(str2)));
}

在简单数据类型上的not构造:

1
2
3
4
5
6
@Test
public void given2Strings_whenNotEqual_thenCorrect() {
    String str1 ="text";
    String str2 ="texts";
    assertThat(str1, not(str2));
}

检查字符串是否包含给定的子字符串:

1
2
3
4
5
6
@Test
public void givenAStrings_whenContainsAnotherGivenString_thenCorrect() {
    String str1 ="calligraphy";
    String str2 ="call";
    assertThat(str1, containsString(str2));
}

检查字符串是否以给定的子字符串开头:

1
2
3
4
5
6
@Test
public void givenAString_whenStartsWithAnotherGivenString_thenCorrect() {
    String str1 ="calligraphy";
    String str2 ="call";
    assertThat(str1, startsWith(str2));
}

检查字符串是否以给定的子字符串结尾:

1
2
3
4
5
6
@Test
public void givenAString_whenEndsWithAnotherGivenString_thenCorrect() {
    String str1 ="calligraphy";
    String str2 ="phy";
    assertThat(str1, endsWith(str2));
}

检查两个对象是否属于同一实例:

1
2
3
4
5
@Test
public void given2Objects_whenSameInstance_thenCorrect() {
    Cat cat=new Cat();
    assertThat(cat, sameInstance(cat));
}

检查对象是否是给定类的实例:

1
2
3
4
5
@Test
public void givenAnObject_whenInstanceOfGivenClass_thenCorrect() {
    Cat cat=new Cat();
    assertThat(cat, instanceOf(Cat.class));
}

检查集合的所有成员是否满足条件:

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

检查字符串是否不为空:

1
2
3
4
5
@Test
public void givenString_whenNotNull_thenCorrect() {
    String str ="notnull";
    assertThat(str, notNullValue());
}

将条件链接在一起,当目标满足任何条件时测试通过,类似于逻辑或:

1
2
3
4
5
6
7
@Test
public void givenString_whenMeetsAnyOfGivenConditions_thenCorrect() {
    String str ="calligraphy";
    String start ="call";
    String end ="foo";
    assertThat(str, anyOf(startsWith(start), containsString(end)));
}

将条件链接在一起,仅在目标满足所有条件时才通过测试,类似于逻辑与:

1
2
3
4
5
6
7
@Test
public void givenString_whenMeetsAllOfGivenConditions_thenCorrect() {
    String str ="calligraphy";
    String start ="call";
    String end ="phy";
    assertThat(str, allOf(startsWith(start), endsWith(end)));
}

10.自定义匹配器

我们可以通过扩展TypeSafeMatcher来定义自己的匹配器。 在本节中,我们将创建一个自定义匹配器,该匹配器仅在目标为正整数时才允许测试通过。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class IsPositiveInteger extends TypeSafeMatcher<Integer> {

    public void describeTo(Description description) {
        description.appendText("a positive integer");
    }

    @Factory
    public static Matcher<Integer> isAPositiveInteger() {
        return new IsPositiveInteger();
    }

    @Override
    protected boolean matchesSafely(Integer integer) {
        return integer > 0;
    }

}

我们只需要实现matchSafely方法(该方法检查目标确实是一个正整数),以及实现describeTo方法(如果测试未通过的话,该方法将生成失败消息)。

这是使用我们新的自定义匹配器的测试:

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

由于传递了一个非正整数,这是一条失败消息:

1
java.lang.AssertionError: Expected: a positive integer but: was <-1>

11.结论

在本教程中,我们探索了Hamcrest API,并了解了如何使用它编写更好和更可维护的单元测试。

所有这些示例和代码段的完整实现都可以在我的Hamcrest github项目中找到。