Java 單元測試中多個屬性的單個斷言調用
一、概述
作為程序員,我們經常編寫測試以確保我們的代碼按預期工作。測試中的標準做法之一是使用斷言。
當我們想要驗證一個對象的多個屬性時,我們可以編寫一堆斷言來完成工作。
但是,在本教程中,我們將探討如何在一次斷言調用中驗證多個屬性。
二、問題介紹
在很多情況下,我們需要檢查一個對象的多個屬性。傳統上,這意味著為每個屬性編寫單獨的斷言語句,這會使代碼冗長且難以閱讀。
但是,更好的方法是對多個屬性使用單個斷言調用。那麼接下來,讓我們看看它是如何完成的。
為了更直接的演示,首先,我們以一個POJO類為例:
class Product {
private Long id;
private String name;
private String description;
private boolean onSale;
private BigDecimal price;
private int stockQuantity;
// constructor with all properties is omitted
// getters and setters are omitted
}
Product
類有六個屬性。假設我們已經實現了一個程序來生成Product
實例。通常,我們將生成的Product
實例與預期對象進行比較,以斷言程序是否有效,例如assertEquals(EXPECTED_PRODUCT, myProgram.createProduct())
。
但是,在我們的程序中, id
和description
是不可預測的。換句話說,如果我們可以驗證其餘四個字段( name, onSale, price, and stockQuantity
)保持預期值,我們就認為程序正確地完成了工作。
接下來,讓我們創建一個Product
對像作為預期結果:
Product EXPECTED = new Product(42L, "LG Monitor", "32 inches, 4K Resolution, Ideal for programmers", true, new BigDecimal("429.99"), 77);
為簡單起見,我們不會真正實現創建Product
對象的方法。相反,讓我們簡單地創建一個Product
實例來保存所需的值,因為我們的重點是如何在一個語句中聲明四個屬性:
Product TO_BE_TESTED = new Product(-1L, "LG Monitor", "dummy value: whatever", true, new BigDecimal("429.99"), 77);
那麼接下來,讓我們看看如何組織斷言。
3.使用JUnit5的assertAll()
JUnit 是最流行的單元測試框架之一。最新版本 JUnit 5 帶來了許多新特性。例如, assertAll()
就是其中之一。
JUnit 5 的assertAll()
方法接受一個斷言列表,所有斷言都將在一次調用中執行。此外,如果任何斷言失敗,則測試將失敗,並且將報告所有失敗。
接下來,讓我們將屬性斷言組合到一個assertAll()
調用中:
assertAll("Verify Product properties",
() -> assertEquals(EXPECTED.getName(), TO_BE_TESTED.getName()),
() -> assertEquals(EXPECTED.isOnSale(), TO_BE_TESTED.isOnSale()),
() -> assertEquals(EXPECTED.getStockQuantity(), TO_BE_TESTED.getStockQuantity()),
() -> assertEquals(EXPECTED.getPrice(), TO_BE_TESTED.getPrice()));
正如我們所見, assertAll()
方法在一次調用中組合了四個斷言。值得一提的是, price
字段的類型是BigDecimal
。我們使用assertEquals()
來驗證BigDecimal
對象的值和小數位數。
我們已經實現了我們的目標。但是,如果我們仔細查看代碼,在assertAll()
體內,我們仍然有四個斷言,即使它們是 lambda 表達式格式。因此,代碼還是有點冗長。
接下來,讓我們看看在一次調用中斷言這四個屬性的其他方法。
4.使用AssertJ的extracting()
和containsExactly()
AssertJ 是一個功能強大的 Java 庫,它提供了流暢且直觀的 API,用於在測試中編寫斷言。它提供了extracting()
方法,允許我們只從一個對像中提取我們需要的屬性值。提取的值存儲在列表中。然後,AssertJ 提供了其他方法來驗證列表。例如,我們可以使用containsExactly()
來驗證實際組是否按順序完全包含給定的值,而沒有其他內容。
接下來,讓我們組裝extracting()
和containsExactly()
:
assertThat(TO_BE_TESTED)
.extracting("name", "onSale", "stockQuantity", "price")
.containsExactly(EXPECTED.getName(), EXPECTED.isOnSale(), EXPECTED.getStockQuantity(), EXPECTED.getPrice());
正如我們所見,AssertJ 的extracting()
和containsExactly()
允許我們編寫更簡潔和更具表現力的斷言。
如上面的代碼所示,將屬性名稱作為字符串傳遞給extracting()
方法非常簡單。但是,由於名稱是純字符串,因此它們可能包含拼寫錯誤。此外,如果我們重命名屬性,測試方法仍然可以毫無問題地編譯。在我們運行測試之前,我們不會看到問題。另外,最終找到命名問題可能需要一些時間。
因此,AssertJ 支持將 getter 方法引用而不是屬性名稱傳遞給extracting()
:
assertThat(TO_BE_TESTED)
.extracting(Product::getName, Product::isOnSale, Product::getStockQuantity,Product::getPrice)
.containsExactly(EXPECTED.getName(), EXPECTED.isOnSale(), EXPECTED.getStockQuantity(), EXPECTED.getPrice());
5. 使用 AssertJ 的returns()
和from()
AssertJ 為各種需求提供了一組豐富的斷言。我們已經學會了使用extracting()
和containsExactly()
在一次斷言調用中驗證多個屬性。在我們的示例中,我們將檢查四個屬性。然而,我們可能想要驗證現實世界中的十個屬性。隨著待檢查屬性數量的增加,斷言行變得難以閱讀。另外,寫這麼長的斷言行很容易出錯。
接下來,讓我們看看使用 AssertJ 的returns()
和from()
方法的替代方法。用法非常簡單: assertThat(ToBeTestedObject).returns(Expected, from(FunctionToGetTheValue)).
因此, returns()
方法驗證被測對像從給定函數FunctionToGetTheValue
返回Expected
值。
接下來,讓我們應用這種方法來驗證Product
對象:
assertThat(TO_BE_TESTED)
.returns(EXPECTED.getName(), from(Product::getName))
.returns(EXPECTED.isOnSale(), from(Product::isOnSale))
.returns(EXPECTED.getStockQuantity(), from(Product::getStockQuantity))
.returns(EXPECTED.getPrice(), from(Product::getPrice));
正如我們所見,代碼流暢且易於閱讀。此外,即使我們需要驗證許多屬性,我們也不會迷路。
值得一提的是,AssertJ 提供了doesNotReturn()
方法來驗證from()
結果是否與預期值匹配。此外,我們可以在同一個斷言中使用doesNotReturn()
和returns()
。
最後,讓我們編寫一個混合了returns()
和doesNotReturn()
方法的單行斷言:
assertThat(TO_BE_TESTED)
.returns(EXPECTED.getName(), from(Product::getName))
.returns(EXPECTED.isOnSale(), from(Product::isOnSale))
.returns(EXPECTED.getStockQuantity(), from(Product::getStockQuantity))
.returns(EXPECTED.getPrice(), from(Product::getPrice))
.doesNotReturn(EXPECTED.getId(), from(Product::getId))
.doesNotReturn(EXPECTED.getDescription(), from(Product::getDescription));
六,結論
使用單個斷言調用來測試多個屬性提供了許多好處,例如提高可讀性、不易出錯、更好的可維護性等。
在本文中,我們通過示例學習了三種在一次斷言調用中驗證多個屬性的方法:
- JUnit5——
assertAll()
- AssertJ –
extracting()
和containsExactly()
- AssertJ –
returns(), doesNotReturn(),
和from()
與往常一樣,本文中提供的所有代碼片段都可以在 GitHub 上找到。