มาทำความรู้จัก JUnit 5 กันเถอะ

Phayao Boonon
4 min readFeb 25, 2019

--

สำหรับ Java developer ส่วนใหญ่ ถ้าเขียน Unit test จะเคยใช้ JUnit สำหรับ Java application แต่ที่เราใช้อยู่เมื่อก่อนนี้ส่วนใหญ่จะเป็น JUnit 4 แต่มีเวอร์ชันล่าสุดที่เพิ่มประสิทธิภาพของ JUnit มากมาย นั้นก็คือ JUnit นั้นเอง ซึ่งเป้าหมายของเวอร์ชันนี้ก็คือรองรับ feature ต่างๆ ของ Java 8 ขึ้นไป รวมทั้งทำให้มีการ Test ในแบบต่างๆ เพิ่มขึ้นมากมาย

JUnit 5 คืออะไร?

ไม่เหมือนกับ JUnit เวอร์ชันก่อนหน้านี้ ซึ่ง JUnit 5 เป็นการรวมกันของส่วนประกอบหลัก 3 ส่วน คือ

JUnit 5 = JUnit Platfrom + JUnit Jupiter + JUnit Vintage

JUnit Platform

เป็นส่วนพื้นฐานของ JUnit ที่ทำหน้าที่เริ่มต้นกระบวนการทดสอบบน JVM โดยมีการกำหนด TestEngine API สำหรับพัฒนา Testing Platform เพื่อมาทำงานบน JUnit Platform นี้อีกทีหนึ่งได้ นอกจากนี้แล้วยังสามารถเรียกใช้งานการทดสอบด้วยคำสั่งด้วย Console Launcher และ JUnit 4 base Runner สำหรับทำงาน TestEngine ใน JUnit 4 environment ได้ด้วย ซึ่ง IDE หลักๆ อย่างเช่น Eclipse, IntelliJ หรือ VS Code ก็รองรับ JUnit 5 Platform ด้วย

JUnit Jupiter

เป็นการรวมกันของ programming model รูปแบบใหม่ ซึ่งมี annotation ใหม่ๆ มากมาย และ extension model สำหรับเขียนการทดสอบ (Test) และส่วนขยาย (Extension) ใน JUnit 5 ซึ่ง project ย่อยของ Jupiter จะมี TestEngine สำหรับทำให้การทดสอบที่สร้างด้วย Jupiter ทำงานได้บน JUnit 5 Platform

JUnit Vintage

เป็นส่วนที่มี TestEngine ที่ทำให้ JUnit 3 และ JUnit 4 ทำงานได้บน JUnit 5 Platform

https://subscription.packtpub.com/book/web_development/9781787285736/2/ch02lvl1sec13/junit-5-architecture

Install

JUnit 5 รองรับ build tools และ dependency management ยอดนิยมต่างๆ ทั้งหมด เช่น Maven, Gradle หรือ Ant ซึ่งตัวอย่างของการเพิ่ม JUnit 5 สำหรับ Maven และ Gradle ดังนี้

Maven

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.4.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.4.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.4.0</version>
<scope>test</scope>
</dependency>

Gradle

dependencies {
testImplementation('org.junit.jupiter:junit-jupiter:5.4.0')
}

test {
useJUnitPlatform()
testLogging {
events "passed", "skipped", "failed"
}
}

เริ่มต้นเขียน Test

เพื่อทดลองเขียน Test case ด้วย JUnit 5 จากตัวอย่าง (จากคู่มือ JUnit 5) ก็เลยลองเขียน Test case ง่ายๆ จะเห็นได้ว่า @Test annotation จะ import มาจาก Jupiter package ซึ่งเป็นการใช้งานส่วนของ JUnit Jupiter ที่ทดสอบผลรวมของ 2 จำนวน ของ Calculator class โดยใช้ assertEquals assertion มาทดสอบค่าที่ได้จาก method add

import com.iphayao.demo.Calculator;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class MyFirstJupiterTest {
private final Calculator calculator = new Calculator();

@Test
void
addition() {
assertEquals(2, calculator.add(1, 1));
}
}

Annotation

