1. 概述
Truth 是一个流畅且灵活的开源测试框架,旨在让测试断言和失败信息更具可读性。
本文将深入探讨 Truth 框架的核心特性,并通过示例展示其强大功能。
2. Maven 依赖
首先,在 pom.xml
中添加 truth
和 truth-java8-extension
依赖:
<dependency>
<groupId>com.google.truth</groupId>
<artifactId>truth</artifactId>
<version>0.32</version>
</dependency>
<dependency>
<groupId>com.google.truth.extensions</groupId>
<artifactId>truth-java8-extension</artifactId>
<version>0.32</version>
<scope>test</scope>
</dependency>
最新版本可在 Maven Central 查找:
3. 框架介绍
Truth 支持多种类型的可读断言和失败信息:
- 标准 Java 类型 – 基本类型、数组、字符串、对象、集合、异常、类等
- Java 8 类型 –
Optional
和Stream
实例 - Guava 类型 –
Optional
、Multimap
、Multiset
和Table
对象 - 自定义类型 – 通过扩展
Subject
类实现(后文详述)
通过 Truth
和 Truth8
类,库提供了针对被测对象(subject)的断言工具方法。一旦确定 subject 类型,Truth 能在编译时推断出该对象支持的断言方法。例如:
- 对 List 断言时返回
IterableSubject
,提供contains()
等方法 - 对 Map 断言时返回
MapSubject
,提供containsEntry()
等方法
4. 快速上手
导入 Truth 的入口点开始编写断言:
import static com.google.common.truth.Truth.*;
import static com.google.common.truth.Truth8.*;
创建示例类(后续示例使用):
public class User {
private String name = "John Doe";
private List<String> emails = Arrays.asList("john.doe@example.com", "john.doe2@example.com");
public boolean equals(Object obj) {
if (obj == null || getClass() != obj.getClass()) {
return false;
}
User other = (User) obj;
return Objects.equals(this.name, other.name);
}
// 标准构造器、getter/setter
}
注意自定义 equals()
方法:仅当 name 相同时判定两个 User 相等。
5. 标准 Java 类型断言
5.1 Object 断言
Truth 提供 Subject
包装器处理对象断言。它是所有包装器的父类,核心方法包括:
@Test
public void whenComparingUsers_thenEqual() {
User aUser = new User("John Doe");
User anotherUser = new User("John Doe");
assertThat(aUser).isEqualTo(anotherUser);
}
其他常用断言:
@Test
public void whenComparingUser_thenInList() {
User aUser = new User();
assertThat(aUser).isIn(Arrays.asList(1, 3, aUser, null));
}
@Test
public void whenComparingUser_thenNotInList() {
User aUser = new User();
assertThat(aUser).isNotIn(Arrays.asList(1, 3, "Three"));
}
@Test
public void whenComparingUser_thenIsNull() {
User aUser = null;
assertThat(aUser).isNull();
}
@Test
public void whenComparingUser_thenInstanceOf() {
User aUser = new User();
assertThat(aUser).isInstanceOf(User.class);
}
⚠️ 所有
Subject
的方法都适用于子类,但后续章节聚焦各类型的专属方法。
5.2 数值类型断言(Integer/Float/Double)
支持等值、大小比较:
@Test
public void whenComparingInteger_thenEqual() {
int anInt = 10;
assertThat(anInt).isEqualTo(10);
}
@Test
public void whenComparingFloat_thenIsBigger() {
float aFloat = 10.0f;
assertThat(aFloat).isGreaterThan(1.0f);
}
@Test
public void whenComparingDouble_thenIsSmaller() {
double aDouble = 10.0;
assertThat(aDouble).isLessThan(20.0);
}
浮点数精度断言:
@Test
public void whenComparingDouble_thenWithinPrecision() {
double aDouble = 22.18;
assertThat(aDouble).isWithin(2).of(23d);
}
@Test
public void whenComparingFloat_thenNotWithinPrecision() {
float aFloat = 23.04f;
assertThat(aFloat).isNotWithin(1.3f).of(100f);
}
5.3 BigDecimal 断言
支持忽略 scale 的比较:
@Test
public void whenComparingBigDecimal_thenEqualIgnoringScale() {
BigDecimal aBigDecimal = BigDecimal.valueOf(1000, 3);
assertThat(aBigDecimal).isEqualToIgnoringScale(new BigDecimal(1.0));
}
5.4 Boolean 断言
仅提供两个核心方法:
@Test
public void whenCheckingBoolean_thenTrue() {
boolean aBoolean = true;
assertThat(aBoolean).isTrue();
}
5.5 String 断言
支持前缀、包含、后缀等检查:
@Test
public void whenCheckingString_thenStartsWith() {
String aString = "This is a string";
assertThat(aString).startsWith("This");
}
其他方法(contains()
、endsWith()
、isEmpty()
)详见源码。
5.6 数组断言
支持等值和空数组检查:
@Test
public void whenComparingArrays_thenEqual() {
String[] first = { "one", "two", "three" };
String[] second = { "one", "two", "three" };
assertThat(first).isEqualTo(second);
}
@Test
public void whenCheckingArray_thenEmpty() {
Object[] anArray = {};
assertThat(anArray).isEmpty();
}
5.7 Comparable 断言
支持范围和列表检查:
@Test
public void whenCheckingComparable_thenAtLeast() {
Comparable<Integer> aComparable = 5;
assertThat(aComparable).isAtLeast(1);
}
@Test
public void whenCheckingComparable_thenInRange() {
Comparable<Integer> aComparable = 5;
assertThat(aComparable).isIn(Range.closed(1, 10));
}
@Test
public void whenCheckingComparable_thenInList() {
Comparable<Integer> aComparable = 5;
assertThat(aComparable).isIn(Arrays.asList(4, 5, 6));
}
等价性检查(需实现 Comparable
):
public class User implements Comparable<User> {
// ...
public int compareTo(User o) {
return this.getName().compareToIgnoreCase(o.getName());
}
}
@Test
public void whenComparingUsers_thenEquivalent() {
User aUser = new User(); aUser.setName("John Doe");
User anotherUser = new User(); anotherUser.setName("john doe");
assertThat(aUser).isEquivalentAccordingToCompareTo(anotherUser);
}
5.8 Iterable 断言
核心断言包括:
@Test
public void whenCheckingIterable_thenContains() {
List<Integer> aList = Arrays.asList(4, 5, 6);
assertThat(aList).contains(5);
}
@Test
public void whenCheckingIterable_thenContainsAnyInList() {
List<Integer> aList = Arrays.asList(1, 2, 3);
assertThat(aList).containsAnyIn(Arrays.asList(1, 5, 10));
}
@Test
public void whenCheckingIterable_thenContainsExactElements() {
List<String> aList = Arrays.asList("10", "20", "30");
List<String> anotherList = Arrays.asList("10", "20", "30");
assertThat(aList)
.containsExactlyElementsIn(anotherList)
.inOrder();
}
自定义排序检查:
@Test
public void givenComparator_whenCheckingIterable_thenOrdered() {
Comparator<String> aComparator = (a, b) -> new Float(a).compareTo(new Float(b));
List<String> aList = Arrays.asList("1", "012", "0020", "100");
assertThat(aList).isOrdered(aComparator);
}
5.9 Map 断言
支持条目、键值检查:
@Test
public void whenCheckingMap_thenContainsEntry() {
Map<String, Object> aMap = new HashMap<>();
aMap.put("one", 1L);
assertThat(aMap).containsEntry("one", 1L);
}
@Test
public void whenCheckingMap_thenContainsKey() {
Map<String, Object> map = new HashMap<>();
map.put("one", 1L);
assertThat(map).containsKey("one");
}
@Test
public void whenCheckingMap_thenContainsEntries() {
Map<String, Object> aMap = new HashMap<>();
aMap.put("first", 1L); aMap.put("second", 2.0); aMap.put("third", 3f);
Map<String, Object> anotherMap = new HashMap<>(aMap);
assertThat(aMap).containsExactlyEntriesIn(anotherMap);
}
5.10 Exception 断言
支持异常原因和消息检查:
@Test
public void whenCheckingException_thenInstanceOf() {
Exception anException = new IllegalArgumentException(new NumberFormatException());
assertThat(anException)
.hasCauseThat()
.isInstanceOf(NumberFormatException.class);
}
@Test
public void whenCheckingException_thenCauseMessageIsKnown() {
Exception anException = new IllegalArgumentException("Bad value");
assertThat(anException)
.hasMessageThat()
.startsWith("Bad");
}
5.11 Class 断言
检查类型可分配性:
@Test
public void whenCheckingClass_thenIsAssignable() {
Class<Double> aClass = Double.class;
assertThat(aClass).isAssignableTo(Number.class);
}
6. Java 8 类型断言
6.1 Optional 断言
核心方法:
@Test
public void whenCheckingJavaOptional_thenHasValue() {
Optional<Integer> anOptional = Optional.of(1);
assertThat(anOptional).hasValue(1);
}
@Test
public void whenCheckingJavaOptional_thenPresent() {
Optional<String> anOptional = Optional.of("Baeldung");
assertThat(anOptional).isPresent();
}
@Test
public void whenCheckingJavaOptional_thenEmpty() {
Optional anOptional = Optional.empty();
assertThat(anOptional).isEmpty();
}
6.2 Stream 断言
与 Iterable 断言类似:
@Test
public void whenCheckingStream_thenContainsInOrder() {
Stream<Integer> aStream = Stream.of(1, 2, 3);
assertThat(aStream)
.containsAllOf(1, 2, 3)
.inOrder();
}
更多方法参考 Iterable 断言章节。
7. Guava 类型断言
7.1 Optional 断言
与 Java 8 Optional 类似,但空值检查用 isAbsent()
:
@Test
public void whenCheckingGuavaOptional_thenIsAbsent() {
Optional anOptional = Optional.absent();
assertThat(anOptional).isAbsent();
}
7.2 Multimap 断言
支持键值对和值集合检查:
@Test
public void whenCheckingGuavaMultimap_thenExpectedSize() {
Multimap<String, Object> aMultimap = ArrayListMultimap.create();
aMultimap.put("one", 1L);
aMultimap.put("one", 2.0);
assertThat(aMultimap)
.valuesForKey("one")
.hasSize(2);
}
7.3 Multiset 断言
支持元素计数检查:
@Test
public void whenCheckingGuavaMultiset_thenExpectedCount() {
TreeMultiset<String> aMultiset = TreeMultiset.create();
aMultiset.add("baeldung", 10);
assertThat(aMultiset).hasCount("baeldung", 10);
}
7.4 Table 断言
支持单元格和行列检查:
@Test
public void whenCheckingGuavaTable_thenContains() {
Table<String, String, String> aTable = TreeBasedTable.create();
aTable.put("firstRow", "firstColumn", "baeldung");
assertThat(aTable).contains("firstRow", "firstColumn");
}
@Test
public void whenCheckingGuavaTable_thenContainsCell() {
Table<String, String, String> aTable = TreeBasedTable.create();
aTable.put("firstRow", "firstColumn", "baeldung");
assertThat(aTable).containsCell("firstRow", "firstColumn", "baeldung");
}
8. 自定义失败信息与标签
Truth 默认提供清晰的失败信息,但支持自定义:
@Test
public void whenFailingAssertion_thenCustomMessage() {
assertWithMessage("TEST-985: Secret user subject was NOT null!")
.that(new User())
.isNull();
}
输出:
TEST-985: Secret user subject was NOT null!:
Not true that <com.baeldung.testing.truth.User@ae805d5e> is null
添加标签增强可读性:
@Test
public void whenFailingAssertion_thenMessagePrefix() {
User aUser = new User();
assertThat(aUser)
.named("User [%s]", aUser.getName())
.isNull();
}
输出:
Not true that User [John Doe]
(<com.baeldung.testing.truth.User@ae805d5e>) is null
9. 扩展框架
扩展 Truth 需创建自定义 Subject 类:
- 继承
Subject
或其子类 - 定义双参数构造器(
FailureStrategy
+ 自定义类型实例) - 声明
SubjectFactory
字段 - 实现静态
assertThat()
方法 - 暴露断言 API
示例:UserSubject
public class UserSubject extends ComparableSubject<UserSubject, User> {
private UserSubject(FailureStrategy failureStrategy, User target) {
super(failureStrategy, target);
}
private static final SubjectFactory<UserSubject, User> USER_SUBJECT_FACTORY =
new SubjectFactory<UserSubject, User>() {
public UserSubject getSubject(FailureStrategy failureStrategy, User target) {
return new UserSubject(failureStrategy, target);
}
};
public static UserSubject assertThat(User user) {
return Truth.assertAbout(USER_SUBJECT_FACTORY).that(user);
}
public void hasName(String name) {
if (!actual().getName().equals(name)) {
fail("has name", name);
}
}
public void hasNameIgnoringCase(String name) {
if (!actual().getName().equalsIgnoreCase(name)) {
fail("has name ignoring case", name);
}
}
public IterableSubject emails() {
return Truth.assertThat(actual().getEmails());
}
}
使用自定义断言:
@Test
public void whenCheckingUser_thenHasName() {
User aUser = new User();
assertThat(aUser).hasName("John Doe");
}
@Test
public void givenUser_whenCheckingEmails_thenExpectedSize() {
User aUser = new User();
assertThat(aUser)
.emails()
.hasSize(2);
}
10. 总结
本文系统介绍了 Truth 框架的核心能力:
- ✅ 主流 Java/Guava 类型的断言方法
- ✅ 失败信息定制与标签增强
- ✅ 通过自定义 Subject 扩展框架
Truth 让测试断言更直观、失败信息更精准,是提升测试可读性的利器。完整示例代码见 GitHub。