Product Management
Đăng nhập
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Bài 6 — Tester: đóng vòng chất lượng bằng bằng chứng thực thi

Đã đọc 0/12 mục · còn khoảng 5 phút
Cách chúng tôi ghi nhận

Một mục được tính là đã đọc khi nó nằm giữa màn hình đủ lâu so với độ dài của nó (bảng và sơ đồ tính một nửa), bạn vẫn đang cuộn hoặc bấm phím, và bạn đã thấy tới cuối mục. Lướt nhanh hoặc để tab mở không được tính. Chúng tôi chỉ lưu thời gian đọc từng mục — không lưu gì khác.

Không chạy test thì cả đội chỉ đang "tưởng tượng" code đúng

Reviewer đọc code và suy luận; nhưng suy luận có thể sai. Chỉ có chạy test thật mới cho bằng chứng khách quan. Tester là vai trò biến "tôi nghĩ nó đúng" thành "nó xanh, đây là bằng chứng". Đây là cổng chất lượng cứng nhất trong đội.

Tester làm hai việc

  • Sinh test từ acceptance của task (kể cả ca biên).
  • Chạy test trong môi trường cách ly và báo cáo kết quả có cấu trúc.
Điểm mấu chốt: test phải chạy trong sandbox (container/venv tách biệt), không đụng vào hệ thống thật. Agent tuyệt đối không được chạy lệnh tùy tiện trên máy production.

Ví dụ báo cáo của Tester

{
  "passed": false,
  "total": 5, "failed": 1,
  "failures": [
    {"test":"test_add_duplicate_sku",
     "expected":"qty=3","actual":"qty=1",
     "trace":"add_item ghi đè thay vì cộng dồn"}
  ]
}

Trường passed cho orchestrator biết ngay nên commit hay quay lại Coder; failures cho Coder biết chính xác sửa gì.

Sinh test từ acceptance

Nhớ lại Bài 4: acceptance được viết để "thành một test". Giờ Tester hiện thực hóa điều đó:

# acceptance: "Thêm trùng sku thì cộng dồn qty"
def test_add_duplicate_sku():
    cart = Cart()
    cart.add_item("A", 1)
    cart.add_item("A", 2)
    assert cart.items["A"].qty == 3

Các bước triển khai Tester

  • Chuẩn bị sandbox chạy test (Docker hoặc venv riêng, timeout rõ ràng).
  • Cho Tester acceptance + code, yêu cầu sinh test bao gồm ca biên và ca lỗi.
  • Chạy test, thu output thô (stdout/stderr, exit code).
  • Ép Tester tóm tắt output thành JSON {passed, failures[]} — orchestrator chỉ đọc JSON này.
  • Nếu đỏ, chuyển failures về Coder; nếu xanh, cho phép commit.
  • Lưu test đã sinh vào repo để chạy lại (regression) ở các task sau.

Chống "test giả xanh"

Rủi ro lớn: Coder có thể "gian lận" bằng cách viết code làm test luôn pass, hoặc Tester viết test rỗng. Cách phòng:

  • Tách người viết test khỏi người viết code: Tester sinh test độc lập từ acceptance, không nhìn cách Coder cài đặt (chỉ nhìn giao diện).
  • Yêu cầu tối thiểu số ca kiểm thử (ví dụ 1 happy path + 1 ca biên + 1 ca lỗi).
  • Kiểm tra assertion: từ chối test không có assert.
  • Chạy test trên phiên bản code cũ (biết sai) để chắc test bắt được lỗi (mutation-check nhẹ).

Khung tư duy: "Bằng chứng, không phải niềm tin"

Mỗi khi định đánh dấu task hoàn thành, hỏi: tôi có log test xanh làm bằng chứng không? Nếu chỉ có "Reviewer nói ổn", task chưa xong.

Sai lầm thường gặp

  • Không chạy test thật, chỉ để LLM "đọc và đoán" là đúng.
  • Chạy test trên môi trường thật thay vì sandbox, gây rủi ro phá dữ liệu.
  • Chấp nhận test rỗng/không assert, tạo cảm giác an toàn giả.
  • Không lưu test lại, mất khả năng bắt lỗi hồi quy ở task sau.
  • Để Tester đọc cách cài đặt của Coder, test bị "nhiễm" và bỏ sót lỗi thiết kế.
<!--les-append-->

Test xanh giả: bốn dạng, và cách bắt từng dạng

"Chống test giả xanh" là mục quan trọng nhất của bài, và nó có bốn dạng cụ thể:

DạngBiểu hiệnCách bắt
Không có assertionTest chạy code, không kiểm gìTừ chối test không có assert
Assertion luôn đúngassert result is not NoneYêu cầu assert vào giá trị cụ thể
Test bắt chước cài đặtMock đúng cách Coder viết, nên luôn passTester sinh test từ acceptance, không đọc code
Test không chạy đường cần kiểmPass vì nhánh lỗi không bao giờ vàoChạy test trên code cũ (biết sai) — phải FAIL
Dạng thứ hai là dạng phổ biến nhất khi để LLM sinh test, vì is not None luôn an toàn và luôn pass. Câu lệnh chặn: "Mọi assert phải so với một giá trị cụ thể mong đợi, không dùng kiểm tra tồn tại."

Dạng thứ tư cần phép kiểm mạnh nhất và cũng rẻ: chạy test mới trên phiên bản code trước khi sửa. Nếu nó pass, test đang không kiểm thứ cần kiểm. Đây là một dòng trong quy trình Tester và nó bắt được phần lớn test vô dụng.

Tại sao Tester phải mù với cài đặt

Điểm này dễ bị xem là tiểu tiết, nhưng nó là kiến trúc. Nếu Tester đọc code của Coder, nó sẽ viết test mô tả code đó thay vì mô tả yêu cầu. Hệ quả: test pass với một cài đặt sai, vì nó đã học sai từ chính cài đặt đó.

Cụ thể trong sơ đồ đa agent: Tester chỉ nhận acceptance và giao diện công khai. Nó không nhận diff. Việc này làm Tester yếu hơn ở việc viết test chạy được ngay, và mạnh hơn nhiều ở việc bắt lỗi thật — đó là đánh đổi đúng.

Ba loại ca tối thiểu: cụ thể là gì

"1 happy path + 1 ca biên + 1 ca lỗi" cần định nghĩa rõ, vì agent sẽ hiểu lỏng:

  • Happy path: đầu vào hợp lệ điển hình, kết quả đúng như acceptance nói.
  • Ca biên: giá trị ở ranh giới — rỗng, 0, một phần tử, giá trị lớn nhất, trùng lặp. Với ví dụ "thêm trùng sku thì cộng dồn qty", ca biên là thêm trùng ba lần.
  • Ca lỗi: đầu vào không hợp lệ, và test kiểm rằng hệ thống báo lỗi đúng cách, không phải kiểm rằng nó không sập.
Phân biệt cuối quan trọng: assert không raise là một test kém. assert raise ValidationError với thông điệp X là một test thật.

Chỉ số cho Tester

Một con số nên theo qua các task: tỉ lệ lỗi do Reviewer hoặc người phát hiện sau khi Tester đã báo xanh. Nếu con số đó cao, vấn đề không ở Coder — nó ở chất lượng test, và bạn đang có một cảm giác an toàn giả trong toàn bộ dây chuyền. <!--/les-append-->

Học xong bài này rồi? Tạo tài khoản miễn phí để lưu lại — lần sau vào là biết ngay đang dở ở đâu, và học hết khóa thì có chứng chỉ. Lưu tiến độ của tôi