0

Nghệ Thuật Viết Test: Đừng Để Hai Bánh Răng Xịn Ghép Vào Nhau Lại Bị Kẹt

Có một câu nói đùa nhưng rất thật trong ngành IT: "Mọi lập trình viên đều cực kỳ tự tin vào code của mình, cho đến khi họ bấm nút Deploy lên Production vào chiều thứ Sáu."

Tại sao chúng ta lại sợ hãi việc đưa code lên mạng? Vì một hệ thống khi đã phình to ra, việc bạn sửa một dòng code ở tính năng A hoàn toàn có thể làm "chết đứng" tính năng B ở một ngóc ngách nào đó mà bạn không hề hay biết.

Tư duy "Thợ gõ" là: Viết code xong, mở Postman lên bấm gọi API vài lần, thấy trả về HTTP 200 OK là kêu "Code em chạy ngon rồi!". Nhưng một Vibe Coder không bao giờ giao phó sinh mạng của hệ thống cho việc test bằng tay (Manual Testing). Chúng ta viết code để tự động kiểm tra code.

Đó là lúc hai khái niệm sống còn ra đời: Unit Test (Test Đơn Vị)Integration Test (Test Tích Hợp). Hôm nay, chúng ta sẽ mổ xẻ ranh giới của chúng!

Để dễ hình dung nhất, hãy tưởng tượng dự án Backend của bạn là một chiếc xe hơi.

1. Unit Test (Test Đơn Vị) - Soi Kỹ Từng Con Ốc Vít

Bản chất: Unit Test là việc bạn tháo rời từng bộ phận nhỏ nhất của chiếc xe (ví dụ: cái bánh xe, cái bugi, cái vô lăng) ra khỏi xe, mang vào phòng thí nghiệm, và kiểm tra xem CÁI BỘ PHẬN ĐÓ có hoạt động đúng logic hay không

Trong Code: Một "Unit" thường là một Hàm (Function) hoặc một Lớp (Class) độc lập. Nguyên tắc tối thượng của Unit Test là Sự Cách Ly (Isolation). Khi test hàm calculateTax(), bạn KHÔNG ĐƯỢC PHÉP cho nó gọi xuống Database thật, không được gọi API của bên thứ 3. Mọi sự phụ thuộc ra bên ngoài đều phải được làm giả (gọi là Mocking hoặc Stubbing).

Ví dụ thực chiến: Bạn test hàm tính tổng tiền giỏ hàng getTotalPrice().

  • Kịch bản 1: Giỏ hàng có 2 món (10k và 20k) -> Kết quả mong đợi: 30k.
  • Kịch bản 2: Giỏ hàng có áp mã giảm giá 10% -> Kết quả mong đợi: 27k.
  • Bạn cung cấp dữ liệu giả (Mock Data) trực tiếp vào hàm. Hàm chạy trên RAM chỉ mất 1ms (mili-giây). Nhanh như điện!

Ưu điểm của Unit Test:

  • Tốc độ bàn thờ: Chạy 10.000 cái Unit Test mất chưa tới 5 giây.
  • Chỉ điểm chính xác: Nếu test tạch, bạn biết CHẮC CHẮN dòng code nào trong hàm đó đang viết sai logic.

2. Integration Test (Test Tích Hợp) - Lắp Ráp Cỗ Máy

Bản chất: Bánh xe của bạn rất xịn (pass Unit Test). Cái trục xe của bạn cũng làm bằng thép xịn nhất (pass Unit Test). Nhưng khi bạn lắp cái bánh xe vào cái trục, lỡ cái lỗ ốc nó không khớp nhau thì sao? Chiếc xe sẽ gãy nát ngay khi nổ máy!

Integration Test sinh ra để kiểm tra LỚP KEO DÍNH giữa các bộ phận đó.

Trong Code: Integration Test kiểm tra luồng đi của dữ liệu từ khi nhận Request cho đến khi chui xuống Database.

Ở đây, chúng ta KHÔNG DÙNG MOCK CHO DATABASE NỮA. Chúng ta sẽ dựng lên một cái Test Database (Database dùng riêng cho việc test, tách biệt với Production).

