Tại sao cần agent sinh test và agent khác script sinh test ra sao
Viết test là việc ai cũng biết là quan trọng nhưng ai cũng ngại làm. Một hàm calculate_discount() có thể mất 5 phút để viết nhưng 30 phút để test cho đủ các nhánh. Đó là lý do coverage của phần lớn dự án nằm dưới 40%. Một agent sinh test không phải là công cụ magic thay bạn tư duy, mà là một vòng lặp tự động: đọc code → sinh test → chạy test → đọc kết quả → sửa test → lặp lại cho đến khi đạt mục tiêu.
Agent khác gì so với script hoặc prompt một lần
Rất nhiều người nhầm "gọi LLM sinh test" là đã có agent. Không phải. Sự khác biệt nằm ở vòng lặp có phản hồi (feedback loop):
- Prompt một lần: bạn dán code, LLM trả về test, bạn tự chạy. Nếu test sai import, sai assertion, bạn tự sửa.
- Agent: hệ thống tự chạy test vừa sinh, đọc lỗi (traceback, assertion fail, coverage report), và tự vòng lại để sửa cho đến khi test thực sự pass và đo được.
Kiến trúc tối thiểu của một agent sinh test
[Đọc code + context]
↓
[LLM sinh test]
↓
[Chạy test trong sandbox] ──fail──┐
↓ pass │
[Đo coverage] │
↓ chưa đủ │
[Xác định nhánh còn thiếu] ─────────┘ (vòng lại sinh thêm)
↓ đủ
[Kết xuất test file + báo cáo]
Bốn thành phần bắt buộc: bộ đọc context, LLM sinh code, runner chạy test trong môi trường cô lập, và bộ đánh giá (pass/fail + coverage).
Ví dụ cụ thể
Giả sử bạn có hàm:
def apply_discount(price, code):
if price <= 0:
raise ValueError("price must be positive")
if code == "VIP":
return price * 0.8
return price
Một prompt một lần có thể quên test nhánh price <= 0. Agent sẽ chạy coverage, thấy dòng raise ValueError chưa được chạm, và tự sinh thêm test cho case đó. Đây chính là điểm khiến agent vượt trội: nó không dừng ở test trông có vẻ đúng, mà dừng khi số liệu đạt.
Các bước để bắt đầu tư duy như một agent builder
- Xác định tín hiệu thành công đo được (measurable signal): coverage %, số test pass, mutation score — không phải "trông ổn".
- Đảm bảo có môi trường chạy test tự động (pytest, jest…) mà agent có thể gọi và đọc output.
- Thiết kế vòng lặp dừng đúng lúc: đạt mục tiêu thì dừng, quá N vòng thì báo cáo và dừng an toàn.
- Luôn giữ con người ở vòng review cuối: agent sinh, người duyệt trước khi merge.
Khung tư duy: "Generate → Execute → Evaluate → Refine" (GEER)
Mỗi khi thiết kế agent test, hỏi 4 câu:
- Generate: agent lấy context gì để sinh?
- Execute: chạy ở đâu, cô lập thế nào?
- Evaluate: đo bằng số liệu nào?
- Refine: khi chưa đạt, vòng lại thông tin gì?
Khi nào nên dùng agent, khi nào không
Agent sinh test tỏa sáng ở code có logic nhánh rõ ràng, hàm thuần, module utility, service tầng nghiệp vụ — nơi đầu vào/đầu ra dễ xác định. Ngược lại, với code phụ thuộc nặng vào UI, timing thực, hoặc hành vi khó xác định (non-deterministic), agent dễ sinh test giòn (brittle) hoặc flaky. Hiểu ranh giới này giúp bạn triển khai đúng chỗ, thu ROI thật thay vì tạo gánh nặng bảo trì mới. Một chiến lược thực dụng: bắt đầu ở lớp logic thuần nhất, chứng minh giá trị, rồi mới mở rộng dần ra vùng khó hơn khi guardrails đã vững.
Sai lầm thường gặp
- Coi output LLM là test luôn dùng được: không chạy thử, dẫn tới test sai import hoặc assertion vô nghĩa.
- Không đo coverage: agent "tưởng" đã xong nhưng bỏ sót nhánh, đây là cái bẫy lớn nhất.
- Vòng lặp không có điều kiện dừng: agent lặp vô hạn hoặc đốt token khi gặp lỗi không thể sửa.
- Bỏ review con người: merge thẳng test agent sinh, để lọt test khẳng định hành vi sai (test that locks in a bug).
- Áp agent vào chỗ sai: ép sinh test cho code UI/timing khó xác định rồi than phiền agent kém, trong khi vấn đề là chọn sai điểm áp dụng.