ใน JUnit Jupiter มี Annotation ใหม่ๆ และที่มีอยู่แต่เปลี่ยนแปลงมากมาย ซึ่จะยกตัวอย่างของ Annotation ที่สำคัญดังนี้

@BeforeAll/@BeforeEach

ใน JUnit Jupiter แยก @Before annotation ออกเป็น 2 annotation เพื่อจะ setup ก่อนทดสอบได้ 2 แบบ คือ @BeforeAll ทำก่อนการทดสอบทั้งหมด และ @BeforeEach ทำก่อนแต่ละการทดสอบ

@BeforeAll
static void
setup() {
log.info("Execute before all test method");
}

@BeforeEach
void
init() {
log.info("Execute before each test method");
}

@BeforeAll annotation จำเป็นต้องเป็น static method และ @BeforeEach จะต้องเป็น non static method

@AfterAll/@AfterEach

เช่นเดียวกับ @Before ใน JUnit Jupiter แยก @After ออกเป็น 2 annotation เพื่อที่จะดำเนินการหลังจากการทดสอบ คือ @AfterAll ทำหลังจากการทดสอบทั้งหมด และ @AfterEach ทำหลังแต่ละการทดสอบ

@AfterAll
static void
done() {
log.info("Execute after all test method");
}

@AfterEach
void
tearDown() {
log.info("Execute after each test method");
}

@AfterAll annotation จำเป็นต้องเป็น static method และ @AfterEach จะต้องเป็น non static method

@DisplayName/@Disable

ใน JUnit Jupiter ได้เพิ่ม @DisaplayName annotation สำหรับแสดงชื่อของการทดสอบ หรือ Test method ที่แตกต่างจากชื่อของ test method เองเพื่อที่จะทำให้ชื่อของการทดสอบเข้าใจง่ายยิ่งขึ้น และ @Disable annotation สำหรับปิด test method เพื่อที่จะไม่ต้องทำการทดสอบ test method นั้น สำหรับกรณีที่เราอาจจะไม่ต้องการทดสอบใน test method นั้นๆ

@Test
@DisplayName
("Addition test successful")
void additionTest() {
assertEquals(2, calculator.add(1, 1));
}

@Test
@Disabled
void
disableTest() {
assertEquals(3, calculator.add(1,1));
}

Assertion

Assertion ของ JUnit ย้ายมาใว้ใน package org.junit.jupiter.api.Assertions และมีการพัฒนาเพิ่มเติมมากมากที่สำคัญคือการรองรับ feature ของ Java 8 ที่ทำให้ใช้ rambdas ใน assertion ได้เลย

@Test
void groupAssertion() {
assertAll("person",
() -> assertEquals("John", person.getFirstName()),
() -> assertEquals("Doe", person.getLastName())
);
}
@Test
void dependencyAssertion() {
assertAll("properties",
() -> {
String firstName = person.getFirstName();
assertNotNull(firstName);

assertAll("first name",
() -> assertTrue(firstName.startsWith("J")),
() -> assertTrue(firstName.endsWith("n"))
);
}
);
}

ทำให้การทดสอบที่เขียนด้วย JUnit 5 สามารถมี assertion ที่ซับซ้อนได้ง่ายมากขึ้น

Assumption

จะใช้ Assumption กับการทดสอบที่ต้องตรวจสอบเงื่อนไขที่เป็นจริง โดยใช้สำหรับเงื่อนไขภายนอกที่จะทำการทดสอบ สามารถใช้ assumption ด้วย assumeTrue(), assumeFalse() และ assumingThat() และสามารถใช้ rambdas expression ได้

@Test
void
testOnlyOnProductionServer() {
assumeTrue("DEV".equals(System.getenv("ENV")), () -> "Aborting test: not on developer workstation");
assertEquals(2, calculator.add(1, 1));

}

@Test
void
testOnlyOnCiServer() {
assertFalse("CI".equals(System.getenv("ENV")));
assertEquals(2, calculator.add(1, 1));
}

@Test
void
testAllEnvironment() {
assumingThat("CI".equals(System.getenv("ENV")), () -> {
assertEquals(2, calculator.add(1, 1));
});
assertEquals(4, calculator.multiply(2, 2));
}

