Kiểm thử trước, mã sau (TDD) là cách làm hợp với trợ lý AI hơn cả thời nó mới ra đời: kiểm thử chính là bản yêu cầu chạy được, thứ duy nhất AI không thể cãi. Khi viết kiểm thử trước, bạn buộc mình nghĩ rõ đầu vào, đầu ra và các ca mép. Sau đó AI viết mã cho tới khi kiểm thử xanh, và bạn có thước đo khách quan thay vì phải đọc từng dòng để đoán nó đúng hay sai.
Bài này trình bày vòng đỏ, xanh, dọn khi có AI tham gia, một ví dụ giả định bằng Jest, những luật phải dặn để AI không gian lận cho qua, và những trường hợp TDD với AI tốn công hơn được. Bạn cần biết cơ bản về một khung kiểm thử; không cần đã từng làm TDD.
Vì sao đảo thứ tự lại hợp với trợ lý AI
Khi bạn nhờ AI viết mã trước rồi mới viết kiểm thử, cả hai thứ đều đến từ cùng một cách hiểu, có khi là cách hiểu sai. Kiểm thử viết sau sẽ khớp với mã, và bạn không có điểm tựa nào độc lập. Đảo lại thứ tự, kiểm thử do bạn viết hoặc do bạn duyệt từng dòng trở thành hợp đồng; mã AI viết chỉ được nhận khi thoả hợp đồng đó.
Thứ hai, kiểm thử là cách giao việc rõ nhất. Mô tả bằng lời luôn có chỗ mập mờ: "làm tròn" là lên hay xuống, "chuỗi rỗng" có tính là thiếu dữ liệu không. Một câu assert không có chỗ mập mờ. Trợ lý AI làm tốt nhất khi đích đến rõ ràng và kiểm chứng được, và bộ kiểm thử cho nó đúng thứ đó.
Thứ ba, vòng lặp trở nên tự động hơn. Với tác tử AI có quyền chạy lệnh, bạn có thể giao "chạy kiểm thử, sửa mã cho tới khi qua, không đụng vào tệp kiểm thử". Tác tử tự lặp vài vòng, bạn chỉ quay lại khi nó xong hoặc kẹt. Không có kiểm thử, tác tử không có cách nào biết lúc nào nên dừng ngoài việc tự cho là đã xong.
Vòng đỏ, xanh, dọn khi có AI
Vòng TDD cổ điển có ba nhịp. Khi có AI, mỗi nhịp có thêm một việc nhỏ cần giữ kỷ luật, cộng thêm một nhịp commit:
- Đỏ. Bạn viết một hoặc vài ca kiểm thử cho hành vi tiếp theo cần có. Có thể nhờ AI gợi ý ca, nhưng giá trị mong đợi phải do bạn quyết. Chạy và xác nhận ca mới đỏ vì đúng lý do, tức là vì hàm chưa làm việc đó, không phải vì gõ sai tên.
- Xanh. Giao cho AI viết mã tối thiểu để các ca đỏ chuyển xanh mà không làm đỏ ca cũ. Dặn rõ không sửa tệp kiểm thử. Bạn tự chạy toàn bộ kiểm thử, không dựa vào lời báo của AI.
- Dọn. Khi mọi thứ xanh, nhờ AI đề xuất làm gọn mã: đặt lại tên, tách hàm, bỏ trùng lặp. Mỗi lần dọn xong chạy lại kiểm thử. Đây là nhịp AI làm rất tốt vì đã có lưới an toàn bên dưới.
- Commit. Commit sau mỗi vòng xanh, kể cả vòng rất nhỏ. Nếu vòng sau AI làm hỏng, bạn quay về điểm xanh gần nhất trong vài giây thay vì gỡ rối một đống thay đổi lẫn lộn.
- Lặp lại với hành vi tiếp theo. Một hàm vừa phải thường cần bốn đến tám vòng; mỗi vòng chỉ nên kéo dài vài phút.
Nhịp nhỏ là điều cốt lõi. Viết hai mươi ca kiểm thử một lúc rồi giao AI viết cả hàm thì bạn quay về cách làm cũ: một khối mã lớn, khó đọc, và khi có ca đỏ, bạn không biết đỏ vì đâu.
Ví dụ giả định: hàm chuẩn hoá họ tên bằng Jest
Giả sử biểu mẫu đăng ký nhận họ tên người dùng gõ tuỳ ý: thừa khoảng trắng, viết thường, viết hoa lộn xộn. Bạn cần một hàm chuẩn hoá. Nhịp đỏ đầu tiên, bạn viết bốn ca:
const { chuanHoaTen } = require('./chuan-hoa');
describe('chuanHoaTen', () => {
test('bỏ khoảng trắng thừa ở hai đầu và ở giữa', () => {
expect(chuanHoaTen(' nguyễn văn an ')).toBe('Nguyễn Văn An');
});
test('viết hoa chữ đầu, giữ nguyên dấu tiếng Việt', () => {
expect(chuanHoaTen('ĐẶNG thị ánh')).toBe('Đặng Thị Ánh');
});
test('chỉ có khoảng trắng thì trả về chuỗi rỗng', () => {
expect(chuanHoaTen(' ')).toBe('');
});
test('ném TypeError khi đầu vào không phải chuỗi', () => {
expect(() => chuanHoaTen(null)).toThrow(TypeError);
});
});
Bạn giao AI: viết hàm chuanHoaTen trong tệp chuan-hoa.js cho bốn ca này qua, không sửa tệp kiểm thử. Một lời giải hợp lý mà AI hay đưa ra là cắt khoảng trắng hai đầu, tách theo khoảng trắng, bỏ phần tử rỗng, viết hoa ký tự đầu bằng toLocaleUpperCase với ngôn ngữ vi, viết thường phần còn lại, rồi ghép lại bằng một dấu cách. Chạy thử: xanh cả bốn.
Vòng tiếp theo mới là chỗ thú vị. Tên tiếng Việt có thể đến từ bộ gõ hoặc hệ thống cũ dùng dạng Unicode tổ hợp, tức chữ cái gốc và dấu là hai ký tự riêng. Bạn viết ca thứ năm: đầu vào là chuỗi đã chuyển sang dạng tổ hợp bằng normalize với tham số NFD, kết quả mong đợi là dạng dựng sẵn NFC. Ca này đỏ, và AI chỉ cần thêm một dòng chuẩn hoá về NFC ở đầu hàm. Đây đúng là kiểu lỗi mà viết mã trước rất dễ bỏ sót, vì trên màn hình hai dạng trông y hệt nhau.
Luật phải dặn để AI không gian lận cho qua
Không sửa, không xoá, không bỏ qua kiểm thử. Gặp ca đỏ khó, tác tử AI có xu hướng tìm đường ngắn: đổi giá trị mong đợi, đánh dấu ca là bỏ qua, hoặc bọc trong khối bắt lỗi để nuốt ngoại lệ. Ghi rõ trong câu lệnh rằng tệp kiểm thử là chỉ đọc, và khi rà thay đổi, nhìn danh sách tệp bị sửa trước tiên.
Không viết mã nhận diện riêng ca kiểm thử. Mã kiểu "nếu đầu vào đúng bằng chuỗi này thì trả về chuỗi kia" làm kiểm thử xanh mà không giải quyết gì. Chuyện này hiếm nhưng có xảy ra, nhất là khi ca kiểm thử ít và quá đặc thù. Thêm một hai ca với dữ liệu khác hẳn là phá được kiểu mã này.
Không thêm tính năng ngoài yêu cầu. AI hay tiện tay xử lý thêm những trường hợp không ai hỏi. Mã thừa không có kiểm thử giữ là mã chưa được kiểm. Dặn: chỉ viết đủ để các ca hiện có qua, ý tưởng mở rộng thì liệt kê ra để bạn quyết có viết ca cho nó hay không.
Dừng lại và báo khi thấy kiểm thử mâu thuẫn. Đôi khi chính bạn viết hai ca mâu thuẫn nhau. Dặn AI chỉ ra mâu thuẫn thay vì cố thoả cả hai bằng mã kỳ quặc. Đây là lợi ích ít người để ý của TDD với AI: nó soát lại logic của chính bạn.
Chia một tính năng thành chuỗi ca kiểm thử nhỏ
Phần khó nhất của TDD không phải viết kiểm thử mà là chọn ca kế tiếp. Với một tính năng mới, bạn có thể nhờ AI đề xuất danh sách ca theo thứ tự từ đơn giản tới phức tạp, rồi tự sắp lại. Thứ tự hay dùng:
- Ca hiển nhiên nhất, đường đi bình thường với dữ liệu sạch, mục đích là dựng khung hàm và đường gọi.
- Các biến thể của đường bình thường: nhiều phần tử, giá trị lớn, ký tự tiếng Việt có dấu.
- Ca rỗng và ca thiếu: chuỗi rỗng, danh sách rỗng, trường không có mặt.
- Ca mép: đúng tại mốc, ngay dưới mốc, ngay trên mốc.
- Ca sai đầu vào: kiểu dữ liệu lạ, giá trị âm, định dạng hỏng. Quyết định trước là hàm nên ném lỗi hay trả giá trị mặc định.
- Ca liên quan tới thứ bên ngoài: thời gian, múi giờ, mạng. Những ca này thường cần đồ giả, để sau cùng.
Câu lệnh gợi ý: "Đây là mô tả tính năng [...]. Đề xuất danh sách ca kiểm thử theo thứ tự nên viết khi làm TDD, mỗi ca một dòng gồm tên ca, đầu vào, kết quả mong đợi và lý do. Chưa viết mã." Bạn duyệt danh sách, sửa kết quả mong đợi nào sai, gạch ca thừa, rồi bắt đầu vòng đầu tiên với ca thứ nhất.
Khi nào TDD với AI đáng công, khi nào không
TDD không phải luật cho mọi dòng mã. Bảng sau giúp bạn quyết định nhanh với từng loại việc thường gặp:
| Loại việc | TDD với AI | Lý do |
|---|---|---|
| Hàm tính toán, xử lý chuỗi, quy tắc nghiệp vụ | Rất đáng | Đầu vào đầu ra rõ, kiểm thử chạy nhanh, AI viết mã cho qua tốt |
| Sửa lỗi đã có người báo | Đáng | Viết ca tái hiện lỗi trước; ca đó đỏ rồi xanh là bằng chứng lỗi đã hết và không quay lại |
| Giao diện còn đang thử bố cục | Ít đáng | Yêu cầu đổi liên tục, kiểm thử viết trước sẽ phải viết lại nhiều lần |
| Mã khám phá, thử một thư viện lạ | Chưa đáng lúc đầu | Chưa biết mình muốn gì; viết thử rồi bỏ, khi chốt hướng mới viết kiểm thử |
| Tích hợp nhiều dịch vụ ngoài | Đáng có chọn lọc | TDD cho phần logic của bạn, phần gọi ra ngoài kiểm bằng kiểm thử tích hợp riêng |
Dòng sửa lỗi đáng nhấn mạnh: ngay cả khi nhóm bạn không theo TDD, quy ước "lỗi nào cũng có một ca kiểm thử đỏ trước khi sửa" là thói quen rẻ mà hiệu quả. AI viết ca tái hiện rất nhanh nếu bạn đưa đủ bước tái hiện và dữ liệu đã che thông tin thật của khách.
Đưa vào nếp làm việc hằng ngày
Đừng bắt đầu bằng cả dự án. Chọn một hàm mới trong tuần này, loại hàm tính toán thuần, và làm đúng vòng đỏ, xanh, dọn với AI. Ghi lại thời gian, số vòng, và số lần AI định sửa kiểm thử. Sau ba bốn hàm, bạn sẽ có cảm nhận rõ cách này nhanh hơn hay chậm hơn với mình.
Nếu dùng tác tử AI, viết luật TDD vào tệp hướng dẫn dự án mà công cụ đọc mỗi lần bắt đầu: kiểm thử là chỉ đọc khi đang ở nhịp xanh, mỗi vòng phải chạy toàn bộ kiểm thử, dừng và báo khi thấy mâu thuẫn. Nhờ vậy bạn không phải dặn lại mỗi lần, và người khác trong nhóm cũng dùng cùng một luật.
Khi rà yêu cầu gộp mã của đồng nghiệp làm theo cách này, đọc kiểm thử trước mã. Nếu kiểm thử nói đúng điều tính năng cần làm, phần mã do AI viết có thể đọc nhanh hơn ở chỗ logic thuần, dồn sức vào tên gọi, hiệu năng và những chỗ đụng dữ liệu thật. Kiểm thử tốt không thay được người rà, nhưng giúp người rà nhìn đúng chỗ đáng nhìn.
Câu hỏi thường gặp
TDD với AI có chậm hơn để AI viết mã luôn không?
Ở vài hàm đầu thì có, vì bạn phải tập viết kiểm thử trước. Về sau thường nhanh hơn xét tổng thể, vì ít phải đọc dò từng dòng mã AI và ít lỗi lọt sang giai đoạn sau. Cách chắc nhất là tự đo trên công việc của mình.
Có được nhờ AI viết luôn cả kiểm thử trong TDD không?
Được nhờ AI đề xuất và viết nháp, nhưng bạn phải đọc và duyệt từng giá trị mong đợi trước khi sang nhịp xanh. Nếu trong cùng một lượt AI viết cả kiểm thử lẫn mã, bạn mất điểm tựa độc lập mà TDD mang lại.
Tác tử AI vẫn sửa tệp kiểm thử dù đã dặn thì làm sao?
Xem danh sách tệp thay đổi trước khi xem nội dung. Nếu tệp kiểm thử có trong đó, bỏ thay đổi ở tệp đó và giao lại việc. Có thể thêm một bước trong quy trình tích hợp liên tục cảnh báo khi một nhánh vừa sửa mã vừa sửa kiểm thử.
TDD có dùng được với mã cũ chưa có kiểm thử không?
Dùng được cho phần mã mới hoặc phần sắp sửa. Trước khi động vào mã cũ, viết vài ca ghi lại hành vi hiện tại để làm lưới, sau đó áp dụng TDD cho thay đổi mới. Không cần viết kiểm thử cho cả dự án trước rồi mới bắt đầu.
Mỗi vòng nên viết bao nhiêu ca kiểm thử?
Thường là một, nhiều nhất hai ba ca rất gần nhau. Vòng càng nhỏ, khi đỏ càng dễ biết vì sao, và AI càng ít cơ hội viết thừa mã. Nếu một vòng mất quá mười lăm phút, hãy chia nhỏ ca ra.
TDD có giúp gì khi AI hay bịa hàm không tồn tại?
Có. Mã gọi hàm bịa sẽ đỏ ngay khi bạn chạy kiểm thử, nên lỗi lộ ra trong vài giây thay vì lúc chạy thật trước mặt người dùng. Điều kiện là bạn chạy kiểm thử trên máy mình chứ không tin lời báo của AI.