Dựng vòng lặp agent sinh unit test đầu tiên
Đã có môi trường và context, giờ ta ráp vòng lặp GEER (Generate → Execute → Evaluate → Refine) thành một agent chạy được từ đầu đến cuối cho unit test.
Kiến trúc vòng lặp
def test_agent(func, max_iters=4, target_cov=0.85):
context = build_context(func) # Bài 3
tests = llm_generate_tests(context) # Generate
for i in range(max_iters):
result = run_in_sandbox(tests) # Execute (Bài 2)
if result.failed: # Refine: sửa test fail
tests = llm_fix(tests, result.failures, context)
continue
cov = result.coverage
if cov >= target_cov: # Evaluate: đạt thì dừng
return tests, cov
# chưa đủ coverage -> sinh thêm test cho nhánh thiếu
tests += llm_generate_for_uncovered(
result.uncovered, context)
return tests, cov # dừng an toàn sau N vòng
Hai loại refine khác nhau: sửa test fail (test viết sai) và bổ sung test (thiếu nhánh). Đừng gộp làm một — nguyên nhân và cách xử lý khác nhau.
Prompt sinh test hiệu quả
Prompt cần ràng buộc rõ để output chạy được ngay:
Bạn là kỹ sư test. Viết pytest cho hàm dưới đây.
YÊU CẦU:
- Mỗi test một hành vi, tên test mô tả hành vi.
- Mock mọi dependency ngoài (DB, network) — KHÔNG gọi thật.
- Bao gồm: happy path, biên (boundary), và nhánh lỗi (raises).
- Chỉ trả về code trong khối python, không giải thích.CONTEXT: {context}
## Các bước dựng agent
1. Viết hàm `llm_generate_tests(context)` trả về code test thô.
2. Trích code từ khối `python `` (loại bỏ chữ thừa LLM hay thêm).
- Ghi test vào file tạm trong sandbox, chạy wrapper (Bài 2).
- Nếu có failure: gom traceback + assertion, gọi
llm_fix với đúng thông tin lỗi.
Nếu pass nhưng thiếu coverage: đưa danh sách dòng/nhánh chưa phủ, yêu cầu sinh test bổ sung (không viết lại từ đầu).
Đặt max_iters và ngưỡng coverage; luôn có nhánh dừng an toàn.
Kết xuất test file + báo cáo coverage cho người review.
Ví dụ một vòng thực tế
Hàm
apply_discount (Bài 1). Vòng 1: agent sinh 2 test (VIP, thường), pass nhưng coverage 66% — dòng raise ValueError chưa phủ. Vòng 2: agent nhận uncovered: [line 2], sinh test_negative_price_raises`. Chạy lại: 100% branch coverage, dừng. Chỉ 2 vòng, không cần người can thiệp.Tách bạch hai đường refine trong code
Một lỗi thiết kế hay gặp là gộp "sửa test fail" và "bổ sung test thiếu nhánh" vào cùng một lời gọi LLM. Hai việc này có bản chất khác nhau và cần prompt khác nhau. Sửa test fail: đưa traceback, hỏi "test này viết sai ở đâu, sửa lại cho đúng". Bổ sung test: đưa danh sách nhánh chưa phủ, hỏi "viết THÊM test cho các nhánh này, không đụng test cũ". Nếu gộp, agent dễ viết lại cả file, xóa mất test tốt, khiến coverage dao động lên xuống qua các vòng — một dấu hiệu kinh điển của vòng lặp thiết kế kém. Giữ hai nhánh tách bạch giúp mỗi vòng đơn nhiệm và dễ debug.
Ghi log mỗi vòng để quan sát hành vi agent
Khi phát triển agent, hãy log lại từng vòng: context đưa vào, test sinh ra, kết quả chạy, quyết định vòng sau. Log này vô giá khi agent hành xử lạ — bạn thấy ngay nó lặp vì thiếu tín hiệu mới, hay vì gặp bug thật không sửa được. Không có log, debug một agent nhiều vòng chẳng khác gì mò kim đáy bể. Ngoài ra, log giúp bạn cải thiện prompt: đọc những vòng mà agent đi sai hướng để biết prompt còn mơ hồ ở đâu.
Khung tư duy: mỗi vòng phải giảm bất định
Mỗi lần lặp phải mang lại thông tin mới để vòng sau tốt hơn: một failure cụ thể, một nhánh thiếu cụ thể. Nếu vòng lặp gọi LLM với cùng một context không đổi, nó sẽ sinh ra cùng lỗi — đó là lặp vô nghĩa, đốt token. Nguyên tắc: no new signal, no new loop.
Sai lầm thường gặp
- Gọi lại LLM với prompt y hệt sau khi fail: không thêm traceback, agent lặp lại đúng lỗi cũ.
- Viết lại toàn bộ test mỗi vòng: mất các test tốt đã có, coverage dao động lên xuống.
- Không giới hạn max_iters: gặp lỗi không sửa được (ví dụ bug thật trong code) thì lặp vô hạn.
- Không trích sạch code khỏi markdown: dính chữ "Here is the test:" làm file không chạy.
- Coi test fail luôn là do test sai: đôi khi test đúng và phát hiện bug thật — cần cờ báo con người thay vì cố "sửa" test cho pass.