在設計自動化時,遵守的核心原則是 Arrange-Actor-Assert,3A 原則,斷言工具直接影響到用例的執行效率,提供高可讀性、流式驗證、更直覺的判斷方法, EP 12-3 增加了常見的 String 、 List 、 Map 、 Exception 等範例及採用 JUnit 5 單元測試來驗證產出結果。
Table of Contents
Toggle前言
AssertJ 是一個撰寫斷言的套件、流式斷言器,常見的斷言器一條斷言語句,只能對實際值斷言一個校驗點,而流式斷言器,支援一條斷言語句,對實際值同時斷言多個校驗點,語法跟自然語言相近,對於編寫測試時,容易閱讀及維護上提供了相當大的改進。
JUnit 5 AssertJ
檔案目錄
+- build.gradle
+- src
+- main
| +- java
| +- org
| +- ruoxue
| +- spring_boot_168
| +- sso
| +- member
| +- repository
| +- MemberRepository.java
| +- MemberRepositoryImpl.java
+- test
+- java
+- org
+- ruoxue
+- spring_boot_168
+- sso
+- member
+- repository
+- MemberRepositoryImplTest.java
Gradle
build.gradle
Spring Boot Starter Test 排除 JUnit 4 ,增加 JUnit 5,排除 AssertJ 3.11.1,使用 3.23.1 。
plugins 增加 Spring Boot 、Dependency Management 。
修改完後,點右鍵,Gradle -> Refresh Gradle Project 。
buildscript {
group 'org.ruoxue.spring-boot-168'
version = '0.0.1-SNAPSHOT'
ext {
springBootVersion = '2.1.7.RELEASE'
assertjVersion = '3.23.1'
}
}
plugins {
id 'java-library'
id 'eclipse'
id 'org.springframework.boot' version '2.1.7.RELEASE'
id 'io.spring.dependency-management' version '1.0.6.RELEASE'
}
dependencies {
testImplementation ("org.springframework.boot:spring-boot-starter-test:${springBootVersion}") {
exclude group: 'junit', module: 'junit'
exclude group: 'org.assertj', module: 'assertj-core'
}
testImplementation "org.assertj:assertj-core:${assertjVersion}"
testImplementation "org.junit.jupiter:junit-jupiter-api"
testRuntimeOnly "org.junit.platform:junit-platform-launcher"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine"
testRuntimeOnly "org.junit.vintage:junit-vintage-engine"
}
test {
useJUnitPlatform()
}
JUnit 5
assertEquals(expected, actual);
assertEquals(expected, actual, “assertion desc”);
AssertJ
assertThat(actual).isEqualTo(expected);
assertThat(actual).as(“assertion desc”).isEqualTo(expected);
實作
MemberRepository.java
建立 Repository 介面,宣告 findName 、 findAll 、 updateName 方法,此時並沒有任何實作。
package org.ruoxue.spring_boot_168.sso.member.repository;
import java.util.List;
import java.util.Map;
public interface MemberRepository {
String findName(String cid);
List<String> findAll();
Map<String, String> findAll(int page, int size);
int updateName(String cid, String name);
}
MemberRepositoryImpl.java
實作方法,傳回 String 、 List 、 Map 、 int 等資料型別,在單元測試時,驗證這些返回值,是否有達到預期的標準。
package org.ruoxue.spring_boot_168.sso.member.repository;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import com.google.common.collect.ImmutableMap;
public class MemberRepositoryImpl implements MemberRepository {
@Override
public String findName(String cid) {
return "player";
}
@Override
public List<String> findAll() {
List<String> list = Arrays.asList("ruoxue", "ruoxue2");
return list;
}
@Override
public Map<String, String> findAll(int page, int size) {
Map<String, String> map = ImmutableMap.of("ruoxue", "player");
return map;
}
@Override
public int updateName(String cid, String name) {
return 1 / 0;
}
}
測試 JUnit 5
assertThat findName
@Test
public void findName() {
String name = memberRepository.findName("ruoxue");
System.out.println(name);
assertThat(name)
.isEqualTo("player")
.isEqualToIgnoringCase("Player")
.startsWith("p")
.endsWith("r")
.contains("play")
;
}
assertThat findAll
@Test
public void findAll() {
List<String> list = memberRepository.findAll();
System.out.println(list);
assertThat(list)
.hasSize(2)
.contains("ruoxue","ruoxue2")
.contains("ruoxue",Index.atIndex(0))
.contains("ruoxue2",Index.atIndex(1))
.doesNotContain("player")
;
}
assertThatThrownBy findAllThrowException
@Test
public void findAllThrowException() {
assertThatThrownBy(() -> {
List<String> list = memberRepository.findAll();
list.get(2);
})
.isInstanceOf(IndexOutOfBoundsException.class)
.hasMessageContaining("2")
;
}
assertThat findAllByPage
@Test
public void findAllByPage() {
Map<String, String> map = memberRepository.findAll(0,10);
System.out.println(map);
assertThat(map)
.hasSize(1)
.extractingByKey("ruoxue", as(InstanceOfAssertFactories.STRING))
.isEqualToIgnoringCase("player")
.endsWith("r")
.contains("play")
;
assertThat(map).extracting("ruoxue")
.isEqualTo("player");
}
assertThatThrownBy updateName
@Test
public void updateName() {
assertThatThrownBy(() -> memberRepository.updateName("ruoxue", "test_player"))
.isInstanceOf(ArithmeticException.class)
.hasMessageContaining("zero")
.hasMessage("/ by zero")
;
}
MemberRepositoryImplTest.java
package org.ruoxue.spring_boot_168.sso.member.repository;
import static org.assertj.core.api.Assertions.*;
import java.util.List;
import java.util.Map;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.assertj.core.data.Index;
import org.junit.jupiter.api.Test;
public class MemberRepositoryImplTest {
private MemberRepository memberRepository = new MemberRepositoryImpl();
@Test
public void findName() {
String name = memberRepository.findName("ruoxue");
System.out.println(name);
assertThat(name)
.isEqualTo("player")
.isEqualToIgnoringCase("Player")
.startsWith("p")
.endsWith("r")
.contains("play")
;
}
@Test
public void findAll() {
List<String> list = memberRepository.findAll();
System.out.println(list);
assertThat(list)
.hasSize(2)
.contains("ruoxue","ruoxue2")
.contains("ruoxue",Index.atIndex(0))
.contains("ruoxue2",Index.atIndex(1))
.doesNotContain("player")
;
}
@Test
public void findAllThrowException() {
assertThatThrownBy(() -> {
List<String> list = memberRepository.findAll();
list.get(2);
})
.isInstanceOf(IndexOutOfBoundsException.class)
.hasMessageContaining("2")
;
}
@Test
public void findAllByPage() {
Map<String, String> map = memberRepository.findAll(0,10);
System.out.println(map);
assertThat(map)
.hasSize(1)
.extractingByKey("ruoxue", as(InstanceOfAssertFactories.STRING))
.isEqualToIgnoringCase("player")
.endsWith("r")
.contains("play")
;
assertThat(map).extracting("ruoxue")
.isEqualTo("player");
}
@Test
public void updateName() {
assertThatThrownBy(() -> memberRepository.updateName("ruoxue", "test_player"))
.isInstanceOf(ArithmeticException.class)
.hasMessageContaining("zero")
.hasMessage("/ by zero")
;
}
}
故障排除
找不到 assertj InstanceOfAssertFactories
執行 JUnit Test,拋出例外,發生錯誤,stack trace 如下:
java.lang.Error: Unresolved compilation problem:
InstanceOfAssertFactories cannot be resolved to a variable
at org.ruoxue.spring_boot_168.sso.member.repository.MemberRepositoryImplTest.findAllByPage(MemberRepositoryImplTest.java:59)
這是因為比較早期的版本,並沒有 InstanceOfAssertFactories 這個類別,所以在 build.gradle 設定排除舊版,引用新版,修改完後,再次執行,就能順利運行。
// 修改前
implementation ("org.springframework.boot:spring-boot-starter-test:${springBootVersion}") {
}
// 修改後
implementation ("org.springframework.boot:spring-boot-starter-test:${springBootVersion}") {
exclude group: 'org.assertj', module: 'assertj-core'
}
testImplementation "org.assertj:assertj-core:3.23.1"
心得分享
Assertions 提供了靜態斷言方法 assertThat ,使用了重載的方法,支援了所有基本類型,像是 int 、 double 及 String 、 Map 與 Iterable 等。