Backend

99 Ngày Java — Ngày 88: Unit Test với JUnit 5

SSite Admin
23 tháng 10, 2026 3 phút đọc 5 lượt xem
99 Ngày Java — Ngày 88: Unit Test với JUnit 5

Unit test là đoạn code kiểm tra một hành vi nhỏ, chạy trong vài mili giây và không cần mạng hay database. JUnit 5, còn gọi là JUnit Jupiter, là framework chuẩn của hệ sinh thái Java. Bài này đi qua phần dùng hằng ngày: vòng đời, assertion và test tham số hóa.

Unit Test với JUnit 5

1. @Test và cấu trúc ba bước

Một phương thức có @Test là một ca kiểm thử; lớp và phương thức không cần public. Viết theo ba bước: chuẩn bị dữ liệu, thực hiện đúng một hành động, kiểm tra kết quả. Tên test mô tả hành vi mong đợi, không mô tả cài đặt; @DisplayName cho phép viết tên dạng câu. Mỗi test chỉ nên có một lý do để thất bại.

class PriceCalculatorTest {
    private final PriceCalculator calculator = new PriceCalculator();

    @Test
    @DisplayName("applies a 10% discount to orders above one million")
    void appliesDiscountAboveThreshold() {
        var order = new Order(1_500_000);          // arrange
        long total = calculator.total(order);      // act
        assertEquals(1_350_000, total);            // assert
    }
}

2. Vòng đời: mỗi test một instance

Mặc định JUnit tạo một instance mới của lớp test cho mỗi phương thức, nên trường của lớp không rò rỉ trạng thái giữa các test. @BeforeEach và @AfterEach chạy quanh từng test; @BeforeAll và @AfterAll chạy một lần cho cả lớp và phải là static. Thứ tự chạy các test không được bảo đảm, vì vậy test không được phụ thuộc lẫn nhau. @Nested gom các test cùng ngữ cảnh thành nhóm dễ đọc.

class CartTest {
    private Cart cart;

    @BeforeEach
    void setUp() { cart = new Cart(); }   // fresh state for every test

    @Nested
    class WhenEmpty {
        @Test
        void totalIsZero() { assertEquals(0, cart.total()); }
    }
}

3. Assertion đáng nhớ

assertEquals nhận giá trị mong đợi trước, giá trị thực sau; đảo thứ tự làm thông báo lỗi gây hiểu nhầm. assertThrows kiểm tra ngoại lệ và trả về nó để kiểm tra tiếp thông điệp. assertAll chạy mọi kiểm tra bên trong rồi báo tất cả lỗi một lượt, hợp khi kiểm tra nhiều thuộc tính của một kết quả. Thông điệp lỗi nên truyền dạng lambda để chỉ được tạo khi test thất bại.

var error = assertThrows(IllegalArgumentException.class,
        () -> new Order(-1));
assertEquals("amount must be positive", error.getMessage());

assertAll("receipt",
        () -> assertEquals("PAID", receipt.status()),
        () -> assertEquals(1_350_000, receipt.total()),
        () -> assertNotNull(receipt.paidAt()));

4. Parameterized test

Khi cùng một logic cần thử với nhiều bộ dữ liệu, @ParameterizedTest chạy một phương thức nhiều lần với tham số khác nhau, và mỗi bộ dữ liệu được báo cáo như một test riêng. @ValueSource cho một tham số đơn, @CsvSource cho vài cột, @MethodSource cho dữ liệu phức tạp. Cần thêm artifact junit-jupiter-params, đã nằm sẵn trong junit-jupiter.

  • Thử giá trị biên: 0, âm, đúng ngưỡng, ngay trên ngưỡng.

  • Đừng đưa logic tính kết quả mong đợi vào test; ghi thẳng giá trị.

  • Test chậm hoặc cần hạ tầng thì tách sang integration test.

@ParameterizedTest(name = "{0} -> {1}")
@CsvSource({
        "999999, 999999",
        "1000000, 1000000",
        "1000100, 900090",
})
void discountStartsAboveOneMillion(long amount, long expected) {
    assertEquals(expected, calculator.total(new Order(amount)));
}

Test tốt đọc như đặc tả: tên nói hành vi, thân test có ba bước rõ ràng. Ngày 89 xử lý phần còn thiếu: cô lập lớp cần test khỏi phụ thuộc của nó bằng Mockito.

Tài liệu đối chiếu

  • https://docs.junit.org/current/user-guide/

  • https://docs.junit.org/current/api/org.junit.jupiter.api/org/junit/jupiter/api/Assertions.html

S

Site Admin

Engineer and writer. Building things with TypeScript and distributed systems.

Bình luận (0)

Bạn cần đăng nhập bằng Google để bình luận.

Hãy là người bình luận đầu tiên.

Bài viết liên quan

99 Ngày Spring — Ngày 89: Docker hóa Spring Boot

Dockerfile nhiều stage, layered jar để tận dụng cache, chạy không dùng root và buildpacks khi không muốn viết Dockerfile.

23 thg 10, 20263 phút7
99 Ngày Java — Ngày 89: Mockito

mock, stub và verify, @Mock với @InjectMocks, ArgumentCaptor và những gì không nên mock.

23 thg 10, 20263 phút6
99 Ngày Spring — Ngày 88: GraalVM Native Image

AOT processing, cách build native executable, giả định thế giới đóng và những hạn chế với reflection.

23 thg 10, 20263 phút5