Ví dụ thực chiến: Test API Đăng ký tài khoản (POST /api/register).

  1. Gửi một request HTTP thật chứa emailpassword.
  2. Controller gọi Service.
  3. Service gọi lệnh Insert xuống Test Database thực tế.
  4. Kiểm tra xem trong DB đã có dòng dữ liệu đó chưa.
  5. Kiểm tra API có trả về mã 201 Created không.

Ưu điểm của Integration Test:

  • Bắt được những lỗi "chết người" mà Unit Test mù tịt: Lỗi sai tên cột trong Database, lỗi mất kết nối Redis, lỗi cấu hình sai Router.
  • Mang lại sự tự tin cực cao (Vì nó mô phỏng gần giống hệt những gì User thật sẽ làm).

3. Cú Lừa Kinh Điển: Hội Chứng "Viết Test Cho Có 100% Coverage"

Nhiều công ty ép nhân viên phải viết Unit Test sao cho độ phủ (Coverage) đạt 100%. Tức là mọi dòng code đều phải được test. Kết quả là anh em dev đối phó bằng cách: Viết Unit Test cho Controller, và Mock (làm giả) toàn bộ Service và Database.

// TƯ DUY ĐỐI PHÓ
it("nên trả về danh sách user", () => {
    // Tự ép hệ thống trả về giả mạo
    db.query = jest.fn().mockReturnValue([{ id: 1, name: "Hiếu" }]); 
    
    const res = await callApi('/users');
    expect(res.data[0].name).toBe("Hiếu"); // PASS XANH LÈ!
});

Code Test báo Xanh (Pass). Coverage báo 100%.

Nhưng hôm sau có người lỡ tay Xóa mất bảng users trong Database thật. Bạn chạy lại Unit Test, nó... VẪN XANH! Vì cái Unit Test của bạn đã bị bịt mắt bởi hàm Mock rồi, nó đâu có chọc xuống DB thật để biết là bảng đó đã bốc hơi! Khi deploy lên Production, hệ thống sập toàn tập.

Lời kết của Vibe Coder (Kim Tự Tháp Testing)

Để kê cao gối ngủ vào cuối tuần, bạn không được phép chọn 1 trong 2, mà phải kết hợp chúng theo tỷ lệ vàng (Testing Pyramid):

  • 70% Unit Test: Viết cho tất cả các hàm tính toán logic phức tạp (tính tiền, tính thuế, validate chuỗi). Viết cực nhanh, chạy cực nhanh.
  • 30% Integration Test: Viết cho các luồng API quan trọng nhất (Đăng nhập, Thanh toán, Đặt hàng). Đảm bảo Controller, Service và Database nói chuyện được với nhau mượt mà.

Đừng bao giờ tin vào những dòng code bạn viết, cho đến khi nó được thử lửa tự động bởi Test!

Chủ đề tiếp theo: Khi Robot Code Thay Bạn - Thiết Lập CI/CD Pipeline Lên GitHub Actions

Bạn đã viết được 100 cái file Unit Test và Integration Test cực xịn. Cứ mỗi lần sửa code, bạn lại tự gõ lệnh npm test ở máy bạn, thấy Xanh (Pass) rồi mới gõ git push lên nhánh main.

Nhưng nếu team bạn có 5 người thì sao? Lỡ có một ông dev lười, sửa code làm hỏng hệ thống nhưng cố tình KHÔNG chạy Test ở máy ổng, mà cứ lén git push đè lên source code chung thì sao? Toàn bộ hệ thống sẽ nát bét!

Ở bài viết tới, chúng ta sẽ bước vào thế giới DevOps thực thụ: CI/CD (Tích hợp liên tục / Triển khai liên tục). Chúng ta sẽ dùng GitHub Actions để bắt một con Robot làm bảo vệ. Bất cứ ai push code lên, con Robot sẽ tự động dựng server, tự động chạy toàn bộ Test. Nếu Test đỏ (Fail), nó sẽ đạp cái code đó ra, không cho phép gộp vào nhánh chính! Anh em nhớ đón đọc nghệ thuật tự động hóa này nhé!


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.