1. 概览

Hamcrest 是Java生态中广为人知的单元测试框架。它已集成在JUnit中,简单来说,它通过使用预定义的匹配器(matcher)来执行断言。

本文将深入探索Hamcrest的API,展示如何利用它编写更整洁、更直观的单元测试。

2. Hamcrest环境搭建

通过Maven使用Hamcrest,需在pom.xml中添加以下依赖:

<dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest-all</artifactId>
    <version>1.3</version>
</dependency>

最新版本可在此查找

3. 示例测试

Hamcrest常与JUnit等测试框架配合使用。核心特点是——摒弃JUnit的多种assert方法,统一使用assertThat配合匹配器

来看个案例:测试两个字符串忽略大小写是否相等,直观展示Hamctest的用法:

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

后续章节将介绍Hamcrest提供的其他常用匹配器。

4. 对象匹配器

Hamcrest为任意Java对象提供断言匹配器。

验证toString()输出

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

验证继承关系

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

5. JavaBean匹配器

Hamcrest的Bean匹配器用于检查JavaBean属性。

假设有以下Person类:

public class Person {
    String name;
    String address;

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

检查属性是否存在

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

检查属性值是否匹配

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

检查两个Bean属性值相等

@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提供丰富的集合操作匹配器。

检查空集合

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

检查集合大小

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

检查数组大小

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

检查集合元素(无序)

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

检查集合元素(有序)

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

检查数组包含特定元素

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

替代写法:元素在数组中

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

替代写法:元素存在于数组

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

检查数组包含所有元素(无序)

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

检查数组包含所有元素(有序)

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

Map操作:检查Key存在

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

Map操作:检查Value存在

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

Map操作:检查Entry存在

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

7. 数字匹配器

用于数字类型断言,操作简单粗暴。

大于判断

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

大于等于判断

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

小于判断

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

小于等于判断

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

浮点数近似判断

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

注意closeTo的第二个参数是允许偏差。测试通过条件:目标值在[1-0.5, 1+0.5]区间内。

8. 文本匹配器

让字符串断言更直观,避免手动处理各种边界情况。

检查空字符串

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

检查空或null字符串

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

忽略空白字符比较

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

检查子串顺序存在

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

忽略大小写比较

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

9. 核心API

Hamcrest核心API虽面向框架开发者,但提供了增强可读性的语法和基础匹配器。

使用is增强可读性

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

is用于基本类型

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

使用not取反

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

not用于基本类型

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

检查包含子串

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

检查前缀

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

检查后缀

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

检查同一实例

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

检查实例类型

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

检查集合所有元素满足条件

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

检查非null

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

逻辑OR条件(满足任一)

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

逻辑AND条件(全部满足)

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

10. 自定义匹配器

通过继承TypeSafeMatcher创建自定义匹配器。以下示例实现正整数校验

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;
    }

}

关键点

  • matchesSafely:实现核心校验逻辑
  • describeTo:提供失败时的描述信息

使用自定义匹配器

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

失败案例输出

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

11. 总结

本文深入探索了Hamcrest API,展示了如何利用它编写更优雅、更易维护的单元测试。所有示例代码可在我的Hamcrest GitHub项目中获取完整实现。


原始标题:Testing with Hamcrest

« 上一篇: JSON Schema简介