文章目錄
前言
在設計自動化接口 Cases 時,遵守的核心原則是3A(Arrange-> Actor ->Assert)原則;
斷言工具是否強大直接影響到用例的執行效率,本文將介紹目前主流的一種流式斷言神器:AssertJ。
AssertJ簡介
什麼是流式,常見的斷言器一條斷言語句只能對實際值斷言一個校驗點,而流式斷言器,支持一條斷言語句對實際值同時斷言多個校驗點,簡單理解,即 AssertJ 斷言是可以串接的。
AssertJ 是一個 Java 庫,爲 JDK 標準類型提供斷言,可以與 JUnit,TestNG 或任何其他測試框架一起使用。
不同的 AssertJ 主要版本依賴於不同的 Java 版本:
- AssertJ 3.x 需要 Java 8或更高版本
- AssertJ 2.x 需要 Java 7或更高版本
- AssertJ 1.x 需要 Java 6或更高版本
請注意,AssertJ 3.x包含所有AssertJ 2.x功能,並添加了Java 8特定功能(如 lambdas 的異常斷言)
AssertJ 支持如下模塊:
-
Core:AssertJ core is a Java library that provides a fluent interface for writing assertions.
-
Assertions generator:Use the Assertion Generator to create assertions specific to your own classes.
-
Guava:AssertJ assertions for Guava provides assertions for Guava types like Multimap, Table, Optional, Range or ByteSource.
-
Joda-Time:AssertJ assertions for Joda-Time provides assertions for Joda-Time types like DateTime and LocalDateTime.
-
DB:AssertJ-DB provides assertions to test data in a database.
-
Neo4j:Provides assertions for Neo4j 3 or higher.
-
Swing:AssertJ Swing is a Java library that provides a fluent interface for functional Swing UI testing.
官方網站上提供了所有模塊的詳細列表。
地址:https://joel-costigliola.github.io/assertj/index.html
讓我們從幾個例子開始,直接來自 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);
以上的例子只是冰山一角,下面我們將介紹如何使用這個庫編寫斷言
AssertJ使用
導包
SpringBoot 內置了 AssertJ,只需要導入 spring-boot-starter-test
依賴包
<dependencies>
<!--引入效率插件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--引入測試包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--引入testng測試框架-->
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.14.3</version>
</dependency>
</dependencies>
入門使用
爲了編寫一個斷言,你總是需要先將對象傳遞給 Assertions.assertThat()
方法,然後再按照實際的斷言進行操作。
重要的是要記住,與其他一些庫不同,下面的代碼實際上並沒有斷言任何東西,並且永遠不會失敗測試:
assertThat(anyRefenceOrValue);
如果你使用IDE的代碼完成功能,由於其描述性非常強的方法,編寫AssertJ 斷言變得異常簡單。下圖就是它在IntelliJ IDEA
中的樣子:
如圖所見,有許多可供選擇的上下文方法,並且這些方法僅適用於String類型。
對象斷言
可以以各種方式比較對象,以確定兩個對象的相等性或檢查對象的字段。
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Dog {
private String name;
private Float weight;
}
讓我們看兩種方法,我們可以比較兩個對象的相等性。鑑於以下兩個Dog對象 fido和 fidosClone
@Test(description = "對象斷言1")
public void whenComparingReferences_thenNotEqual() {
// 實例化兩個對象
Dog fidos= new Dog("fidos",5.14f);
Dog fidosClone = new Dog("fidosClone",5.14f);
// 斷言兩個對象引用
assertThat(fidos).isNotEqualTo(fidosClone);
}
isEqualTo()
是比較對象引用,所以會執行失敗。如果我們想要比較它們的內容,我們可以使用 isEqualToComparingFieldByFieldRecursively()
@Test(description = "對象斷言2")
public void whenComparingFields_thenEqual() {
// 實例化兩個對象
Dog fido = new Dog("Fido", 5.15f);
Dog fidosClone = new Dog("Fido", 5.15f);
// 斷言兩個對象內容
assertThat(fido).isEqualToComparingFieldByFieldRecursively(fidosClone);
}
當通過字段比較執行遞歸字段時,Fido和fidosClone是相等的,因爲一個對象的每個字段與另一個對象中的字段進行比較。
還有許多其他斷言方法提供了比較和收縮對象以及檢查和斷言其字段的不同方法。具體請參閱官方的 AbstractObjectAssert
API。
布爾斷言
真值測試有一些簡單的方法:
- isTrue()
- isFalse()
舉個例子:
@Test(description = "布爾斷言")
public void whenisEmpty_isTrue() {
// 實例化對象
Dog fido = new Dog("Fido", 5.15f);
// 斷言字段是否爲空
assertThat(fido.getName().isEmpty()).isTrue();
}
Iterable/Array斷言
對於 Iterable 或 Array,有多種方法可以斷言它們的內容是否存在。最常見的斷言之一是檢查 Iterable 或 Array 是否包含給定元素:
或者如果 List 不爲空:
assertThat(list).isNotEmpty();
或者如果 List 以給定字符開頭。例如“1”:
assertThat(list).startsWith("1");
如果要爲同一對象創建多個斷言,可以輕鬆地將它們連接在一起。
下面是一個斷言示例,它檢查提供的列表是否爲空,包含“1”元素,不包含任何空值幷包含元素序列“2”,“3”:
assertThat(list)
.isNotEmpty()
.contains("1")
.doesNotContainNull()
.containsSequence("2", "3");
當然,對於那些類型存在更多可能的斷言 。具體請參閱官方的AbstractIterableAssert
API
完整示例:
@Test(description = "Iterable/Array斷言1")
public void whenCheckingForElement_thenContains(){
List<String> list = Arrays.asList("1", "2", "3");
// 斷言是否包含給定元素
assertThat(list).contains("1");
}
@Test(description = "Iterable/Array斷言2")
public void whenCheckingForElement_thenMultipleAssertions() {
List<String> list = Arrays.asList("1", "2", "3");
// 斷言list不爲空
assertThat(list).isNotEmpty();
// 斷言list以給定字段開頭
assertThat(list).startsWith("1");
// 斷言list不包含null
assertThat(list).doesNotContainNull();
// 多個斷言
assertThat(list).isNotEmpty().contains("1").startsWith("1").doesNotContainNull().containsSequence("2", "3");
}
字符斷言
字符類型的斷言主要涉及比較,甚至檢查給定字符是否來自 Unicode 表。
下面是一個斷言示例,它檢查提供的字符是否不是 ‘a’,在 Unicode 表中,是否大於 ‘b’ 並且是小寫的:
assertThat(someCharacter)
.isNotEqualTo('a')
.inUnicode()
.isGreaterThanOrEqualTo('b')
.isLowerCase();
有關所有字符類型斷言的詳細列表,請參閱 AbstractCharacterAssert
API
完整示例:
@Test(description = "字符斷言")
public void whenCheckingCharacter_thenIsUnicode() {
char someCharacter = 'c';
// 斷言字符是否不是 'a',在 Unicode 表中,是否大於 'b' 並且是小寫的
assertThat(someCharacter).isNotEqualTo('a').inUnicode().isGreaterThanOrEqualTo('b').isLowerCase();
}
類斷言
Class 類型的斷言主要是檢查其字段,類類型,註釋的存在和類的最終性。
如果你想斷言Runnable類是一個接口,你需要簡單地寫:
assertThat(Runnable.class).isInterface();
或者如果你想檢查一個類是否可以從另一個類中分配:
assertThat(Exception.class).isAssignableFrom(NoSuchElementException.class);
可以在 AbstractClassAssert
API 中查看所有可能的類斷言。
完整示例:
@Test(description = "類斷言1")
public void whenCheckingRunnable_thenIsInterface() {
// 斷言Runnable類是一個接口
assertThat(Runnable.class).isInterface();
}
@Test(description = "類斷言2")
public void whenAssigningNSEExToException_thenIsAssignable(){
// 斷言一個類是否可以從另一個類中分配
assertThat(Exception.class).isAssignableFrom(NoSuchElementException.class);
}
文件斷言
文件斷言都是關於檢查給定的文件實例是否存在,是目錄還是文件,具有某些內容,是否可讀或具有擴展名。
在這裏斷言的示例,該斷言檢查給定文件是否存在,是文件而不是目錄,可讀寫的:
assertThat(someFile)
.exists()
.isFile()
.canRead()
.canWrite();
可以在 AbstractFileAssert
API 中查看所有可能的類斷言。
完整示例:
@Test(description = "文件斷言")
public void whenCheckingFile_then() throws IOException {
final File someFile = File.createTempFile("aaa", "bbb");
someFile.deleteOnExit();
// 斷言文件是否存在,是文件而不是目錄,可讀寫的
assertThat(someFile).exists().isFile().canRead().canWrite();
}
Double/Float/Integer斷言
數字斷言都是關於比較給定偏移量內或沒有給定偏移量的數值。
例如,如果要根據給定的精度檢查兩個值是否相等,我們可以執行以下操作:
assertThat(5.1).isEqualTo(5, withPrecision(1d));
請注意,我們使用已導入的 withPrecision(雙偏移)輔助方法來生成偏移對象。
有關更多斷言,請訪問 AbstractDoubleAssert
API。
InputStream斷言
只有一個可用的 InputStream 特定斷言:
hasSameContentAs
(預期的 InputStream)
使用方法:
assertThat(given).hasSameContentAs(expected);
完整示例:
@Test(description = "InputStream斷言")
public void whenCheckingIS_then() {
InputStream given = new ByteArrayInputStream("foo".getBytes());
InputStream expected = new ByteArrayInputStream("foo".getBytes());
// 斷言是否預期的 InputStream
assertThat(given).hasSameContentAs(expected);
}
Map斷言
Map 斷言允許你分別檢查 Map 是否包含某些條目,條目集或鍵/值。
你可以看到斷言的示例,該斷言檢查給定的Map是否爲空,包含key “2”,不包含數字鍵“10”幷包含條目:key:2,value:“a”:
assertThat(map)
.isNotEmpty()
.containsKey(2)
.doesNotContainKeys(10)
.contains(entry(2, "a"));
有關更多斷言,請參閱 AbstractMapAssert
API。
完整示例:
@Test(description = "Map斷言")
public void whenGivenMap_then() {
Map<Integer, String> map = Maps.newHashMap(2, "a");
// 斷言Map是否爲空,包含key “2”,不包含key “10” 幷包含元素:key:2,value:“a”
assertThat(map).isNotEmpty().containsKey(2).doesNotContainKeys(10).contains(entry(2, "a"));
}
Throwable 斷言
Throwable 的斷言允許例如:檢查異常的信息,蹤跡,原因檢查或者異常被拋出已驗證。
讓我們看一下斷言示例,該斷言檢查是否拋出了給定的異常並且消息以“c”結尾:
assertThat(ex).hasNoCause().hasMessageEndingWith("c");
有關更多斷言,請參閱 AbstractThrowableAssert
API。
完整示例:
@Test(description = "Throwable斷言")
public void whenGivenException_then() {
Exception ex = new Exception("abc");
// 斷言是否拋出了給定的異常並且消息以“c”結尾
assertThat(ex).hasNoCause().hasMessageEndingWith("c");
}
描述斷言
爲了獲得更高的詳細級別,你可以爲斷言創建動態生成的自定義描述。
這樣做的關鍵是 as(String description,Object … args)方法。
如果你定義這樣的斷言:
assertThat(fidos.getWeight())
.as("%s's age should be equal to 5.15f")
.isEqualTo(5.15f);
這是運行測試時的結果:
org.junit.ComparisonFailure: [%s's age should be equal to 5.15f]
Expected :5.1[5]f
Actual :5.1[4]f
完整示例:
@Test(description = "描述斷言")
public void whenRunningAssertion_thenDescribed() throws Exception {
Dog fidos= new Dog("fidos",5.14f);
assertThat(fidos.getWeight()).as("%s's age should be equal to 5.15f").isEqualTo(5.15f);
}
小結
在本文中,我們簡要探討了AssertJ 爲核心Java類型提供最流行的流式斷言的使用方法。