Một thay đổi nhỏ trong pricing còn đang dang dở thì review xuất hiện câu hỏi quen thuộc: “Test đâu?” Cả phòng chững lại. Ai cũng biết test là cần, nhưng ai cũng từng thấy một chỉnh sửa tưởng đơn giản kéo theo hai giờ vật lộn với mock, fixture và setup dễ vỡ. TDD nghe rất đúng đắn; giữa một tuần delivery bận rộn, câu hỏi thật vẫn là: phần công bỏ ra có đáng không?
Test-Driven Development thường được tóm trong ba nhịp: viết một test đang fail, viết ít code nhất để test pass, rồi refactor khi test vẫn xanh. Red, green, refactor. Vòng lặp nghe đơn giản, nhưng giá trị của nó không chỉ là cuối cùng có thêm test. TDD buộc người viết mô tả kết quả mong đợi trước khi quá yêu cách cài đặt vừa nghĩ ra.
Trật tự ấy tác động trực tiếp tới thiết kế. Viết test trước nghĩa là gặp code từ phía người dùng nó: đầu vào nào hợp lệ, đầu ra nào phải có, edge case nào đáng giữ, và caller không nên biết chi tiết gì bên trong. Test khó viết đôi khi là dấu hiệu API cũng khó dùng. Một quy tắc nhỏ mà phải khởi động nửa application mới thử được thì code có thể đang dính quá chặt. Test nào cũng cần năm mock thì ranh giới có lẽ chưa rõ. TDD đưa những chi phí ấy ra ánh sáng sớm hơn.
Nó thường đáng công ở phần logic quan trọng, nhiều nhánh hoặc hay đổi: quy tắc định giá, kiểm tra quyền, lịch chạy, biến đổi dữ liệu, validation, state machine và quyết định thử lại. Các khu vực này có đủ edge case để “viết test sau” dễ biến thành bài kiểm tra trí nhớ. Test trước khiến nhóm chậm lại đúng nơi sự chính xác quan trọng hơn tốc độ gõ.
TDD cũng hữu ích khi chuẩn bị refactor. Một characterization test nhỏ ghi lại cách vận hành hiện tại trước khi thay đổi cấu trúc. Từ đó, nhóm có thể dọn code mà không phải đoán liệu mình vừa làm lệch điều gì. Cuộc tranh luận cũng bớt cảm tính: thay vì hỏi code mới có “sạch” hơn không, ta kiểm tra được kết quả nhìn thấy từ bên ngoài còn được giữ hay không.
Tất nhiên, TDD không miễn phí. Khi nhóm còn chưa hiểu bài toán, nó có thể làm giai đoạn khám phá nặng thêm. Với spike, prototype hoặc bản phác UI, bước hợp lý đôi khi là làm nhanh một bản để học, bỏ nó đi, rồi quay lại viết test khi hình dạng vấn đề đã rõ. Ép TDD lên mọi dòng code chỉ biến một cách làm hữu ích thành thủ tục để chứng minh mình làm đúng quy trình.
TDD còn phản tác dụng nếu test bám vào chi tiết cài đặt thay vì kết quả. Một refactor làm hàng loạt test gãy dù tính năng vẫn chạy đúng không tạo thêm an toàn; nó đang đóng băng nhầm thứ. Test tốt hỏi unit hứa gì với bên ngoài, không kiểm tra private helper nào phải được gọi. Phân biệt được hai việc này cần luyện tập, nên test cũng xứng đáng được review kỹ như production code.
Mock là chỗ cần dè chừng riêng. Mock giúp cô lập logic và giữ test chạy nhanh. Nhưng dùng quá tay sẽ tạo ra một hệ thống tưởng tượng, nơi mọi thứ đều pass vì test đã dạy code làm việc với các thành phần giả theo đúng kịch bản. Đến lúc tích hợp thật mới vỡ. Một test suite khỏe thường có nhiều tầng: unit test cho quy tắc, integration test cho ranh giới quan trọng và một nhóm end-to-end test nhỏ bảo vệ luồng trọng yếu.
Khi tính thời gian, phải tính cả giá của việc không viết test. Bug trong permission rule có thể kéo theo xử lý sự cố, giải thích với bộ phận hỗ trợ và sửa dữ liệu. Thiếu test quanh thanh toán khiến những lần sửa sau đều chậm vì không ai dám chạm. Trong các trường hợp ấy, TDD chậm hơn ở bàn phím nhưng nhanh hơn trong cả vòng đời tính năng. Chỉ nhìn vào thời gian viết ban đầu thì phép tính sẽ sai.
Nhóm thực dụng không biến TDD thành bản sắc. Họ dùng nó ở nơi tạo được đòn bẩy: quy tắc khó, thay đổi rủi ro và phần code sắp refactor. Họ có thể hoãn test với thử nghiệm chắc chắn sẽ bỏ. Họ giữ test dễ đọc và xóa những test không còn bảo vệ kết quả nào. Vòng red-green-refactor là công cụ để suy nghĩ, không phải huy hiệu.
Vì vậy, câu trả lời không phải lúc nào cũng “có” hoặc “không”. Nếu chưa chắc sản phẩm nên làm gì, hãy nói chuyện, prototype và làm rõ trước. Nếu kết quả mong đợi đã rõ nhưng chưa chắc code có tiếp tục làm đúng sau nhiều lần thay đổi, TDD thường là khoản đầu tư hợp lý.
Lần review tới, thay vì hỏi TDD nói chung có đáng không, có thể hỏi cụ thể hơn: thay đổi này có thể làm hỏng điều gì, kết quả nào cần được bảo vệ, và loại test nào giúp người sửa tiếp theo bớt lo? Khi câu hỏi đi vào đúng rủi ro, kiểm thử thôi là nghi thức và trở lại đúng vai trò của nó: một phần trong cách nhóm suy nghĩ.