“Bắt agent làm TDD” đã trở thành một loại kiến thức dân gian lan truyền trong kênh Slack kỹ thuật mà không ai thực sự đo lường. Lý luận nghe có vẻ vững trên bề mặt: TDD giúp developer con người viết code tốt hơn, nên bảo AI agent viết test fail trước, rồi implement, rồi refactor, sẽ mang lại lợi ích tương tự. Tôi đã bảo agent làm chính xác điều này trên nhiều hơn một dự án.

Một thí nghiệm có kiểm soát từ Thoughtworks vừa kiểm chứng trực tiếp giả định đó, với số liệu token thật và đánh giá chất lượng mù thay vì cảm tính. Kết quả nên thay đổi cách bạn prompt coding agent, và nó không giống điều kiến thức dân gian dự đoán.

Thiết Kế Thí Nghiệm

Thiết kế cố tình đơn giản và chống thiên vị: Claude Sonnet 4.6 tạo code dưới hai chiến lược prompting khác nhau — một yêu cầu TDD red-green-refactor nghiêm ngặt (viết test fail, chạy, xác nhận fail, implement, chạy lại, refactor), một cách tiếp cận spec-first thẳng thắn (“implement cái này, rồi viết test”). Một model riêng, Claude Opus 4.8, đánh giá mù chất lượng code kết quả mà không biết phương pháp nào tạo ra output nào. Năm batch task chạy qua cả hai điều kiện.

Bước đánh giá mù này quan trọng hơn nghe có vẻ. Tự báo cáo kiểu “bản TDD cảm thấy kỹ lưỡng hơn” chính xác là loại thiên vị mà một thí nghiệm có kiểm soát tồn tại để loại bỏ.

Số Liệu Chi Phí Token

Đây là chỗ kiến thức dân gian va chạm với thực tế:

Kích thước taskToken TDDToken Non-TDDHệ số
Task nhỏ1,018,245119,8158.5x
Task lớn hơn~cao hơn 2.96xbaseline2.96x

Với task nhỏ, phạm vi rõ ràng, vòng lặp TDD tốn 8.5 lần nhiều token hơn prompting spec-first. Với task lớn hơn, hệ số giảm xuống còn khoảng 3x — vẫn là chi phí đáng kể, chỉ bớt cực đoan vì task lớn phân bổ overhead của chu kỳ red-green trên nhiều công việc implementation thực tế hơn.

Chi phí đó thực sự đi đâu? Mỗi chu kỳ red-green round-trip qua model nhiều lần cho mỗi đơn vị chức năng: viết test, chạy test (xác nhận red), viết implementation, chạy test lại (xác nhận green), rồi một lượt refactor. Mỗi bước đó đọc lại context, suy luận lại về trạng thái hiện tại, và tạo lại output. Một con người làm TDD giữ toàn bộ trạng thái đó trong đầu miễn phí; một agent phải suy ra lại một phần đáng kể từ đầu ở mỗi vòng lặp, vì “trạng thái” đối với model là bất cứ thứ gì nằm trong context window, không phải một mô hình tinh thần bền vững.

Kết Quả Chất Lượng Không Ai Ngờ

Đây là phần thực sự khiến tôi bất ngờ: đánh giá mù bởi Opus 4.8 ưu tiên output không-TDD. Không phải hòa — mà ưu tiên hẳn.

Điểm mutation testing, một proxy hợp lý cho việc test có thực sự bắt được bug hay không, quay về không phân biệt được về mặt thống kê giữa hai cách tiếp cận. Cấu trúc tăng dần, test-trước của vòng lặp TDD — viết một test, làm nó pass, viết test tiếp theo, làm nó pass — đẩy agent về hướng implementation đúng cục bộ, phạm vi hẹp, thỏa mãn từng test khi nó đến, mà không lùi lại để xem xét toàn bộ không gian bài toán. Điều đó thu hẹp phạm vi bao phủ edge-case thay vì mở rộng nó, ngược lại hoàn toàn với lý do người ta nghĩ TDD nên giúp ích.

