Contents

Hamcrest 自定义匹配器简介

1. 简介

除了内置的匹配器,Hamcrest 还支持创建自定义匹配器。

在本教程中,我们将仔细研究如何创建和使用它们。要抢先了解可用的匹配器,请参阅这篇文章

2. 自定义匹配器设置

要获得 Hamcrest,我们需要将以下 Maven 依赖项添加到我们的pom.xml中:

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

最新的 Hamcrest 版本可以在Maven Central 上找到。

3. 介绍TypeSafeMatcher

在开始我们的示例之前,了解类TypeSafeMatcher很重要。我们必须扩展这个类来创建我们自己的匹配器。

TypeSafeMatcher是一个抽象类,因此所有子类都必须实现以下方法:

  • matchesSafely(T t):包含我们的匹配逻辑
  • describeTo(Description description) : 自定义当我们的匹配逻辑不满足时客户端将得到的消息

正如我们在第一个方法中看到的那样,**TypeSafeMatcher是参数化的,所以我们在使用它时必须声明一个类型。**这将是我们正在测试的对象的类型。

让我们通过查看下一节中的第一个示例来更清楚地说明这一点。

4. 创建onlyDigits匹配器

对于我们的第一个用例,我们将创建一个匹配器,如果某个string仅包含数字,则该匹配器返回 true。

因此,应用于“123”的onlyDigits应该返回true,而“ hello1 ”和“ bye ”应该返回 false。

让我们开始吧!

4.1. 创建匹配器

从我们的匹配器开始,我们将创建一个扩展TypeSafeMatcher的类:

public class IsOnlyDigits extends TypeSafeMatcher<String> {
   
    @Override
    protected boolean matchesSafely(String s) {
        // ...
    }
    @Override
    public void describeTo(Description description) {
        // ...
    }
}

请注意,由于我们将测试的对象是文本,因此我们使用 String 类参数化TypeSafeMatcher的子类。

现在我们准备添加我们的实现:

public class IsOnlyDigits extends TypeSafeMatcher<String> {
    @Override
    protected boolean matchesSafely(String s) {
        try {
            Integer.parseInt(s);
            return true;
        } catch (NumberFormatException nfe){
            return false;
        }
    }
    @Override
    public void describeTo(Description description) {
        description.appendText("only digits");
    }
}

正如我们所见,matchesSafey正在尝试将我们的输入String解析为Integer。如果成功,则返回true。如果失败,则返回false。它成功地响应了我们的用例。

另一方面,describeTo附加了一个代表我们期望的文本。当我们使用我们的匹配器时,我们将看到接下来如何显示。

我们只需要一件事来完成我们的匹配器:访问它的静态方法,因此它的行为与内置匹配器的其余部分一样。

因此,我们将添加如下内容:

public static Matcher<String> onlyDigits() {
    return new IsOnlyDigits();
}

我们完成了!让我们在下一节中看看如何使用这个匹配器。

4.2. 匹配器使用

使用我们全新的匹配器,我们将创建一个测试

@Test
public void givenAString_whenIsOnlyDigits_thenCorrect() {
    String digits = "1234";
    assertThat(digits, onlyDigits());
}

就是这样。此测试将通过,因为输入string仅包含数字。请记住,为了使其更易读,我们可以使用匹配器is作为任何其他 matcher 的包装器

assertThat(digits, is(onlyDigits()));

最后,如果我们运行相同的测试但输入“123ABC”,输出消息将是:

java.lang.AssertionError: 
Expected: only digits
     but: was "123ABC"

这是我们看到附加到describeTo方法的文本的地方。正如我们可能已经注意到的那样,对测试中的预期内容进行适当的描述非常重要。

5. divisibleBy

那么,如果我们想创建一个匹配器来定义一个数字是否可以被另一个数字整除呢?对于这种情况,我们必须将其中一个参数存储在某处。

让我们看看如何做到这一点:

public class IsDivisibleBy extends TypeSafeMatcher<Integer> {
    private Integer divider;
    // constructors
    @Override
    protected boolean matchesSafely(Integer dividend) {
        if (divider == 0) {
            return false;
        }
        return ((dividend % divider) == 0);
    }
    @Override
    public void describeTo(Description description) {
        description.appendText("divisible by " + divider);
    }
    public static Matcher<Integer> divisibleBy(Integer divider) {
        return new IsDivisibleBy(divider);
    }
}

很简单,我们只是为我们的类添加了一个新属性并在构造期间分配它。然后,我们只是将它作为参数传递给我们的静态方法:

@Test
public void givenAnEvenInteger_whenDivisibleByTwo_thenCorrect() {
    Integer ten = 10;
    Integer two = 2;
    assertThat(ten,is(divisibleBy(two)));
}
@Test
public void givenAnOddInteger_whenNotDivisibleByTwo_thenCorrect() {
    Integer eleven = 11;
    Integer two = 2;
    assertThat(eleven,is(not(divisibleBy(two))));
}

就是这样!我们已经有了使用多个输入的匹配器!