[Java Backend Zero to Hello] [Phase 7] BÀI 7.1: UNIT TEST VỚI JUNIT 5
📚 Series: Java Backend Zero to Hello 📂 Phân đoạn: Phase 7: Testing & DevOps 📖 Nội dung: BÀI 7.1: UNIT TEST VỚI JUNIT 5 💡 Khóa học lập trình Backend Java & Spring Boot chuẩn doanh nghiệp từ con số 0.
BÀI 7.1: UNIT TEST VỚI JUNIT 5
Mục tiêu
- Viết Unit Test với JUnit 5
- Sử dụng Assertions
- Áp dụng AAA Pattern
- Test với Parameterized Tests
1. JUNIT 5 LÀ GÌ?
JUnit 5 là framework testing phổ biến nhất cho Java, gồm:
- JUnit Platform - Nền tảng chạy test
- JUnit Jupiter - API viết test
- JUnit Vintage - Chạy test JUnit 3/4
2. DEPENDENCY
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
Bao gồm: JUnit 5, Mockito, AssertJ, Spring Test, ...
3. CẤU TRÚC TEST
3.1 Test class cơ bản
import org.junit.jupiter.api.*;
class UserServiceTest {
@BeforeAll
static void setupAll() {
// Chạy 1 lần trước tất cả test
}
@BeforeEach
void setup() {
// Chạy trước mỗi test
}
@AfterEach
void teardown() {
// Chạy sau mỗi test
}
@AfterAll
static void teardownAll() {
// Chạy 1 lần sau tất cả test
}
@Test
void shouldCreateUser() {
// Test logic
}
@Test
@Disabled // Bỏ qua test này
void skippedTest() {
// ...
}
}
3.2 DisplayName
@Test
@DisplayName("Should create user successfully when input is valid")
void shouldCreateUser() { ... }
4. ASSERTIONS
4.1 AssertJ (khuyến nghị)
import static org.assertj.core.api.Assertions.*;
@Test
void shouldCreateUser() {
User user = userService.create(request);
assertThat(user).isNotNull();
assertThat(user.getName()).isEqualTo("An");
assertThat(user.getAge()).isGreaterThan(0);
assertThat(user.getEmail()).contains("@");
}
4.2 JUnit Assertions
import static org.junit.jupiter.api.Assertions.*;
@Test
void shouldCreateUser() {
User user = userService.create(request);
assertNotNull(user);
assertEquals("An", user.getName());
assertTrue(user.getAge() > 0);
assertThrows(NotFoundException.class, () -> userService.findById(999L));
}
4.3 Assert nhiều giá trị
assertAll("user",
() -> assertEquals("An", user.getName()),
() -> assertEquals("an@example.com", user.getEmail()),
() -> assertTrue(user.isActive())
);
5. AAA PATTERN
@Test
void shouldCalculateTotal() {
// Arrange - Chuẩn bị
Order order = new Order();
order.addItem(new Item("Product", 100.0));
order.addItem(new Item("Product 2", 50.0));
// Act - Thực hiện
double total = orderService.calculateTotal(order);
// Assert - Kiểm tra
assertThat(total).isEqualTo(150.0);
}
6. ASSUMPTIONS
@Test
void testOnWindows() {
Assumptions.assumeTrue(System.getProperty("os.name").contains("Windows"));
// Test chỉ chạy trên Windows
}
@Test
void testOnMac() {
Assumptions.assumeFalse(System.getProperty("os.name").contains("Mac"));
// ...
}
7. NESTED TESTS
@DisplayName("User Service Tests")
class UserServiceTest {
@Nested
@DisplayName("When creating user")
class CreateUser {
@Test
void shouldSucceedWithValidData() { ... }
@Test
void shouldFailWithInvalidEmail() { ... }
}
@Nested
@DisplayName("When finding user")
class FindUser {
@Test
void shouldReturnUserWhenExists() { ... }
@Test
void shouldThrowWhenNotFound() { ... }
}
}
8. PARAMETERIZED TESTS
8.1 @ValueSource
@ParameterizedTest
@ValueSource(ints = {1, 2, 3, 5, 10})
void shouldBePositive(int number) {
assertThat(number).isPositive();
}
@ParameterizedTest
@ValueSource(strings = {"An", "Bình", "Châu"})
void shouldNotBeEmpty(String name) {
assertThat(name).isNotEmpty();
}
8.2 @CsvSource
@ParameterizedTest
@CsvSource({
"1, An, 25",
"2, Bình, 30",
"3, Châu, 28"
})
void shouldCreateUser(Long id, String name, int age) {
User user = new User(id, name, age);
assertThat(user.getId()).isEqualTo(id);
}
8.3 @MethodSource
@ParameterizedTest
@MethodSource("provideUsers")
void shouldValidateUser(User user, boolean expected) {
assertThat(validator.isValid(user)).isEqualTo(expected);
}
static Stream<Arguments> provideUsers() {
return Stream.of(
Arguments.of(new User("An", "an@example.com"), true),
Arguments.of(new User("", "an@example.com"), false),
Arguments.of(new User("An", "invalid"), false)
);
}
9. TEST LIFECYCLE
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class UserServiceTest {
@BeforeAll
void setupAll() {
// Không cần static
}
}
10. TEST ORDER
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class UserServiceTest {
@Test
@Order(1)
void shouldCreateUser() { ... }
@Test
@Order(2)
void shouldFindUser() { ... }
}
11. VÍ DỤ HOÀN CHỈNH
@DisplayName("Calculator Service Tests")
class CalculatorServiceTest {
private CalculatorService calculator;
@BeforeEach
void setUp() {
calculator = new CalculatorService();
}
@Test
@DisplayName("Should add two positive numbers")
void shouldAdd() {
assertThat(calculator.add(2, 3)).isEqualTo(5);
}
@Test
@DisplayName("Should throw when dividing by zero")
void shouldThrowOnDivideByZero() {
assertThatThrownBy(() -> calculator.divide(10, 0))
.isInstanceOf(ArithmeticException.class)
.hasMessage("Cannot divide by zero");
}
@ParameterizedTest
@CsvSource({
"1, 1, 1",
"2, 3, 5",
"10, 20, 30"
})
void shouldAddMultiple(int a, int b, int expected) {
assertThat(calculator.add(a, b)).isEqualTo(expected);
}
}
12. BÀI TẬP THỰC HÀNH
Bài 1: Test Service
Viết unit test cho UserService với các method CRUD.
Bài 2: Test Exception
Test các trường hợp exception.
Bài 3: Parameterized Test
Test với nhiều input khác nhau.
13. TÓM TẮT
| Annotation | Mô tả |
|---|---|
@Test |
Đánh dấu method test |
@BeforeEach |
Chạy trước mỗi test |
@AfterEach |
Chạy sau mỗi test |
@BeforeAll |
Chạy 1 lần trước tất cả |
@DisplayName |
Tên hiển thị |
@Disabled |
Bỏ qua test |
@Nested |
Test lồng nhau |
@ParameterizedTest |
Test với nhiều input |
Bài tiếp theo: 7.2 Mockito
🧭 Điều hướng bài học
- ⬅️ Bài trước: BÀI 6.10: LOGGING & MONITORING
- ➡️ Bài tiếp theo: BÀI 7.2: MOCKITO
- 📑 Toàn bộ lộ trình: Java Backend Zero to Hello
⭐ Hãy bookmark (clip) lại series để tiện theo dõi các bài học tiếp theo nhé!
All rights reserved