概述
1.概述
Truth是一种流畅,灵活的开源测试框架,旨在使测试断言和失败消息更具可读性。
在本文中,我们将探索Truth框架的关键功能,并通过示例来展示其功能。
首先,我们需要将添加Truth和Truth-java8扩展到我们的pom.xml:
1 2 3 4 5 6 7 8 9 10 11 | <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>
或者项目工程下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<String> emails = Arrays.asList("contact@baeldung.com", "staff@baeldung.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); }
// 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<Integer> aComparable = 5;
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<User> { // ...
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<Integer> aList = Arrays.asList(4, 5, 6);
assertThat(aList).contains(5); } |
它包含另一个Iterable的任何元素:
1 2 3 4 5 6 | @Test public void whenCheckingIterable_thenContainsAnyInList() { List<Integer> aList = Arrays.asList(1, 2, 3);
assertThat(aList).containsAnyIn(Arrays.asList(1, 5, 10)); } |
并且该主题具有相同的元素,并且顺序相同,就像另一个元素一样:
1 2 3 4 5 6 7 8 9 | @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(); } |
并使用自定义比较器订购:
1 2 3 4 5 6 7 8 9 | @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); } |
除了断言Map实例是否为空或具有特定大小之外,我们可以检查它是否具有特定条目:
1 2 3 4 5 6 7 | @Test public void whenCheckingMap_thenContainsEntry() { Map<String, Object> aMap = new HashMap<>(); 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<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); } |
仅为异常对象提供了两种重要的方法。
我们可以编写针对异常原因的断言:
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<Double> aClass = Double.class;
assertThat(aClass).isAssignableTo(Number.class); } |
可选和流是Truth支持的仅有的两种Java 8类型。
有三种重要的方法可以验证Optional。
我们可以测试它是否具有特定的值:
1 2 3 4 5 6 | @Test public void whenCheckingJavaOptional_thenHasValue() { Optional<Integer> anOptional = Optional.of(1);
assertThat(anOptional).hasValue(1); } |
如果存在该值:
1 2 3 4 5 6 | @Test public void whenCheckingJavaOptional_thenPresent() { Optional<String> anOptional = Optional.of("Baeldung");
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<Integer> anStream = Stream.of(1, 2, 3);
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<String, Object> aMultimap = ArrayListMultimap.create(); 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<String> aMultiset = TreeMultiset.create(); aMultiset.add("baeldung", 10);
assertThat(aMultiset).hasCount("baeldung", 10); } |
除了检查它的大小或它的空白处,我们还可以检查一个表以验证它是否包含给定行和列的特定映射:
1 2 3 4 5 6 7 | @Test public void whenCheckingGuavaTable_thenContains() { Table<String, String, String> aTable = TreeBasedTable.create(); aTable.put("firstRow", "firstColumn", "baeldung");
assertThat(aTable).contains("firstRow", "firstColumn"); } |
或者它包含特定的单元格:
1 2 3 4 5 6 | @Test public void whenCheckingGuavaTable_thenContainsCell() { Table<String, String, String> aTable = getDummyGuavaTable();
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 <com.baeldung.testing.truth.User@ae805d5e> is null |
同样,我们可以添加一个自定义标签,该标签将在错误消息中的主题之前显示。当对象没有有用的字符串表示形式时,这可能会派上用场:
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] (<com.baeldung.testing.truth.User@ae805d5e>) is null |
扩展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<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()); } } |
现在,我们可以静态导入自定义主题的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上找到本文的完整源代码。