1. 概述

Hamcrest 提供静态匹配器,帮助简化单元测试断言并提升可读性。你可以从这里开始探索部分可用匹配器。

本文将深入讲解数字相关的匹配器。

2. 环境配置

引入 Hamcrest 只需在 pom.xml 中添加以下 Maven 依赖:

<dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>java-hamcrest</artifactId>
    <version>2.0.0.0</version>
</dependency>

最新版本可在 Maven Central 查询。

3. 近似值匹配器

第一组匹配器用于检测元素是否接近某个值(允许误差范围内)
形式化定义:

value - error <= element <= value + error

若上述条件成立,断言通过。我们直接看代码示例:

3.1. isClose 处理 Double 类型

假设有一个 double 变量 actual,需验证其是否接近 1 ± 0.5
即:

1 - 0.5 <= actual <= 1 + 0.5
    0.5 <= actual <= 1.5

使用 isClose 匹配器编写测试:

@Test
public void givenADouble_whenCloseTo_thenCorrect() {
    double actual = 1.3;
    double operand = 1;
    double error = 0.5;
 
    assertThat(actual, closeTo(operand, error));
}

✅ 1.3 在 [0.5, 1.5] 区间内,测试通过。
反向场景测试:

@Test
public void givenADouble_whenNotCloseTo_thenCorrect() {
    double actual = 1.6;
    double operand = 1;
    double error = 0.5;
 
    assertThat(actual, not(closeTo(operand, error)));
}

3.2. isClose 处理 BigDecimal 类型

isClose 方法已重载,可同样用于 BigDecimal 对象:

@Test
public void givenABigDecimal_whenCloseTo_thenCorrect() {
    BigDecimal actual = new BigDecimal("1.0003");
    BigDecimal operand = new BigDecimal("1");
    BigDecimal error = new BigDecimal("0.0005");
    
    assertThat(actual, is(closeTo(operand, error)));
}

@Test
public void givenABigDecimal_whenNotCloseTo_thenCorrect() {
    BigDecimal actual = new BigDecimal("1.0006");
    BigDecimal operand = new BigDecimal("1");
    BigDecimal error = new BigDecimal("0.0005");
    
    assertThat(actual, is(not(closeTo(operand, error))));
}

⚠️ is 匹配器仅装饰其他匹配器,不增加额外逻辑,仅提升可读性。

4. 顺序匹配器

顾名思义,这类匹配器用于验证元素间的顺序关系
包含五种匹配器:

  • comparesEqualTo
  • greaterThan
  • greaterThanOrEqualTo
  • lessThan
  • lessThanOrEqualTo

4.1. 处理 Integer 类型

最常见场景是数字比较,直接看示例:

@Test
public void given5_whenComparesEqualTo5_thenCorrect() {
    Integer five = 5;
    
    assertThat(five, comparesEqualTo(five));
}

@Test
public void given5_whenNotComparesEqualTo7_thenCorrect() {
    Integer seven = 7;
    Integer five = 5;

    assertThat(five, not(comparesEqualTo(seven)));
}

@Test
public void given7_whenGreaterThan5_thenCorrect() {
    Integer seven = 7;
    Integer five = 5;
 
    assertThat(seven, is(greaterThan(five)));
}

@Test
public void given7_whenGreaterThanOrEqualTo5_thenCorrect() {
    Integer seven = 7;
    Integer five = 5;
 
    assertThat(seven, is(greaterThanOrEqualTo(five)));
}

@Test
public void given5_whenGreaterThanOrEqualTo5_thenCorrect() {
    Integer five = 5;
 
    assertThat(five, is(greaterThanOrEqualTo(five)));
}

@Test
public void given3_whenLessThan5_thenCorrect() {
   Integer three = 3;
   Integer five = 5;
 
   assertThat(three, is(lessThan(five)));
}

@Test
public void given3_whenLessThanOrEqualTo5_thenCorrect() {
   Integer three = 3;
   Integer five = 5;
 
   assertThat(three, is(lessThanOrEqualTo(five)));
}

@Test
public void given5_whenLessThanOrEqualTo5_thenCorrect() {
   Integer five = 5;
 
   assertThat(five, is(lessThanOrEqualTo(five)));
}

4.2. 处理 String 类型

虽然数字比较很直观,但顺序匹配器可应用于任何实现 Comparable 接口的类
String 为例:

@Test
public void givenBenjamin_whenGreaterThanAmanda_thenCorrect() {
    String amanda = "Amanda";
    String benjamin = "Benjamin";
 
    assertThat(benjamin, is(greaterThan(amanda)));
}

@Test
public void givenAmanda_whenLessThanBenajmin_thenCorrect() {
    String amanda = "Amanda";
    String benjamin = "Benjamin";
 
    assertThat(amanda, is(lessThan(benjamin)));
}

String 通过 compareTo 实现字典序比较,因此 "Amanda" 排在 "Benjamin" 前面。

4.3. 处理 LocalDate 类型

日期比较同样适用,看 LocalDate 示例:

@Test
public void givenToday_whenGreaterThanYesterday_thenCorrect() {
    LocalDate today = LocalDate.now();
    LocalDate yesterday = today.minusDays(1);
 
    assertThat(today, is(greaterThan(yesterday)));
}

@Test
public void givenToday_whenLessThanTomorrow_thenCorrect() {
    LocalDate today = LocalDate.now();
    LocalDate tomorrow = today.plusDays(1);
    
    assertThat(today, is(lessThan(tomorrow)));
}

assertThat(today, is(lessThan(tomorrow))) 的可读性接近自然语言。

4.4. 处理自定义类

通过实现 Comparable 接口,可为自定义类定义排序规则
创建 Person 类:

public class Person {
    String name;
    int age;

    // 标准构造器、getter/setter
}

实现 Comparable 接口:

public class Person implements Comparable<Person> {
        
    // ...

    @Override
    public int compareTo(Person o) {
        if (this.age == o.getAge()) return 0;
        if (this.age > o.getAge()) return 1;
        else return -1;
    }
}

按年龄比较,编写测试:

@Test
public void givenAmanda_whenOlderThanBenjamin_thenCorrect() {
    Person amanda = new Person("Amanda", 20);
    Person benjamin = new Person("Benjamin", 18);
 
    assertThat(amanda, is(greaterThan(benjamin)));
}

@Test
public void 
givenBenjamin_whenYoungerThanAmanda_thenCorrect() {
    Person amanda = new Person("Amanda", 20);
    Person benjamin = new Person("Benjamin", 18);
 
    assertThat(benjamin, is(lessThan(amanda)));
}

匹配器将基于自定义的 compareTo 逻辑工作。

5. NaN 匹配器

Hamcrest 提供额外匹配器检测非数字值

@Test
public void givenNaN_whenIsNotANumber_thenCorrect() {
    double zero = 0d;
    
    assertThat(zero / zero, is(notANumber()));
}

6. 总结

数字匹配器极大简化了常见断言场景
Hamcrest 匹配器整体具有自解释性和高可读性
结合自定义比较逻辑的能力,使其成为大多数项目的强大工具。

本文完整示例代码可在 GitHub 获取。


原始标题:Using Hamcrest Number Matchers