概述
2. Maven依赖
3.简介
4.入门
5.标准的Java断言
5.1。对象断言
5.2。整数,浮点和双断言
5.3。大十进制断言
5.4。布尔断言
5.5。字符串断言
5.6。数组断言
5.7。可比断言
5.8。迭代断言
5.9。Map断言
5.10。异常断言
5.11。类断言
6. Java 8断言
6.1。可选断言
6.2。流断言
7.Guava断言
7.1。可选断言
7.2。多Map断言
7.3。多集断言
7.4。表断言
8.自定义失败消息和标签
9.扩展
10.结论
1.概述
Truth是一种流畅,灵活的开源测试框架,旨在使测试断言和失败消息更具可读性。
在本文中,我们将探索Truth框架的关键功能,并通过示例来展示其功能。
首先,我们需要将添加Truth和Truth-java8扩展到我们的pom.xml:
1 2 3 4 5 6 7 8 9 10 11 |
或者项目工程下build.gradle |
您可以在Maven Central上找到Truth和Truth-java8-extension的最新版本。
Truth允许我们为各种类编写可读的断言和失败消息:
标准Java –基元,数组,字符串,对象,集合,可抛出对象,类等。
Java 8 – 可选和Stream实例
番石榴 – 可选,Multimap,Multiset和Table对象
自定义类型 –通过扩展Subject类,我们将在后面看到
通过Truth和Truth8类,该库提供了实用的方法,可用于编写对主题(即被测试的值或对象)起作用的断言。
一旦知道了该主题,真相就可以在编译时推断该主题已知的命题。这使它可以返回围绕我们的值的包装器,这些包装器声明特定于该特定主题的命题方法。
例如,在对列表进行断言时,Truth返回IterableSubject实例,该实例定义诸如contains()和containsAnyOf()等方法。在Map上声明时,它返回一个MapSubject,该方法声明诸如containsEntry()和containsKey()之类的方法。
要开始编写断言,让我们首先导入Truth的入口点:
1 2 |
import static com.google.common.truth.Truth.*; import static com.google.common.truth.Truth8.*; |
现在,让我们编写一个简单的类,在下面的一些示例中将使用它们:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class User { private String name = "John Doe"; private List = Arrays.asList("[email protected]", "[email protected]");
public boolean equals(Object obj) { if (obj == null || getClass() != obj.getClass()) { return false; }
User other = (User) obj; return Objects.equals(this.name, other.name); }
// standard constructors, getters and setters } |
注意自定义的equals()方法,在该方法中,我们声明两个User对象(如果它们的名称相同)是相等的。
在本节中,我们将看到有关如何为标准Java类型编写测试断言的详细示例。
真相提供了主题包装器,用于对对象执行断言。Subject也是库中所有其他包装器的父级,并声明用于确定Object(在我们的情况下为User)是否等于另一个对象的方法:
1 2 3 4 5 6 7 |
@Test public void whenComparingUsers_thenEqual() { User aUser = new User("John Doe"); User anotherUser = new User("John Doe");
assertThat(aUser).isEqualTo(anotherUser); } |
或等于列表中的给定对象:
1 2 3 4 5 6 |
@Test public void whenComparingUser_thenInList() { User aUser = new User();
assertThat(aUser).isIn(Arrays.asList(1, 3, aUser, null)); } |
或如果不是:
1 2 3 4 5 6 |
@Test public void whenComparingUser_thenNotInList() { // ...
assertThat(aUser).isNotIn(Arrays.asList(1, 3, "Three")); } |
是否为null:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@Test public void whenComparingUser_thenIsNull() { User aUser = null;
assertThat(aUser).isNull(); }
@Test public void whenComparingUser_thenNotNull() { User aUser = new User();
assertThat(aUser).isNotNull(); } |
或者它是特定类的实例:
1 2 3 4 5 6 |
@Test public void whenComparingUser_thenInstanceOf() { // ...
assertThat(aUser).isInstanceOf(User.class); } |
Subject类中还有其他断言方法。要发现所有内容,请参阅主题文档。
在以下各节中,我们将重点介绍 Truth支持的每种特定类型的最相关方法。但是,请记住,Subject类中的所有方法也可以应用。
可以比较Integer,Float和Double实例的相等性:
1 2 3 4 5 6 |
@Test public void whenComparingInteger_thenEqual() { int anInt = 10;
assertThat(anInt).isEqualTo(10); } |
如果更大:
1 2 3 4 5 6 |
@Test public void whenComparingFloat_thenIsBigger() { float aFloat = 10.0f;
assertThat(aFloat).isGreaterThan(1.0f); } |
或更小:
1 2 3 4 5 6 |
@Test public void whenComparingDouble_thenIsSmaller() { double aDouble = 10.0f;
assertThat(aDouble).isLessThan(20.0); } |
此外,还可以检查Float和Double实例以查看它们是否在预期的精度范围内:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@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); } |
除了常见的断言之外,还可以忽略这种类型的规模来进行比较:
1 2 3 4 5 6 |
@Test public void whenComparingBigDecimal_thenEqualIgnoringScale() { BigDecimal aBigDecimal = BigDecimal.valueOf(1000, 3);
assertThat(aBigDecimal).isEqualToIgnoringScale(new BigDecimal(1.0)); } |
仅提供了两种相关方法,isTrue()和isFalse():
1 2 3 4 5 6 |
@Test public void whenCheckingBoolean_thenTrue() { boolean aBoolean = true;
assertThat(aBoolean).isTrue(); } |
我们可以测试字符串是否以特定文本开头:
1 2 3 4 5 6 |
@Test public void whenCheckingString_thenStartsWith() { String aString = "This is a string";
assertThat(aString).startsWith("This"); } |
另外,我们可以检查字符串是否包含给定的String,是否以期望值结尾或是否为空。这些和其他方法的测试用例在源代码中可用。
我们可以检查Array,看它们是否等于其他数组:
1 2 3 4 5 6 7 |
@Test public void whenComparingArrays_thenEqual() { String[] firstArrayOfStrings = { "one", "two", "three" }; String[] secondArrayOfStrings = { "one", "two", "three" };
assertThat(firstArrayOfStrings).isEqualTo(secondArrayOfStrings); } |
或如果它们为空:
1 2 3 4 5 6 |
@Test public void whenCheckingArray_thenEmpty() { Object[] anArray = {};
assertThat(anArray).isEmpty(); } |
除了测试Comparable是否大于或小于另一个实例之外,我们还可以检查它们是否至少为给定值:
1 2 3 4 5 6 |
@Test public void whenCheckingComparable_thenAtLeast() { Comparable
assertThat(aComparable).isAtLeast(1); } |
此外,我们可以测试它们是否在特定范围内:
1 2 3 4 5 6 |
@Test public void whenCheckingComparable_thenInRange() { // ...
assertThat(aComparable).isIn(Range.closed(1, 10)); } |
或在特定列表中:
1 2 3 4 5 6 |
@Test public void whenCheckingComparable_thenInList() { // ...
assertThat(aComparable).isIn(Arrays.asList(4, 5, 6)); } |
我们还可以根据类的compareTo()方法测试两个Comparable实例是否等效。
首先,让我们修改User类以实现Comparable接口:
1 2 3 4 5 6 7 |
public class User implements Comparable // ...
public int compareTo(User o) { return this.getName().compareToIgnoreCase(o.getName()); } } |
现在,让我们断言两个具有相同名称的用户是等效的:
1 2 3 4 5 6 7 8 9 10 |
@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); } |
除了声明Iterable实例的大小(无论它是空的还是没有重复的)外,对Iterable的最典型的声明是它包含一些元素:
1 2 3 4 5 6 |
@Test public void whenCheckingIterable_thenContains() { List
assertThat(aList).contains(5); } |
它包含另一个Iterable的任何元素:
1 2 3 4 5 6 |
@Test public void whenCheckingIterable_thenContainsAnyInList() { List
assertThat(aList).containsAnyIn(Arrays.asList(1, 5, 10)); } |
并且该主题具有相同的元素,并且顺序相同,就像另一个元素一样:
1 2 3 4 5 6 7 8 9 |
@Test public void whenCheckingIterable_thenContainsExactElements() { List List
assertThat(aList) .containsExactlyElementsIn(anotherList) .inOrder(); } |
并使用自定义比较器订购:
1 2 3 4 5 6 7 8 9 |
@Test public void givenComparator_whenCheckingIterable_thenOrdered() { Comparator = (a, b) -> new Float(a).compareTo(new Float(b));
List
assertThat(aList).isOrdered(aComparator); } |
除了断言Map实例是否为空或具有特定大小之外,我们可以检查它是否具有特定条目:
1 2 3 4 5 6 7 |
@Test public void whenCheckingMap_thenContainsEntry() { Map aMap.put("one", 1L);
assertThat(aMap).containsEntry("one", 1L); } |
如果它具有特定的密钥:
1 2 3 4 5 6 |
@Test public void whenCheckingMap_thenContainsKey() { // ...
assertThat(map).containsKey("one"); } |
或者它具有与另一个Map相同的条目:
1 2 3 4 5 6 7 8 9 10 11 |
@Test public void whenCheckingMap_thenContainsEntries() { Map aMap.put("first", 1L); aMap.put("second", 2.0); aMap.put("third", 3f);
Map
assertThat(aMap).containsExactlyEntriesIn(anotherMap); } |
仅为异常对象提供了两种重要的方法。
我们可以编写针对异常原因的断言:
1 2 3 4 5 6 7 8 9 |
@Test public void whenCheckingException_thenInstanceOf() { Exception anException = new IllegalArgumentException(new NumberFormatException());
assertThat(anException) .hasCauseThat() .isInstanceOf(NumberFormatException.class); } |
或其讯息:
1 2 3 4 5 6 7 8 9 |
@Test public void whenCheckingException_thenCauseMessageIsKnown() { Exception anException = new IllegalArgumentException("Bad value");
assertThat(anException) .hasMessageThat() .startsWith("Bad"); } |
对于类断言,只有一种重要的方法可以用来测试一个类是否可分配给另一个:
1 2 3 4 5 6 |
@Test public void whenCheckingClass_thenIsAssignable() { Class
assertThat(aClass).isAssignableTo(Number.class); } |
可选和流是Truth支持的仅有的两种Java 8类型。
有三种重要的方法可以验证Optional。
我们可以测试它是否具有特定的值:
1 2 3 4 5 6 |
@Test public void whenCheckingJavaOptional_thenHasValue() { Optional
assertThat(anOptional).hasValue(1); } |
如果存在该值:
1 2 3 4 5 6 |
@Test public void whenCheckingJavaOptional_thenPresent() { Optional
assertThat(anOptional).isPresent(); } |
或如果不存在该值:
1 2 3 4 5 6 |
@Test public void whenCheckingJavaOptional_thenEmpty() { Optional anOptional = Optional.empty();
assertThat(anOptional).isEmpty(); } |
Stream的断言与Iterable的断言非常相似。
例如,我们可以测试特定的Stream是否包含相同顺序的Iterable的所有对象:
1 2 3 4 5 6 7 8 |
@Test public void whenCheckingStream_thenContainsInOrder() { Stream
assertThat(anStream) .containsAllOf(1, 2, 3) .inOrder(); } |
有关更多示例,请参考Iterable Assertions部分。
在本节中,我们将看到Truth中支持的Guava类型的断言示例。
对于Guava Optional也有三种重要的断言方法。该hasValue的()和isPresent()方法的行为完全一样与Java 8 可选。
但不是的isEmpty()断言一个可选的不存在,我们使用isAbsent() :
1 2 3 4 5 6 |
@Test public void whenCheckingGuavaOptional_thenIsAbsent() { Optional anOptional = Optional.absent();
assertThat(anOptional).isAbsent(); } |
Multimap和标准Map断言非常相似。
一个显着的区别是,我们可以在Multimap中获取键的多个值,并对这些值进行断言。
这是一个示例,测试“ one”键的值是否具有两个大小:
1 2 3 4 5 6 7 8 9 10 |
@Test public void whenCheckingGuavaMultimap_thenExpectedSize() { Multimap aMultimap.put("one", 1L); aMultimap.put("one", 2.0);
assertThat(aMultimap) .valuesForKey("one") .hasSize(2); } |
有关更多示例,请参考“ Map断言”部分。
多对象对象的断言包括用于Iterable的断言,以及一种用于验证键是否具有特定出现次数的额外方法:
1 2 3 4 5 6 7 |
@Test public void whenCheckingGuavaMultiset_thenExpectedCount() { TreeMultiset aMultiset.add("baeldung", 10);
assertThat(aMultiset).hasCount("baeldung", 10); } |
除了检查它的大小或它的空白处,我们还可以检查一个表以验证它是否包含给定行和列的特定映射:
1 2 3 4 5 6 7 |
@Test public void whenCheckingGuavaTable_thenContains() { Table aTable.put("firstRow", "firstColumn", "baeldung");
assertThat(aTable).contains("firstRow", "firstColumn"); } |
或者它包含特定的单元格:
1 2 3 4 5 6 |
@Test public void whenCheckingGuavaTable_thenContainsCell() { Table
assertThat(aTable).containsCell("firstRow", "firstColumn", "baeldung"); } |
此外,我们可以检查它是否包含给定的行,列或值。请参阅相关测试用例的源代码。
当断言失败时,Truth会显示易读的消息,准确指出问题所在。但是,有时有必要向这些消息添加更多信息,以提供有关所发生情况的更多详细信息。
Truth使我们能够自定义那些失败消息:
1 2 3 4 5 6 |
@Test public void whenFailingAssertion_thenCustomMessage() { assertWithMessage("TEST-985: Secret user subject was NOT null!") .that(new User()) .isNull(); } |
运行测试后,我们得到以下输出:
1 2 |
TEST-985: Secret user subject was NOT null!: Not true that |
同样,我们可以添加一个自定义标签,该标签将在错误消息中的主题之前显示。当对象没有有用的字符串表示形式时,这可能会派上用场:
1 2 3 4 5 6 7 8 |
@Test public void whenFailingAssertion_thenMessagePrefix() { User aUser = new User();
assertThat(aUser) .named("User [%s]", aUser.getName()) .isNull(); } |
如果运行测试,我们将看到以下输出:
1 2 |
Not true that User [John Doe] ( |
扩展Truth意味着我们可以添加对自定义类型的支持。为此,我们需要创建一个类:
扩展Subject类或其子类之一
定义一个接受两个参数的构造函数– FailureStrategy和我们的自定义类型的实例
声明一个SubjectFactory类型的字段,Truth将使用该字段创建我们自定义主题的实例
实现一个静态的assertThat()方法,该方法接受我们的自定义类型
公开我们的测试断言API
现在我们知道了如何扩展Truth,让我们创建一个添加对User类型的对象的支持的类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
public class UserSubject extends ComparableSubject
private UserSubject( FailureStrategy failureStrategy, User target) { super(failureStrategy, target); }
private static final SubjectFactory = new SubjectFactory
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()); } } |
现在,我们可以静态导入自定义主题的assertThat()方法并编写一些测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
@Test public void whenCheckingUser_thenHasName() { User aUser = new User();
assertThat(aUser).hasName("John Doe"); }
@Test public void whenCheckingUser_thenHasNameIgnoringCase() { // ...
assertThat(aUser).hasNameIgnoringCase("john doe"); }
@Test public void givenUser_whenCheckingEmails_thenExpectedSize() { // ...
assertThat(aUser) .emails() .hasSize(2); } |
在本教程中,我们探索了Truth给我们编写更具可读性的测试和失败消息的可能性。
我们展示了支持Java和番石榴类型,定制的失败消息最流行的断言方法,并扩展Truth定制主题。
与往常一样,可以在Github上找到本文的完整源代码。