Khi AI Viết Code: Tại Sao Tôi Vẫn Viết Test Trước
AI đã thay đổi cách tôi làm việc — nhưng không phải cái cách bạn nghĩ.
Nó Bắt Đầu Bằng Một Prompt
Một ngày đẹp trời, mở Claude Code, gõ:
Viết function tính tổng các phần tử trong một array.
3 giây sau, Claude trả về một function hoàn hảo. Đẹp. Sạch. Pass luôn cả ví dụ mẫu.
Mừng rơi nước mắt. Rồi gõ tiếp:
Viết function kiểm tra input có hợp lệ không,
chặn SQL injection, XSS, và trả về error message phù hợp.
Lần này Claude trả về một đoạn code dài hơn. Trông cũng khá hợp lý. Copy-paste vào project.
Một tuần sau, production down 3 tiếng vì một lỗi logic mà không hề phát hiện.
AI Vừa Kỳ Diệu, Vừa Đáng Sợ
Đừng hiểu nhầm — tôi không thần thánh hóa AI. AI giỏi thật: viết code nhanh, tiết kiệm hàng giờ boilerplate, giúp brainstorm khi tôi bí ý tưởng, thậm chí viết được test cases đàng hoàng nếu tôi cho nó một pattern tốt.
Nhưng.
AI cũng hallucinate — tạo ra code trông đúng nhưng logic sai bè. Nó dùng != thay vì == mà không gợi ý gì. Nó generate code chứa lỗ hổng bảo mật mà nó còn khẳng định là "secure by design." Nó tạo ra những đoạn code mà chính nó cũng không giải thích nổi.
Kent Beck — người phát minh ra TDD — viết trong cuốn sách mới:
"AI is both insanely impressive and incredibly unreliable."
Đây là sự thật. Và đây cũng là lý do tôi bắt đầu nghĩ lại về cách tôi làm việc với AI.
TDD — Cái Tôi Đã Bỏ Qua, Rồi Tìm Lại
TDD = Test-Driven Development. Cycle đơn giản đến mức ai nghe xong cũng nghĩ "Ồ, thế à" — nhưng ai đã từng áp dụng nghiêm túc mới hiểu:
RED → Viết test TRƯỚC, code chưa có nên test fail
GREEN → Viết code để test pass
REFACTOR → Dọn code, vẫn đảm bảo test pass
↺ Lặp lại
Tôi đã dùng TDD trước đây, rồi bỏ, rồi quay lại. Không phải vì động viên từ ai. Mà vì sau khi dùng AI, tôi nhận ra mình cần nó hơn bao giờ hết.
Bước 1: RED — Viết Test Trước, Rồi Mới Prompt
Thay vì prompt một lần cho cả feature lớn, tôi bắt đầu bằng việc định nghĩa spec bằng test cases.
Nhưng thật ra, prompt cho AI không bao giờ là một lần là xong. Đó là một vòng lặp:
Prompt → Feedback → Điều chỉnh → Prompt lại → Feedback → ...
Đầu tiên, tôi có thể gõ:
Viết unit tests cho hàm tính phí giao dịch.
Claude trả về. Tôi đọc, thấy thiếu boundary cases, tôi điều chỉnh:
Thêm cases:
✓ Số lượng = 0 → reject
✓ Số lượng âm → reject
Rồi Claude bổ sung. Tôi tiếp tục tinh chỉnh cho đến khi bộ test đủ cover nghiệp vụ.
Không có "prompt chuẩn" ở lần đầu. Có một quá trình tinh chỉnh, và có thể cần đến nhiều sub-agents hỗ trợ: một agent để viết happy path, một agent để đào edge cases, một agent để kiểm tra security requirements.
Điều tôi muốn nhấn mạnh là: spec cuối cùng phải nằm trong test cases. Prompt chỉ là bước trung gian để đạt được spec đó.
Tests fail (vì code chưa có) — đây là RED.
Robert C. Martin — tác giả Clean Code — định nghĩa Three Laws of TDD:
"You are not allowed to write any production code until you have first written a failing unit test."
Constraint này buộc tôi phải nghĩ về nghiệp vụ trước khi code. Edge cases là gì? Error scenarios ra sao? Security requirements như thế nào?
Test-First không chỉ là practice kỹ thuật. Nó là cách tôi communicate specifications với AI một cách chính xác — bằng code.
Bước 2: GREEN — Code AI Để Pass Tests
Sau khi có bộ test, tôi bắt đầu viết implementation.
Nhưng đây cũng không phải một lần prompt là xong. Tôi có thể:
- Prompt Claude viết implementation base
- Một agent khác review: có lỗ hổng bảo mật không?
- Tinh chỉnh, chạy test, fix nếu fail
- Lặp lại cho đến khi test pass
Claude viết code → Agent review security → Chạy test → Fail → Fix → Pass → GREEN
TDD cycle không thay đổi — chỉ có động tác "viết code" giờ là cả một workflow nhỏ bên trong.
Martin Fowler viết:
"Write a failing test before you write the code — that way you know exactly what you're building."
Khi tôi prompt AI mà không viết test trước, AI có quyền tự do hoàn toàn. Nó có thể bỏ qua bất kỳ edge case nào, và tôi sẽ không biết cho đến khi user report bug.
Test-First Prompting khóa AI vào specification của tôi.
Bước 3: REFACTOR — AI Tốt, Nhưng Chưa Đủ Tốt
Sau khi code chạy được và pass tests, tôi bắt đầu refactor.
Nhưng bước này cũng là cả một vòng lặp:
Đọc code → Thấy điểm phức tạp → Prompt simplify → Test lại
→ Đọc lại → Agent review security → Fix → Test lại → ...
Trong suốt vòng lặp này, tests luôn pass. Không có test pass → tôi dừng, không refactor tiếp.
Martin Fowler nhấn mạnh:
"If you want to refactor, the essential precondition is having solid tests. If you have them, you can refactor aggressively; if you don't, you should build them first."
AI viết code chạy được. Nhưng tôi mới là người quyết định code đáng đọc và đáng bảo trì hay không.
CI/CD: Bức Tường Lửa Của "Discerning Partner"
Tôi đã nói về việc con người đóng vai trò Discerning Partner. Nhưng có một điều quan trọng: cái vai trò đó phải được "thể chế hóa" bằng máy móc.
Automation Bias — kẻ thù ngầm
Trong một ngày làm việc với deadline dồn dập, có những lúc tôi:
- Lười chạy test vì "nó chắc đúng rồi"
- Trust AI mù quáng vì "Claude viết mà, sao sai được"
- Copy-paste nhanh để kịp deadline
Lúc đó, automation bias len lỏi vào. Và tôi thua.
CI/CD là "người gác đền" không bao giờ mệt
TDD không chỉ nằm ở local máy. Nó phải được gắn chặt vào Git Pipeline:
Local: [Dev] → [Claude] → [copy-paste] → [commit] → [push]
↓
CI/CD: [Test fail] → [BLOCK PR] → [Dev nhận feedback] → [fix]
Pipeline không quan tâm bạn đang vội. Không quan tâm AI viết gì. Nó chỉ quan tâm: test có pass không?
Dù tôi có tin AI mù quáng đến đâu — pipeline vẫn block PR đó lại.
Pipeline chính là Discerning Partner không bao giờ biết mệt mỏi trong kỷ nguyên AI.
Tại Sao TDD "Fit" Với AI Đến Vậy?
Kent Beck nói:
"The AI-enabled workflow of the future will look very different to what we have now, but all the indications are that the most effective approach is going to be incremental, have checks and balances to avoid hallucinations, and systematically involve humans in the loop to ensure quality."
"And the closest workflow we have to that today, is TDD."
Tôi không khẳng định TDD là lời giải duy nhất. Điều tôi nói là cá nhân tôi: TDD giúp tôi làm việc với AI hiệu quả hơn:
| TDD giúp tôi... | Cụ thể như thế nào |
|---|---|
| Chia nhỏ bài toán | Thay vì prompt AI cho cả feature, tôi chia thành từng test case nhỏ |
| Feedback nhanh | Sau mỗi prompt, chạy test → pass thì ok, fail thì biết ngay |
| Có "bằng chứng" | Test pass cho tôi confidence — không phải tuyệt đối, nhưng đủ để không blindfold |
| Team-wide verification | Pipeline CI/CD đảm bảo mọi PR đều pass tests |
Bạn có workflow khác mà hiệu quả? Hãy dùng nó. Điều quan trọng là bạn có cách verify output.
"Leaky Abstraction" — Hiểu Để Dùng AI Tốt Hơn
Joel Spolsky có khái niệm "leaky abstractions": khi abstraction cao cấp thất bại một cách tinh vi, và độ phức tạp bên dưới "rò rỉ" ra ngoài.
AI cũng vậy. Nó hứa hẹn abstraction cho việc viết code — nhưng bạn đã thấy "rò rỉ" chưa? Hallucinations, logic sai ngầm, lỗ hổng bảo mật, code không đáng đọc.
Kent Beck nhận xét:
"The programmers who are going to be most effective with AI are going to be the ones who 'know what good looks like,' both in terms of code quality, test structure, and so on, but also in terms of what a safe and reliable workflow for software development looks like."
Sự tương tự lịch sử:
Khi ngôn ngữ cấp cao như C thay thế assembly, programmer hiệu quả nhất là người hiểu cả assembly bên dưới.
Giờ đây, khi AI hỗ trợ viết code, programmer hiệu quả nhất là người hiểu TDD.
Không phải vì họ viết test trước code. Mà vì họ hiểu tại sao viết test trước code lại quan trọng.
Human-in-the-Loop: Không Chỉ Là "Có Người Ngồi Đó"
HITL Bắt Nguồn Từ Đâu?
Human-in-the-Loop (HITL) không phải xuất phát từ software engineering. Nó bắt đầu từ machine learning — nơi con người tham gia vào quá trình huấn luyện, đánh giá, và tinh chỉnh model.
| Giai đoạn | Vai trò con người |
|---|---|
| Data Labeling | Annotate dữ liệu cho supervised learning |
| Model Training | Feedback qua RLHF |
| Validation & Testing | Verify predictions, bắt edge cases |
| Deployment Monitoring | Review exceptions, audit decisions |
HITL phổ biến trong y tế (bác sĩ xác nhận chẩn đoán AI), tài chính (analyst review giao dịch đáng ngờ), xe tự lái (human operator can thiệp).
Tại Sao HITL Quan Trọng?
1. Chia sẻ trách nhiệm HITL phân bổ accountability giữa developers, operators, và AI system — không ai có thể đổ lỗi cho "AI viết mà."
2. Auditability Mỗi lần con người can thiệp đều tạo ra audit trail — quan trọng trong các ngành regulated: tài chính, y tế, pháp lý.
3. Error Recovery Khi model đưa ra prediction tồi, con người can thiệp trước khi gây hại.
Automation Bias — Kẻ Thù Ngầm
Automation bias = xu hướng trust AI suggestions một cách mù quáng — không kiểm tra, không verify, cứ accept vì AI nói vậy.
Thực tế:
- Nhiều dev copy-paste code từ AI mà không đọc kỹ
- AI nói "secure by design" → họ tin luôn
- AI suggest một approach → họ dùng mà không question
Tại sao nguy hiểm? Vì nó tạo ra false confidence. Bạn nghĩ mình đã verify rồi, nhưng thực ra bạn chỉ đang tin AI.
"Discerning Partner" — Từ Khuyến Nghị Thành Nguyên Tắc
Kent Beck nói:
"AI works best when you, the user, are a discerning partner rather than passive recipient."
| Passive Recipient | Discerning Partner |
|---|---|
| AI suggest gì dùng đó | Tôi đặt spec TRƯỚC, AI phải satisfy |
| Không kiểm tra lại | Tôi viết test để verify |
| Tin AI là đúng | Tôi xác nhận qua bằng chứng |
| Copy-paste blindfold | Đọc kỹ, hiểu, rồi mới dùng |
| Phụ thuộc ý thức cá nhân | Có CI/CD block nếu sai |
Accountability — Ai Chịu Trách Nhiệm Khi AI Gây Lỗi?
Robert C. Martin nói:
"We are not paid to write code. We are paid to solve problems."
Khi code lên production gây lỗi:
- User không quan tâm "AI viết mà"
- Khách hàng không hỏi "ChatGPT hay Claude?"
- Luật pháp ngày càng quan tâm đến AI-assisted decisions
EU AI Act (2024) đã classify AI risk levels và bắt buộc human oversight cho high-risk AI systems.
Trong ngành tài chính — nơi tôi làm việc — một lỗi logic trong hệ thống tính phí, thanh lý margin hay cấp vốn không chỉ là bug. Nó là rủi ro pháp lý và thiệt hại tài chính thực sự.
Chi phí cho một lỗi logic sinh ra bởi AI trong Core Backend đắt đỏ hơn gấp hàng vạn lần so với thời gian tiết kiệm được nhờ gõ code nhanh.
TDD Là Một Hình Thức Của HITL
Khi tôi viết test trước (RED), rồi yêu cầu AI viết code (GREEN), rồi review lại (REFACTOR):
Tôi (Human) → Viết spec (test) → AI viết code → Tôi verify → Tôi quyết định
↑ ↓
←←←←←←← Human-in-the-Loop ←←←←←←←←
Test chính là interface giữa tôi và AI:
- Là cách tôi communicate expectations
- Là cách AI verify lại output
- Là cách tôi có bằng chứng trước khi deploy
- Là cách để accountability không bị mất
TDD = HITL được formalize thành workflow.
Kết Luận: TDD Là "Safety Net" Của Tôi
Sau tất cả, tôi vẫn viết test trước code — khi dùng Claude.
Không phải vì tôi cổ hủ. Không phải vì tôi không tin AI.
Mà vì TDD cho tôi:
- Specification rõ ràng — biết mình đang build gì trước khi build
- Feedback nhanh — biết ngay khi AI tạo code sai
- Confidence khi deploy — test pass = safe to ship (gần như)
- Documentation tự động — tests là spec, spec là tests
- Người gác đền tự động — CI/CD đảm bảo team không ai bypass tests
TDD không hoàn hảo. Nó có chi phí ban đầu. Không phải codebase nào cũng phù hợp. Nhưng với tôi, nó là cách tôi giữ control khi làm việc với AI — và cách team giữ control khi làm việc với tôi.
Nguồn trích dẫn
- Kent Beck — TDD in the Age of AI, Preface to the Third Edition
- Robert C. Martin — Clean Code, The Clean Coder
- Martin Fowler — Refactoring: Improving the Design of Existing Code
- EU AI Act (2024) — European Commission
- NIST AI Risk Management Framework — NIST
- ISO/IEC 42001 — AI Management Systems Standard
- Joel Spolsky — "The Law of Leaky Abstractions" — Joel on Software
All rights reserved