本文将探讨AssertJ——一个开源的、社区驱动的库,用于在Java测试中编写流式且丰富的断言。本文重点介绍AssertJ核心模块(AssertJ-core)中提供的工具。
2. Maven依赖
要使用AssertJ,需要在你的pom.xml
文件中添加以下依赖:
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.26.0</version>
<scope>test</scope>
</dependency>
这个依赖仅覆盖基本的Java断言。如果你需要使用高级断言,则需要单独添加其他模块。
注意:对于Java 7及更早版本,应使用AssertJ核心版本2.x.x。
最新版本可以在这里找到。
3. 简介
AssertJ提供了一组类和实用方法,让我们能够轻松地为以下内容编写流式且优美的断言:
- 标准Java
- Java 8
- Guava
- Joda Time
- Neo4J
- Swing组件
所有模块的详细列表可在项目网站上找到。
让我们直接从AssertJ文档中看几个例子:
assertThat(frodo)
.isNotEqualTo(sauron)
.isIn(fellowshipOfTheRing);
assertThat(frodo.getName())
.startsWith("Fro")
.endsWith("do")
.isEqualToIgnoringCase("frodo");
assertThat(fellowshipOfTheRing)
.hasSize(9)
.contains(frodo, sam)
.doesNotContain(sauron);
以上例子只是冰山一角,但让我们大致了解了使用这个库编写断言的样子。
4. AssertJ实战
本节我们将重点介绍如何设置AssertJ并探索其功能。
4.1 快速开始
将库的jar包加入类路径后,启用断言只需在测试类中添加一个静态导入:
import static org.assertj.core.api.Assertions.*;
4.2 编写断言
编写断言时,总是需要先将你的对象传递给Assertions.assertThat()
方法,然后接着写实际的断言。
要记住,与其他一些库不同,下面的代码实际上并没有断言任何内容,并且永远不会导致测试失败:
assertThat(anyRefenceOrValue);
如果你利用IDE的代码补全功能,编写AssertJ断言会变得异常简单,因为它的方法描述性非常强。在IntelliJ IDEA 16中看起来是这样的:
(IDE会根据类型提示数十个上下文方法,且这些方法仅对String
类型可用)
让我们详细探索一下这个API,并看一些特定的断言。
4.3 对象断言
对象可以通过多种方式进行比较,以确定两个对象是否相等,或检查对象的字段。
我们来看两种比较两个对象相等性的方法。给定以下两个Dog
对象fido
和fidosClone
:
public class Dog {
private String name;
private Float weight;
// 标准getter和setter
}
Dog fido = new Dog("Fido", 5.25);
Dog fidosClone = new Dog("Fido", 5.25);
我们可以用以下断言比较相等性:
assertThat(fido).isEqualTo(fidosClone);
这会失败,因为isEqualTo()
比较的是对象引用。如果我们想比较内容,可以使用isEqualToComparingFieldByFieldRecursively()
,像这样:
assertThat(fido).isEqualToComparingFieldByFieldRecursively(fidosClone);
在递归字段比较时,Fido
和fidosClone
是相等的,因为一个对象的每个字段都与另一个对象的对应字段进行了比较。
还有许多其他断言方法提供不同的方式来比较和约束对象,以及检查和断言它们的字段。要发现所有方法,请参考官方的AbstractObjectAssert
文档。
4.4 布尔值断言
有一些简单的方法用于真值测试:
isTrue()
isFalse()
让我们看看它们如何使用:
assertThat("".isEmpty()).isTrue();
4.5 Iterable/数组断言
对于Iterable
或数组,有多种方式断言其内容存在。最常见的断言之一是检查Iterable
或数组是否包含给定元素:
List<String> list = Arrays.asList("1", "2", "3");
assertThat(list).contains("1");
或者检查列表是否非空:
assertThat(list).isNotEmpty();
或者检查列表是否以给定字符开头,例如“1”:
assertThat(list).startsWith("1");
请记住,如果你想对同一对象进行多个断言,可以轻松地将它们连接起来。
下面是一个断言示例,它检查给定列表:
- 非空
- 包含“1”元素
- 不包含任何null值
- 包含元素序列“2”、“3”
assertThat(list)
.isNotEmpty()
.contains("1")
.doesNotContainNull()
.containsSequence("2", "3");
当然,这些类型还有更多可能的断言。要发现所有方法,请参考官方的AbstractIterableAssert
文档。
4.6 字符断言
字符类型的断言主要涉及比较,甚至检查给定字符是否来自Unicode表。
下面是一个断言示例,它检查给定字符:
- 不是‘a’
- 在Unicode表中
- 大于‘b’
- 是小写
assertThat(someCharacter)
.isNotEqualTo('a')
.inUnicode()
.isGreaterThanOrEqualTo('b')
.isLowerCase();
有关所有字符类型断言的详细列表,请参见AbstractCharacterAssert
文档。
4.7 类断言
Class
类型的断言主要是关于检查其字段、类类型、注解的存在以及类的最终性。
如果你想断言Runnable
类是一个接口,只需写:
assertThat(Runnable.class).isInterface();
或者检查一个类是否可以从另一个类赋值:
assertThat(Exception.class).isAssignableFrom(NoSuchElementException.class);
所有可能的Class
断言可以在AbstractClassAssert
文档中查看。
4.8 文件断言
文件断言主要是关于检查给定的File
实例:
- 是否存在
- 是目录还是文件
- 是否有特定内容
- 是否可读
- 是否有给定的扩展名
这里你可以看到一个断言示例,它检查给定文件:
- 存在
- 是文件而非目录
- 可读
- 可写
assertThat(someFile)
.exists()
.isFile()
.canRead()
.canWrite();
所有可能的文件断言可以在AbstractFileAssert
文档中查看。
4.9 Double/Float/Integer断言
Double/Float/Integer和其他数字类型
数值断言主要是关于在给定偏移量内或外比较数值。例如,如果你想根据给定精度检查两个值是否相等,我们可以这样做:
assertThat(5.1).isEqualTo(5, withPrecision(1d));
注意,我们使用已经导入的withPrecision(Double offset)
辅助方法来生成Offset
对象。
更多断言,请访问AbstractDoubleAssert文档。
4.10 InputStream断言
只有一个InputStream
特定的断言可用:
hasSameContentAs(InputStream expected)
使用示例:
assertThat(given).hasSameContentAs(expected);
4.11 Map断言
Map断言允许你检查map是否:
- 包含特定条目
- 包含一组条目
- 分别包含键/值
这里你可以看到一个断言示例,它检查给定map:
- 非空
- 包含数字键“2”
- 不包含数字键“10”
- 包含条目:键2,值“a”
assertThat(map)
.isNotEmpty()
.containsKey(2)
.doesNotContainKeys(10)
.contains(entry(2, "a"));
更多断言,请参见AbstractMapAssert
文档。
4.12 Throwable断言
Throwable断言允许:
- 检查异常消息
- 检查堆栈跟踪
- 检查原因
- 验证异常是否已被抛出
让我们看一个断言示例,它检查给定异常:
- 已被抛出
- 消息以“c”结尾
assertThat(ex).hasNoCause().hasMessageEndingWith("c");
更多断言,请参见AbstractThrowableAssert文档。
5. 为断言消息添加自定义描述
为了实现更高的详细程度,你可以为断言创建动态生成的自定义描述。关键在于使用as(String description, Object… args)
方法。
如果你像这样定义断言:
assertThat(person.getAge())
.as("%s's age should be equal to 100", person.getName())
.isEqualTo(100);
运行测试时,你会得到:
[Alex's age should be equal to 100] expected:<100> but was:<34>
6. Java 8
AssertJ充分利用了Java 8的函数式编程特性。让我们深入一个例子,看看它的实际应用。首先,我们看看在Java 7中如何做:
assertThat(fellowshipOfTheRing)
.filteredOn("race", HOBBIT)
.containsOnly(sam, frodo, pippin, merry);
这里我们根据种族Hobbit过滤集合,在Java 8中我们可以这样做:
assertThat(fellowshipOfTheRing)
.filteredOn(character -> character.getRace().equals(HOBBIT))
.containsOnly(sam, frodo, pippin, merry);
我们将在本系列的未来文章中探索AssertJ的Java8功能。以上示例取自AssertJ的网站。
7. 结论
本文简要探讨了AssertJ为我们提供的可能性,以及核心Java类型最流行的断言。
所有示例和代码片段的实现可以在GitHub项目中找到。