Contents

AssertJ 的自定义断言

1. 概述

在本教程中,我们将介绍如何创建自定义AssertJ 断言;AssertJ 的基础知识可以在这里 找到。

简单地说,自定义断言允许创建特定于我们自己的类的断言,让我们的测试更好地反映领域模型。

2. 被测类

本教程中的测试用例将围绕Person类构建:

public class Person {
    private String fullName;
    private int age;
    private List<String> nicknames;
    public Person(String fullName, int age) {
        this.fullName = fullName;
        this.age = age;
        this.nicknames = new ArrayList<>();
    }
    public void addNickname(String nickname) {
        nicknames.add(nickname);
    }
    // getters
}

3. 自定义断言类

编写自定义 AssertJ 断言类非常简单。我们需要做的就是声明一个扩展AbstractAssert的类,添加一个必需的构造函数,并提供自定义断言方法。

断言类必须扩展AbstractAssert类以使我们能够访问 API 的基本断言方法,例如isNotNullisEqualTo

这是Person的自定义断言类的骨架:

public class PersonAssert extends AbstractAssert<PersonAssert, Person> {
    public PersonAssert(Person actual) {
        super(actual, PersonAssert.class);
    }
    // assertion methods described later
}

在扩展AbstractAssert类时,我们必须指定两个类型参数:第一个是自定义断言类本身,它是方法链接所必需的,第二个是被测类。

为了给我们的断言类提供一个入口点,我们可以定义一个可以用来启动断言链的静态方法:

public static PersonAssert assertThat(Person actual) {
    return new PersonAssert(actual);
}

接下来,我们将讨论PersonAssert类中包含的几个自定义断言。

第一个方法验证Person的全名是否与String参数匹配:

public PersonAssert hasFullName(String fullName) {
    isNotNull();
    if (!actual.getFullName().equals(fullName)) {
        failWithMessage("Expected person to have full name %s but was %s", 
          fullName, actual.getFullName());
    }
    return this;
}

以下方法根据Person 的Age测试Person是否为成年人:

public PersonAssert isAdult() {
    isNotNull();
    if (actual.getAge() < 18) {
        failWithMessage("Expected person to be adult");
    }
    return this;
}

最后检查是否存在NickName

public PersonAssert hasNickName(String nickName) {
    isNotNull();
    if (!actual.getNickNames().contains(nickName)) {
        failWithMessage("Expected person to have nickname %s", 
          nickName);
    }
    return this;
}

当有多个自定义断言类时,我们可以将所有assertThat方法包装在一个类中,为每个断言类提供一个静态工厂方法:

public class Assertions {
    public static PersonAssert assertThat(Person actual) {
        return new PersonAssert(actual);
    }
    // static factory methods of other assertion classes
}

上面显示的Assertions类是所有自定义断言类的便捷入口点。

此类的静态方法具有相同的名称,并通过其参数类型相互区分。

4. 用例

以下测试用例将说明我们在上一节中创建的自定义断言方法。请注意,assertThat方法是从我们的自定义Assertions类导入的,而不是核心 AssertJ API。

下面是hasFullName方法的使用方法:

@Test
public void whenPersonNameMatches_thenCorrect() {
    Person person = new Person("John Doe", 20);
    assertThat(person)
      .hasFullName("John Doe");
}

这是说明isAdult方法的负面测试用例:

@Test
public void whenPersonAgeLessThanEighteen_thenNotAdult() {
    Person person = new Person("Jane Roe", 16);
    // assertion fails
    assertThat(person).isAdult();
}

以及演示hasNickname方法的另一个测试:

@Test
public void whenPersonDoesNotHaveAMatchingNickname_thenIncorrect() {
    Person person = new Person("John Doe", 20);
    person.addNickname("Nick");
    // assertion will fail
    assertThat(person)
      .hasNickname("John");
}

5. 断言生成器

编写与对象模型对应的自定义断言类为非常易读的测试用例铺平了道路。

但是,如果我们有很多类,手动为所有这些类创建自定义断言类会很痛苦。这就是 AssertJ 断言生成器发挥作用的地方。

要在 Maven 中使用断言生成器,我们需要在pom.xml文件中添加一个插件:

<plugin>
    <groupId>org.assertj</groupId>
    <artifactId>assertj-assertions-generator-maven-plugin</artifactId>
    <version>2.1.0</version>
    <configuration>
        <classes>
            <param>com.blogdemo.testing.assertj.custom.Person</param>
        </classes>
    </configuration>
</plugin>

可以在此处 找到最新版本的assertj-assertions-generator-maven-plugin

上述插件中的classes元素标记了我们要为其生成断言的类。

AssertJ 断言生成器为目标类的每个公共属性创建断言。每个断言方法的具体名称取决于字段或属性的类型。

在项目基目录中执行以下 Maven 命令:

mvn assertj:generate-assertions

我们应该看到在target文件夹*/generated-test-sources/assertj-assertions*中生成的断言类。例如,为生成的断言生成的入口点类如下所示:

// generated comments are stripped off for brevity
package com.blogdemo.testing.assertj.custom;
@javax.annotation.Generated(value="assertj-assertions-generator")
public class Assertions {
    @org.assertj.core.util.CheckReturnValue
    public static com.blogdemo.testing.assertj.custom.PersonAssert
      assertThat(com.blogdemo.testing.assertj.custom.Person actual) {
        return new com.blogdemo.testing.assertj.custom.PersonAssert(actual);
    }
    protected Assertions() {
        // empty
    }
}

现在,我们可以将生成的源文件复制到测试目录,然后添加自定义断言方法来满足我们的测试需求。

需要注意的一件重要事情是生成的代码不能保证完全正确。在这一点上,生成器还不是成品,社区正在努力。

因此,我们应该使用生成器作为支持工具,让我们的生活更轻松,而不是理所当然。