Part 02 · Testing & Code Quality · 2.3

Lab kiểm thử và chất lượng

Các lab xây dần một Transfer Service. Mỗi bài chọn boundary nhỏ nhất đủ chứng minh rủi ro; luôn ghi expected result và evidence thay vì chỉ ghi test đã chạy.


Chuẩn bị: Java 17+, Maven hoặc Gradle, Docker cho Testcontainers, và một repository có thể lưu report. Khi chạy lab với database hoặc broker thật, cô lập resource theo test suite và dọn artifact sau khi verify.

Lab 01 · Domain test bằng JUnit

Yêu cầu: viết Account/TransferPolicy và kiểm tra amount dương, insufficient balance, currency mismatch, overflow và state không đổi khi fail.

@Test
void shouldKeepBalanceWhenDebitFails() {
    var account = new Account(new BigDecimal("10.00"));
    assertThrows(InsufficientBalanceException.class,
            () -> account.debit(new BigDecimal("20.00")));
    assertEquals(new BigDecimal("10.00"), account.balance());
}

Mở rộng: thêm parameterized edge cases, inject Clock cho daily limit và chạy mutation test cho package domain.

Lab 02 · Controller boundary

Dùng @WebMvcTest và MockMvc kiểm tra JSON mapping, validation, status code, error body, content type và authorization rule. Mock application service nhưng chỉ verify command quan trọng, không verify mọi call nội bộ.

Khung test
@WebMvcTest(TransferController.class)
class TransferControllerTest {
    @Autowired MockMvc mvc;
    @MockBean TransferService service;

    @Test void shouldRejectInvalidAmount() throws Exception {
        mvc.perform(post("/transfers")
            .contentType(MediaType.APPLICATION_JSON)
            .content("""{"from":"A","to":"B","amount":0}"""))
            .andExpect(status().isBadRequest())
            .andExpect(jsonPath("$.code").value("VALIDATION_ERROR"));
        verifyNoInteractions(service);
    }
}

Lab 03 · PostgreSQL Testcontainers

Test mapping, unique idempotency key, query, optimistic locking và commit visibility với PostgreSQL container. Thêm component test cho flow request → service → committed database state. Không dùng H2 để thay thế các semantics của production database.

Container khởi đầu
@Container
static PostgreSQLContainer<?> postgres =
    new PostgreSQLContainer<>("postgres:17-alpine");

Lab 04 · Concurrency deterministic

Tạo phiên bản transfer cố ý race rồi sửa. Dùng CountDownLatch/Barrier để 20 workers bắt đầu cùng lúc; mọi Future có timeout.

Lab 05 · Duplicate, retry và messaging

Tách handler business khỏi broker adapter. Unit test handler; integration test broker container.

Lab 06 · CI quality gate và flaky drill

Tạo pipeline chạy unit → integration → report. Thêm JaCoCo, static/dependency scan và PIT cho domain package; lưu XML/HTML report khi fail.

  1. Tạo flaky test phụ thuộc timezone, order hoặc random.
  2. Chạy lặp để thu seed, order, duration và environment evidence.
  3. Sửa bằng Clock, seed hoặc isolation, không chỉ tăng timeout.
  4. Chứng minh test ổn định khi chạy parallel và đổi timezone.
  5. Ghi threshold có lý do, không đặt 100% máy móc.

Lab 07 · Performance regression và saturation

Xây workload model cho Transfer API với warm-up, data volume và target throughput. Đo p50/p95/p99, error, CPU/GC và DB pool wait; chạy load, spike và soak ngắn.

Lab 08 · Security và supply-chain gate

Threat-model trust boundaries; thêm authorization/data-isolation tests, SAST, SCA, secret và container scan. Mô phỏng untrusted PR/cached artifact risk mà không dùng secrets thật.

Rubric

MứcTiêu chí
0Test không chạy ổn định hoặc chỉ happy path.
1Behavior chính đúng, assertion rõ.
2Boundary phù hợp, có failure/edge và resource isolation.
3 · SeniorChứng minh transaction/concurrency/performance/security semantics, CI evidence và giải thích trade-off.
Nguồn tham khảo