0

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đá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ý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:

  1. Specification rõ ràng — biết mình đang build gì trước khi build
  2. Feedback nhanh — biết ngay khi AI tạo code sai
  3. Confidence khi deploy — test pass = safe to ship (gần như)
  4. Documentation tự động — tests là spec, spec là tests
  5. 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


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí