JUnit 5 AssertJ - Spring Boot 168 EP 12-3

JUnit 5 AssertJ – Spring Boot 168 EP 12-3

在設計自動化時,遵守的核心原則是 Arrange-Actor-Assert,3A 原則,斷言工具直接影響到用例的執行效率,提供高可讀性、流式驗證、更直覺的判斷方法, EP 12-3 增加了常見的 String 、 List 、 Map 、 Exception 等範例及採用 JUnit 5 單元測試來驗證產出結果。

前言

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 等。

發佈留言