概述
1.概述
Truth是一種流暢,靈活的開源測試框架,旨在使測試斷言和失敗消息更具可讀性。
在本文中,我們將探索Truth框架的關鍵功能,並通過示例來展示其功能。
2. Maven依賴
首先,我們需要將添加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的最新版本。
3.簡介
Truth允許我們爲各種類編寫可讀的斷言和失敗消息:
-
標準Java –基元,數組,字符串,對象,集合,可拋出對象,類等。
-
Java 8 – 可選和Stream實例
-
番石榴 – 可選,Multimap,Multiset和Table對象
-
自定義類型 –通過擴展Subject類,我們將在後面看到
通過Truth和Truth8類,該庫提供了實用的方法,可用於編寫對主題(即被測試的值或對象)起作用的斷言。
一旦知道了該主題,真相就可以在編譯時推斷該主題已知的命題。這使它可以返回圍繞我們的值的包裝器,這些包裝器聲明特定於該特定主題的命題方法。
例如,在對列表進行斷言時,Truth返回IterableSubject實例,該實例定義諸如contains()和containsAnyOf()等方法。在Map上聲明時,它返回一個MapSubject,該方法聲明諸如containsEntry()和containsKey()之類的方法。
4.入門
要開始編寫斷言,讓我們首先導入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("[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對象(如果它們的名稱相同)是相等的。
5.標準的Java斷言
在本節中,我們將看到有關如何爲標準Java類型編寫測試斷言的詳細示例。
5.1。對象斷言
真相提供了主題包裝器,用於對對象執行斷言。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類中的所有方法也可以應用。
5.2。整數,浮點和雙斷言
可以比較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); } |
5.3。大十進制斷言
除了常見的斷言之外,還可以忽略這種類型的規模來進行比較:
1 2 3 4 5 6 |
@Test public void whenComparingBigDecimal_thenEqualIgnoringScale() { BigDecimal aBigDecimal = BigDecimal.valueOf(1000, 3);
assertThat(aBigDecimal).isEqualToIgnoringScale(new BigDecimal(1.0)); } |
5.4。布爾斷言
僅提供了兩種相關方法,isTrue()和isFalse():
1 2 3 4 5 6 |
@Test public void whenCheckingBoolean_thenTrue() { boolean aBoolean = true;
assertThat(aBoolean).isTrue(); } |
5.5。字符串斷言
我們可以測試字符串是否以特定文本開頭:
1 2 3 4 5 6 |
@Test public void whenCheckingString_thenStartsWith() { String aString = "This is a string";
assertThat(aString).startsWith("This"); } |
另外,我們可以檢查字符串是否包含給定的String,是否以期望值結尾或是否爲空。這些和其他方法的測試用例在源代碼中可用。
5.6。數組斷言
我們可以檢查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(); } |
5.7。可比斷言
除了測試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); } |
5.8。迭代斷言
除了聲明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); } |
5.9。Map斷言
除了斷言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); } |
5.10。異常斷言
僅爲異常對象提供了兩種重要的方法。
我們可以編寫針對異常原因的斷言:
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"); } |
5.11。類斷言
對於類斷言,只有一種重要的方法可以用來測試一個類是否可分配給另一個:
1 2 3 4 5 6 |
@Test public void whenCheckingClass_thenIsAssignable() { Class<Double> aClass = Double.class;
assertThat(aClass).isAssignableTo(Number.class); } |
6. Java 8斷言
可選和流是Truth支持的僅有的兩種Java 8類型。
6.1。可選斷言
有三種重要的方法可以驗證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(); } |
6.2。流斷言
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部分。
7.Guava斷言
在本節中,我們將看到Truth中支持的Guava類型的斷言示例。
7.1。可選斷言
對於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(); } |
7.2。多圖斷言
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斷言”部分。
7.3。多對象斷言
多對象對象的斷言包括用於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); } |
7.4。表斷言
除了檢查它的大小或它的空白處,我們還可以檢查一個表以驗證它是否包含給定行和列的特定映射:
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"); } |
此外,我們可以檢查它是否包含給定的行,列或值。請參閱相關測試用例的源代碼。
8.自定義失敗消息和標籤
當斷言失敗時,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 |
9.擴展
擴展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); } |
10.結論
在本教程中,我們探索了Truth給我們編寫更具可讀性的測試和失敗消息的可能性。
我們展示了支持Java和番石榴類型,定製的失敗消息最流行的斷言方法,並擴展Truth定製主題。
與往常一樣,可以在Github上找到本文的完整源代碼。