Với con người, red-green-refactor buộc bạn nghĩ về thiết kế interface và edge case trước khi bạn cam kết vào một implementation, vì viết test trước đòi hỏi tưởng tượng cách code sẽ được gọi. Agent không có ràng buộc nhận thức tương tự — nó có thể giữ toàn bộ bài toán trong context bất kể bạn có bắt nó viết test trước hay không, và ép vòng lặp tuần tự không thêm kỷ luật mà nó thực sự đang thiếu. Nó chỉ thêm overhead quy trình mà không có cơ chế nhận thức nền tảng khiến quy trình đó có giá trị với con người ngay từ đầu.

Tại Sao Điều Này Quan Trọng: Kiến Thức Dân Gian Không Chuyển Giao Được

Đây là một pattern tôi liên tục gặp trong agentic coding: các thực hành hiệu quả với developer con người bị cấy ghép sang workflow agent dựa trên giả định “practice tốt là practice tốt,” mà không kiểm tra xem cơ chế khiến practice đó hiệu quả có thực sự áp dụng với cách model vận hành hay không. Giá trị của TDD với con người đến phần lớn từ việc buộc một trình tự nhận thức cụ thể — nghĩ về interface, rồi nghĩ về implementation. LLM không sắp xếp trình tự suy luận của nó như con người; nó có thể (và thực sự) xem xét interface và implementation đồng thời trong một lần generation. Ép trình tự của con người lên nó không tái tạo lợi ích — nó chỉ thêm chi phí quy trình mà không có lợi ích nhận thức từng biện minh cho chi phí đó với con người.

Tôi Sẽ Thực Sự Làm Gì Thay Vào Đó

Dựa trên số liệu này, đây là pattern prompting tôi đang chuyển sang cho công việc implementation do agent thực hiện:

# Pattern cũ: vòng lặp TDD lặp lại
"Viết một test fail cho X. Chạy nó và xác nhận nó fail.
Implement code tối thiểu để pass. Chạy test lại.
Refactor. Lặp lại cho requirement tiếp theo."

# Pattern mới: spec-first, verify-sau
"Đây là spec đầy đủ cho X, bao gồm edge case: <spec>.
Implement X hoàn chỉnh. Sau đó viết một bộ test toàn diện
bao phủ happy path, các edge case đã tài liệu hóa, và các
điều kiện lỗi. Chạy toàn bộ suite và fix mọi failure."

Khác biệt không phải “bỏ qua test” — mà là “ngừng ép chu kỳ red-green tuần tự khi model không cần trình tự đó để suy nghĩ rõ ràng.” Viết spec đầy đủ trước (phần thực sự có giá trị của TDD — làm rõ requirement trước khi implementation tồn tại), để agent implement trực tiếp theo đó, rồi tạo một bộ test toàn diện trong một lượt thay vì từng test một.

Nơi tôi vẫn cân nhắc vòng lặp lặp lại: agent tự động chạy dài hạn không có con người trong loop trong thời gian dài, nơi một bộ test tăng dần đóng vai trò lưới an toàn chống regression qua nhiều thay đổi tuần tự thay vì một hàm ép buộc thiết kế cho một implementation đơn lẻ. Đó là một bài toán khác — bảo vệ regression theo thời gian — so với bài toán TDD thường được viện dẫn để giải trong một phiên coding đơn lẻ.

Bài Học Thực Sự

Trước khi áp dụng một thực hành kỹ thuật của con người thành một pattern prompt cho agent, hãy hỏi cơ chế nào khiến thực hành đó có giá trị, và kiểm tra xem cơ chế đó có sống sót qua quá trình chuyển giao hay không. Cơ chế của TDD — ép buộc nhận thức tuần tự — không sống sót khi tiếp xúc với một model không suy luận tuần tự như con người. Số liệu chi phí token ở đây là bằng chứng cụ thể, nhưng bài học hữu ích hơn là chính việc kiểm tra: đo lường trước khi bạn sao chép một thực hành của con người sang agent, vì “hiệu quả với developer” và “hiệu quả với model” không phải là cùng một tuyên bố.


Thuận Lương là Technical Lead với hơn 15 năm kinh nghiệm về .NET, cloud architecture, và hệ thống AI. Anh viết về những bài học thực tế từ việc xây dựng hệ thống production.

Xuất nội dung

Bình luận