1. Sinh test bằng LLM: cơ hội và cạm bẫy cốt lõi
Các mô hình ngôn ngữ lớn như Claude hay GPT có thể viết ra một bộ test end-to-end chỉ từ vài dòng mô tả — nhanh đến kinh ngạc. Nhưng đúng chỗ đó là cạm bẫy: mô hình sinh văn bản trông giống test thật, kể cả khi selector không tồn tại và assertion vô nghĩa. Bài này nói về cách dùng LLM để sinh test một cách có kỷ luật: cho mô hình đủ ngữ cảnh thật (grounding), bắt nó bám vào sự thật của app, xác minh mọi locator trên app sống, cắt tỉa đầu ra thừa, và đo chất lượng bộ test — thay vì tin mù vào những gì mô hình 'tự tin' viết ra.
Vấn đề nghiêm trọng nhất là hallucination: mô hình bịa ra selector #submit-btn-v2 chưa từng có, hoặc khẳng định 'thông báo Thành công xuất hiện' trong khi app hiển thị 'Hoàn tất'. Test bịa đặt còn tệ hơn không có test, vì nó tạo cảm giác an toàn giả và tốn công bảo trì. Cách chữa gốc là grounding — neo mô hình vào sự thật của hệ thống (cây accessibility, OpenAPI, DOM thật) và luôn xác minh lại đầu ra trên app thật trước khi giữ. Nguyên tắc xuyên suốt vẫn là oracle-first: mô hình có thể giúp viết bước thao tác, nhưng khẳng định phải về nghiệp vụ và do con người kiểm duyệt.
2. Grounding: neo mô hình vào sự thật của ứng dụng
Grounding là hành động đưa sự thật cụ thể của hệ thống vào ngữ cảnh mô hình, để nó không phải đoán. Có ba nguồn sự thật chính cho sinh test: cây accessibility (ARIA snapshot) cho biết phần tử UI thật sự tồn tại với vai trò và tên gì; đặc tả OpenAPI cho biết endpoint, tham số, mã lỗi nào là hợp lệ; và DOM/HTML thật cho những chi tiết cây accessibility không phủ. Khi mô hình có ba nguồn này trong ngữ cảnh, nó chọn locator và endpoint từ tập có thật thay vì tưởng tượng — đây là lá chắn số một chống hallucination.
Cách nạp ngữ cảnh cũng quan trọng như bản thân ngữ cảnh. Đừng dán cả trang HTML thô 200KB — nó nhấn chìm tín hiệu trong nhiễu và tốn token. Hãy chắt lọc: cung cấp ARIA snapshot đã cắt gọn quanh khu vực liên quan, phần OpenAPI của đúng nhóm endpoint đang test, và một danh sách 'sự thật ràng buộc' rõ ràng (ví dụ: 'tên nút xác nhận là Hoàn tất, không phải Thành công'). Ngữ cảnh cô đọng và đúng trọng tâm cho ra test chính xác hơn nhiều so với ngữ cảnh dài mà loãng.
// Chắt lọc ngữ cảnh grounding TRƯỚC khi đưa vào prompt sinh test.
// Không dán HTML thô; trích ARIA + slice OpenAPI + sự thật ràng buộc.
const ariaSnap = await page.accessibility.snapshot({ interestingOnly: true });
const relevant = pruneAria(ariaSnap, { around: "form[aria-label='Chuyển tiền']" }); // cắt gọn
const openapiSlice = pickPaths(openapi, ["/transfers", "/accounts/{id}"]); // đúng nhóm
const bindingFacts = [
"Nút xác nhận có role=button, name='Hoàn tất' (KHÔNG phải 'Thành công').",
"Số tiền là textbox name='Số tiền', chỉ nhận số nguyên > 0.",
"Sau chuyển, GET /accounts/{id} trả 'balance' cập nhật; không có endpoint xoá.",
];
const groundingContext = { aria: relevant, openapi: openapiSlice, facts: bindingFacts };
// -> đưa groundingContext vào prompt; mô hình chọn locator từ tập CÓ THẬT.❓ Grounding khác gì với việc chỉ nhắc mô hình 'đừng bịa selector'?
Nhắc suông là ràng buộc bằng lời, mô hình vẫn có thể vi phạm vì nó không có cách nào biết selector nào thật. Grounding là ràng buộc bằng dữ liệu: bạn đưa cây accessibility và OpenAPI thật vào ngữ cảnh, nên tập lựa chọn của mô hình bị thu về những thứ có thật. Lời nhắc giúp định hướng, nhưng chỉ grounding mới thực sự loại bỏ nguồn gốc của hallucination. Tốt nhất là kết hợp cả hai.
3. Mẫu prompt để sinh test bám hợp đồng và oracle-first
Một prompt sinh test tốt có cấu trúc rõ ràng: vai trò và mục tiêu; ngữ cảnh grounding (ARIA/OpenAPI/sự thật ràng buộc); ràng buộc cứng (chỉ dùng locator có thật, oracle phải về nghiệp vụ, không sleep cứng, dùng auto-waiting); định dạng đầu ra (Playwright TypeScript, một test một hành vi); và ví dụ mẫu ngắn để mô hình bắt chước phong cách. Cấu trúc này biến mô hình từ 'người kể chuyện' thành 'thợ viết theo khuôn có kiểm soát'. Điểm quan trọng nhất trong ràng buộc là oracle-first: nêu rõ khẳng định phải kiểm bất biến nghiệp vụ, không phải sự hiện diện của chữ 'thành công'.
Kỹ thuật few-shot rất hiệu quả: cho một hoặc hai ví dụ test 'chuẩn vàng' viết đúng phong cách bạn muốn, mô hình sẽ suy ra khuôn và giữ nhất quán. Ngược lại, đừng để prompt mơ hồ kiểu 'viết vài test cho trang này' — nó mời gọi mô hình tự do sáng tác và bịa. Hãy ràng buộc phạm vi: 'viết đúng test cho hành vi chuyển tiền hợp lệ và ba nhánh lỗi (số âm, vượt số dư, tài khoản không tồn tại), mỗi hành vi một test độc lập, dùng oracle kiểm số dư'. Ràng buộc càng cụ thể, đầu ra càng dễ review và bảo trì.
# PROMPT MẪU — sinh test Playwright oracle-first, bám grounding
VAI TRÒ: Kỹ sư QA viết Playwright (TypeScript).
MỤC TIÊU: Sinh test cho hành vi "chuyển tiền" + 3 nhánh lỗi.
NGỮ CẢNH (sự thật — CHỈ dùng những gì có ở đây):
- ARIA: {{aria_snapshot}}
- OpenAPI: {{openapi_slice}}
- Sự thật ràng buộc: nút xác nhận name="Hoàn tất"; số tiền là số nguyên > 0.
RÀNG BUỘC CỨNG:
1. Locator CHỈ theo role+name có trong ARIA. Không bịa selector, không dùng CSS/xpath mong manh.
2. ORACLE phải kiểm bất biến nghiệp vụ: số dư nguồn giảm đúng, đích tăng đúng, tổng bảo toàn.
TUYỆT ĐỐI không assert chỉ sự hiện diện chữ "thành công".
3. Dùng auto-waiting của Playwright; KHÔNG page.waitForTimeout cố định.
4. Mỗi hành vi = 1 test độc lập, idempotent theo seedKey.
ĐẦU RA: chỉ code TypeScript, kèm // TODO ở locator/endpoint cần người xác minh trên app thật.
VÍ DỤ CHUẨN (few-shot):
{{one_gold_example_test}}❓ Ràng buộc quan trọng nhất cần đưa vào prompt sinh test là gì?
Oracle-first: buộc mô hình viết khẳng định kiểm bất biến nghiệp vụ chứ không phải kiểm sự hiện diện của một dòng chữ 'thành công'. Nếu bỏ ràng buộc này, mô hình rất thích viết expect('Thành công') — vừa dễ bịa (app có thể hiện chữ khác), vừa xanh giả kể cả khi nghiệp vụ sai. Đứng thứ hai là ràng buộc 'chỉ dùng locator có trong ARIA' để chặn hallucinated selector.
4. Xác minh locator sinh ra trên ứng dụng sống
Dù grounding tốt đến đâu, quy tắc bất di bất dịch là: mọi locator do mô hình sinh phải được xác minh trên app thật trước khi giữ. Xác minh nghĩa là chạy một bước kiểm tra tự động phân giải từng locator trên trang sống và khẳng định nó trỏ tới đúng một phần tử actionable. Locator không phân giải được (0 phần tử) là dấu hiệu hallucination; locator trỏ tới nhiều phần tử (strict mode violation) là dấu hiệu mơ hồ cần siết. Bước này biến 'tin lời mô hình' thành 'kiểm chứng bằng máy', và nên chạy tự động ngay sau khi sinh, trước khi con người review nội dung.
Playwright rất hợp cho việc này nhờ strict mode và auto-waiting: getByRole với name sẽ tự chờ phần tử và ném lỗi nếu khớp nhiều hơn một. Bạn viết một 'linter locator' quét bộ test sinh ra, mở app ở trạng thái tương ứng, thử phân giải từng locator, và đánh dấu cái nào hỏng. Những locator hỏng được trả ngược lại vòng sinh (kèm ARIA snapshot cập nhật) để mô hình sửa, hoặc bị đánh cờ cho con người. Quan trọng: đừng để test có locator chưa xác minh lọt vào nhánh chính — coi 'locator đã xác minh' là điều kiện merge.
💬 Bình luận (0)
Chưa có bình luận nào. Hãy là người đầu tiên!