Exception

ใน JUnit 5 ได้เพิ่มให้รองรับ exception ได้ดีขึ้น ด้วย assertThrows() method มาตรวจสอบว่ามี exception เกิดขึ้นหรือไม่

@Test
void
exceptionTesting() {
Exception exception = assertThrows(ArithmeticException.class,
() -> calculator.divide(1, 0));
assertEquals("/ by zero", exception.getMessage());
}

และทำให้สามารถตรวจสอบข้อมูลของ exception ได้ด้วย

Dynamic Test

ใน JUnit 5 ได้เพิ่ม Dynamic Test feature ที่ทำให้เราสามารถประกาศและสร้างการทดสอบได้ในขณะ run-time ซึ่งตรงกันข้ามกับ Static Test ที่จะต้องระบุการทดสอบก่อนที่จะทำการทดสอบที่ compile-time

Dynamic Test สามารถสร้างได้ด้วย @TestFactory annotation และก็ใช้ rambdas expression ได้เช่นเดียวกัน

@TestFactory
Stream<DynamicTest> dynamicTestFromStream() {
return IntStream.iterate(0, n -> n + 2).limit(10)
.mapToObj(n -> dynamicTest("test" + n,
() -> assertTrue(n % 2 == 0)));
}

Repeat Test

ถ้าต้องทดสอบการทดสอบเดิมซ้ำๆ ใน JUnit 5 ก็มี feature รองรับความต้องการนี้ด้วย @RepeatTest annotation ทำให้เราสามารถทดสอบการทดสอบเดิมซ้ำได้ตามที่ต้องการ

@RepeatedTest(10)
void additionTest() {
assertEquals(2, calculator.add(1, 1));
}

Nested Test

@Nested annotation ทำให้ developer สร้างการทดสอบที่มีความซับซ้อนด้วยการซ้อนการทดสอบไว้ในอีกการทดสอบได้ ที่เป็นการทดสอบที่มีความสัมพันธ์กัน

public class TestStackDemo {
Stack<Object> stack;

@Test
void
isInstantiateWithNew() {
new Stack<>();
}

@Nested
class
WhenNew {
@BeforeEach
void
createNewStack() {
stack = new Stack<>();
}

@Test
void
isEmpty() {
assertTrue(stack.isEmpty());
}

@Nested
class
AfterPushing {
String element = "an element";

@BeforeEach
void
pushAnElement() {
stack.push(element);
}

@Test
void
isNotEmpty() {
assertFalse(stack.isEmpty());
}
}
}
}

Parameterized Test

ใน JUnit 5 สามารถจะทำการทดสอบหลายครั้ง ซึ่งแต่ละครั้งมีค่าของการทดสอบที่แตกต่างกันได้ด้วย @ParameterizeTest annotation โดยกำหนดค่าของการทดสอบด้วย @ValueSource และอีกหลาย annotation (@NullSource, @EmptySource, @NullAndEmptySource, @EnumSource เป็นต้น)

@ParameterizedTest
@ValueSource
(ints = {1, 2, 3, 4})
void testWithValueSource(int arg) {
assertTrue(arg > 0 && arg < 4);
}

สรุป

ในบทความนี้เป็นการแนะนำให้รู้จัก JUnit 5 ซึ่งเป็นเวอร์ชั่นใหม่ล่าสุดของ JUnit framework สำหรับ ชาว Java developer ทั้งหลาย โดยทีความสามารถที่เพิ่มขึ้นอีกมากมาย ซึ่งในบทความนี้เป็นเพียงแค่บางส่วนของความสารถทั้งหมดของ JUnit 5 ที่จะทำให้สร้างการทดสอบได้ง่ายและดียิ่งขึ้น เพื่อท้ายที่สุดจะทำให้ซอฟต์แวร์ที่เราสร้างนั้นมีคุณภาพสูงสุด

--

--

Phayao Boonon
Phayao Boonon

Written by Phayao Boonon

Software Engineer 👨🏻‍💻 Stay Hungry Stay Foolish

No responses yet