為什麼要推薦 AssertJ
- 整合了 Junit4 的 Assertions
- 整合了 Junit5 的 Assertions
- 整合了 TestNG 的 Assertions
- 也整合了 Fest2.x (看官方文件是說 AssertJ 本身就是 Fest2.x 的分支)
Junit4, Junit5 的整合, 就很適合當作團隊的基礎驗證基礎.
官方提供 Junit 轉 AssertJ 小工具
我沒實際使用過, 有需要可以前往 github 下載
# windows 作業系統 # in the directory containing the test files convert-junit-assertions-to-assertj.sh
|
# OSX 作業系統 # in the directory containing the test files convert-junit-assertions-to-assertj-on-osx.sh
|
流暢的結構方便閱讀
流暢的 stream 風格, 搭配 IntelliJ IDE 開發起來心情真的很好.
@Test @DisplayName("數字測試") void assertNumbers() { int num = 1; Assertions.assertThat(num) .isPostive() .isNotNegative() isGreaterThanOrEqualTo(0) .isIn(1, 2, 3); }
|
支援 Optional 的特性
Optional
是 Java8 之後常用來處理 NPE 的一個特性, 在一些 method 的處理上, 為了要搭配 Java8 的 stream()
處理, 許多 method 都會設計為回傳 Optional 的型態.
@Test @DisplayName("Optional測試") void assertOptions() { Optional<String> opt = Optional.of("hello");
Assertions.assertThat(opt) .isPresent() .isNotEmpty() .get() .isEqualTo("hello"); }
|
SoftAssertions (不知道要怎麼翻譯)
在某些測試情境是需要整個流程走完, 再總結一次驗證, 比較常用的情境像是集合型的資料驗證, 或是多個流程耦合再一起要驗證的情境.
@Test @DisplayName("SoftAssertions測試") void assertSoftAssertions() { List<String> member = Lists.newArrayList("Frodo", "Sam", "Gimli");
try (var softly = new AutoCloseableSoftAssertions()) { softly.assertThat(member).isInstanceOf(List.class); softly.assertThat(member.size()).as("會員人數").isEqualTo(2); softly.assertThat(member.get(1)).as("1 號會員姓名").isEqualTo("Frodo"); } }
|
驗證結果, 不會因為 會員人數 的驗證失敗, 就沒驗證 1 號會員姓名. SoftAssertions 能夠在測試過程中, 了解整個作業失敗所有細節的測試.
反向測試
反向測試, 也是測試情境中重要的一環, 特別是一些 Exception 的處理, 在處理 Exception 的測試, AssertJ 可以簡單協助你驗證 Root Exception.
@Test @DisplayName("Root Exception 測試") void testRootThrowable() { var throwable = Assertions.catchThrowable(() -> new IllegalStateException("狀態錯誤", new IllegalArgumentException("參數錯誤"))); Assertions.assertThat(throwable) .isInstanceOf(IllegalStateException.class) .hasMessageContaining("狀態錯誤") .hasRootCauseExactlyInstanceOf(IllegalStateException.class) .hasMessageContaining("參數錯誤"); }
|
自定義的 Assertions
AssertJ 可以針對某個 domain / pojo 物件定義 Assert, 隨便用 coupon 折扣卷來舉例.
@Data @Builder @NoArgsConstructor @AllArgsConstructor class Coupon {
String code;
LocalDate effected;
LocalDate expired; }
|
自定義的 Assert 需繼承 AbstractAssert<SELF, ACTUAL>
, SELF 與 ACTUAL 分別是 Java 採用 Java 的 Generic Types 設計, SELF 代表自定義的 Assert 實體, ACTUAL 代表你要驗證的 domain / pojo 物件.
這範例有點長了, 抱歉啦
public class CouponAssert extends AbstractAssert<CouponAssert, Coupon> {
protected CouponAssert(Coupon coupon, Class<?> selfType) { super(coupon, selfType); }
protected CouponAssert(Coupon coupon) { super(coupon, CouponAssert.class); }
public static CouponAssert assertThat(Coupon actual) { return new CouponAssert(actual); }
public CouponAssert hasCode() { if (StringUtils.isBlank(actual.getCode())) { failWithMessage("折扣碼不可為空.", null); } return this; }
public CouponAssert codeLengthShouldBeFive() { if (5 != actual.getCode().length()) { failWithMessage("折扣碼長度必須為 5 碼.", null); } return this; }
public CouponAssert hasExpired() { if (Objects.isNull(actual.getExpired())) { failWithMessage("折扣碼需設定失效日期.", null); } return this; } }
|
測試寫法
@Test @DisplayName("Coupon Domain 測試") void testCouponCode() { Coupon coupon = Coupon.builder() .code("ABD") .effected(LocalDate.now()) .expired(LocalDate.now().plusMonths(1L)) .build();
CouponAssert.assertThat(coupon) .hasCode() .codeLengthShouldBeFive() .hasExpired(); }
|