Claude Code đã sửa mã và báo: "Các bài kiểm thử đã qua". Đằng sau câu ngắn đó là các tệp kiểm thử, lệnh chạy và kết quả cụ thể. Trước tiên, ta sẽ tìm hiểu có những cách kiểm thử nào và có thể kiểm tra thủ công những gì, sau đó xem một bài kiểm thử tự động hoạt động ra sao và kết quả của nó nói lên điều gì.

Ta chỉ cần một ví dụ nhỏ về việc cộng điểm. Bạn chưa cần biết trước các thư viện kiểm thử: đầu tiên, ta sẽ thực hiện kịch bản bằng tay, rồi xác định kết quả mong đợi cho các bài kiểm thử tự động và đọc báo cáo chạy kiểm thử.

Có những cách kiểm thử nào

Kiểm thử giúp tìm hiểu chương trình hoạt động ra sao, có đáp ứng yêu cầu không và vấn đề xuất hiện ở đâu. Chẳng hạn, điểm có được cộng đúng không, có gửi được đơn khi bỏ trống một trường không, thông báo lỗi có dễ hiểu không. Để kiểm tra, ta chọn điều kiện, thực hiện các thao tác rồi xem kết quả.

Có thể phân loại kiểm thử theo nhiều tiêu chí. Trước hết, hãy xem ai thực hiện thao tác và kiểm tra kết quả:

  • kiểm thử thủ công - một người tự thực hiện các bước kiểm tra và đánh giá kết quả;
  • kiểm thử tự động - giao một phần việc kiểm tra cho chương trình: chương trình thực hiện các thao tác và phép so sánh đã định. Các lần kiểm tra này thường được gọi là bài kiểm thử tự động.

Đó là cách phân loại theo phương thức thực hiện. Còn một câu hỏi khác: kiểm tra điều gì? Kiểm thử chức năng tập trung vào hành vi, chẳng hạn điểm có được cộng như quy định không. Kiểm thử phi chức năng tập trung vào đặc tính hoạt động của ứng dụng, chẳng hạn tốc độ phản hồi hoặc mức độ dễ sử dụng. Hai tiêu chí này độc lập: cùng một chức năng có thể được kiểm tra thủ công hoặc tự động.

Tự động hóa không loại bỏ vai trò của con người. Bạn vẫn cần chọn những kịch bản hữu ích, xác định kết quả mong đợi và tìm hiểu các điểm không khớp. Chỉ có phần việc lặp đi lặp lại là do chương trình thực hiện.

Kiểm thử thủ công là gì

Khi kiểm thử thủ công, bạn tự thực hiện các thao tác và xem kết quả có đúng như mong đợi không. Bạn không nhất thiết phải viết mã kiểm thử. Điều cần có là điều kiện rõ ràng và một câu hỏi mà bạn muốn tìm lời giải đáp.

Giả sử trong một cổng thông tin mẫu, mỗi lần khớp được cộng 10 điểm, vậy ba lần khớp được 30 điểm. Bạn có thể kiểm tra việc cộng điểm bằng tay như sau:

  1. mở phiên bản cổng thông tin cần kiểm tra, chuẩn bị tài khoản có số dư 0 và chưa từng nhận thưởng;
  2. tạo đơn có ba lần khớp rồi gửi đúng một lần;
  3. xem số dư sau khi gửi: kết quả mong đợi là 30 điểm;
  4. ghi lại kết quả thực tế. Nếu số điểm khác dự kiến, hãy lưu các bước và, nếu cần, chụp màn hình.

Số dư ban đầu rất quan trọng. Nếu tài khoản đã có 20 điểm, sau khi cộng bạn sẽ mong đợi 50 chứ không phải 30. Thiếu điều kiện ban đầu thì ta dễ nhầm kết quả đúng thành lỗi, hoặc ngược lại.

Bạn có thể ghi ngắn gọn kết quả kiểm tra như sau: "Số dư trước khi gửi là 0, ba lần khớp, gửi một lần, mong đợi 30, nhận được 3". Dựa vào ghi chú này, người khác hoặc Claude Code sẽ hiểu cần tái hiện điều gì. Câu "điểm hoạt động kỳ lạ" thì không nói lên được điều đó.

Kiểm thử thủ công cũng có thể mang tính khám phá: bạn tìm hiểu ứng dụng và nghĩ ra các bước kiểm tra mới trong quá trình sử dụng. Chẳng hạn, bạn nhận thấy nút vẫn bấm được sau khi gửi và quyết định thử bấm lần nữa. Ở đây không có sẵn lộ trình cho từng bước, nhưng mục tiêu vẫn rõ: tìm hiểu hành vi và ghi lại vấn đề phát hiện được. Không phải chỉ bấm thử cho vui.

Bạn hoàn toàn có thể dùng công cụ. Chẳng hạn, mở console của trình duyệt, xem phản hồi của máy chủ, chụp màn hình hoặc lập danh sách kiểm tra. "Thủ công" nghĩa là người điều khiển việc kiểm tra và đánh giá kết quả, chứ không phải làm việc mà không dùng công cụ.

Kiểm tra thủ công phù hợp khi cần khám phá hành vi mới hoặc đánh giá giao diện có dễ hiểu không. Kiểm tra tự động hữu ích khi cần lặp lại nhiều lần các thao tác và phép so sánh đã mô tả. Không nên chia cứng rằng "giao diện chỉ kiểm tra bằng tay, phép tính chỉ kiểm tra bằng mã": thao tác trên trình duyệt cũng có thể tự động hóa, còn phép tính vẫn có thể kiểm tra thủ công.

Khi bạn bấm nút chạy kiểm thử tự động, đó chưa phải là kiểm thử thủ công: sau khi khởi chạy, chương trình tự thực hiện các bước kiểm tra. Còn nếu Claude Code lập danh sách các bước rồi bạn tự thao tác trên cổng thông tin và đánh giá kết quả, việc kiểm tra vẫn là thủ công. Agent chỉ giúp chuẩn bị.

Kiểm thử tự động là gì

Hãy tưởng tượng bạn đang kiểm tra máy tính. Bạn nhập 2 + 2, biết trước kết quả mong đợi là 4 rồi so sánh với con số trên màn hình. Nếu thấy 5, rõ ràng kết quả của phép tính này không đúng. Bạn biết đáp án đúng bất kể máy tính hiển thị gì.

Bài kiểm thử tự động thực hiện công việc tương tự bằng chương trình. Đó là một chương trình nhỏ thiết lập điều kiện ban đầu, thực hiện thao tác rồi kiểm tra kết quả. Thay vì con người, mã chương trình sẽ so sánh kết quả. Từ đây, ta sẽ gọi ngắn gọn bài kiểm thử tự động là bài kiểm thử.

Quay lại cổng thông tin có tính năng thưởng: mỗi lần khớp được 10 điểm, ba lần khớp phải được 30 điểm. Có thể mô tả việc kiểm tra bằng lời như sau:

  1. chuẩn bị số lần khớp: 3;
  2. tính phần thưởng;
  3. kiểm tra xem phần thưởng có bằng 30 không.

Đó sẽ là một bài kiểm thử. Nó có một trường hợp đã biết trước và kết quả mong đợi. Quy tắc "10 điểm cho mỗi lần khớp" áp dụng cho cổng thông tin mẫu của chúng ta; ứng dụng khác có thể dùng quy tắc khác.

Bài kiểm thử không tự nghĩ ra người dùng được nhận bao nhiêu điểm. Cần có người lấy quy tắc từ yêu cầu và ghi rõ kết quả mong đợi. Người đó có thể là lập trình viên hoặc Claude Code theo yêu cầu của bạn, nhưng vẫn cần đối chiếu ý nghĩa của kết quả mong đợi với nhiệm vụ.

Vì sao cần lặp lại việc kiểm tra

Kiểm tra thủ công việc cộng điểm một lần thì khá tiện. Vấn đề nảy sinh về sau: bạn thêm loại phần thưởng mới, thay đổi cách xử lý đơn hoặc nhờ agent đơn giản hóa một hàm. Sau mỗi thay đổi như vậy, câu hỏi cũ lại xuất hiện: ba lần khớp vẫn được 30 điểm chứ?

Bạn có thể chuẩn bị lại đơn, thực hiện các thao tác và xem kết quả mỗi lần. Hoặc ghi bước kiểm tra này vào mã rồi chạy lại. Với cùng điều kiện chuẩn bị, bài kiểm thử sẽ lặp lại các thao tác và phép so sánh đã định, không cần giải thích lại phải bấm vào đâu và kết quả nào được xem là đúng.

Dự án càng phát triển thì càng có nhiều quy tắc như vậy. Đơn không có lần khớp nào thì không được cộng điểm; không được tính một đơn hai lần; danh sách phần thưởng cần có thứ tự nhất định. Bạn có thể chạy các bài kiểm thử cùng lúc và xem kỳ vọng nào không còn được đáp ứng sau thay đổi.

Đó là ích lợi của tự động hóa: kiểm tra lại hành vi đã mô tả cần ít thao tác thủ công hơn. Viết bài kiểm thử cũng tốn thời gian, và khi yêu cầu thay đổi thì bạn cần xem lại chúng. Nhưng với những quy tắc thường xuyên phải kiểm tra, công sức này được bù lại qua nhiều lần chạy.

Các bài kiểm thử nằm ở đâu

Bài kiểm thử được lưu trong các tệp thông thường của dự án. Trong một dự án JavaScript nhỏ, bạn có thể đặt phần kiểm tra cạnh mã mà nó gọi đến:

reward-example/
├── package.json
└── src/
    ├── reward-flow.js
    └── reward-flow.test.js

Tệp reward-flow.js chứa phần tính thưởng. Tệp reward-flow.test.js chứa mã gọi phép tính đó và kiểm tra kết quả. Tệp package.json mô tả cấu hình và các lệnh của dự án, trong đó có lệnh chạy kiểm thử.

Phần .test.js trong tên giúp nhận ra tệp kiểm thử. Đây là quy ước được các công cụ kiểm thử hỗ trợ, không phải quy tắc riêng của ngôn ngữ JavaScript. Dự án khác có thể dùng cách đặt tên khác hoặc một thư mục riêng như tests/. Một tệp có thể chứa nhiều bài kiểm thử; không bắt buộc mỗi tệp mã nguồn phải có một tệp kiểm thử riêng bên cạnh.

Nếu Claude Code đang làm việc trong một dự án chưa quen, trước tiên hãy nhờ nó tìm bài kiểm thử cộng điểm hiện có và cho biết đường dẫn. Nhờ vậy, bạn có thể đối chiếu phần kiểm tra với hành vi cần xác nhận, ngay cả khi chưa biết rõ từng thư mục.

Biến ý định thành bước kiểm tra như thế nào

Yêu cầu "sửa việc cộng điểm và kiểm tra bằng các bài test" để lại cho agent hai câu hỏi: thế nào là cộng điểm đúng và dùng gì để xác nhận kết quả? Tốt hơn hết, hãy bắt đầu từ hành vi mà bạn muốn có. Ví dụ: "Mỗi lần khớp được 10 điểm. Nếu khớp ba lần thì phần thưởng phải là 30 điểm".

Bạn có thể biến ý định đó thành bước kiểm tra mà chưa cần viết một dòng mã nào. Hãy mô tả ba điều sau:

Cần xác địnhTự hỏi điều gì?Ví dụ
Điều kiện ban đầuTrước khi thao tác, ta đã biết gì?Có kết quả với ba lần khớp; mỗi lần được 10 điểm
Thao tácCụ thể ta kiểm tra điều gì?Tính phần thưởng cho kết quả này
Kết quả mong đợiDựa vào điều gì để kết luận kết quả là đúng?Phần thưởng bằng 30 điểm

Có thể gói gọn trong một câu: "Nếu có ba lần khớp thì phần thưởng tính ra phải là 30 điểm". Thế là đủ thông tin để thảo luận ý nghĩa của bài kiểm thử. Agent sẽ tìm hiểu cách viết mã kiểm tra trong dự án này.

Hãy xác định chính xác điều bạn muốn kiểm chứng. "Tính ra phần thưởng là 30" và "số dư của người dùng tăng lên 30" là hai kết quả mong đợi khác nhau. Để kiểm tra điều thứ hai, cần biết số dư ban đầu và thao tác cộng thưởng như trong bước kiểm tra thủ công ở trên. Vì vậy, hãy thay câu "kiểm tra điểm" bằng kết quả cụ thể có thể quan sát được.

Sau đó, hãy nghĩ đến một vài trường hợp gần kề. Với quy tắc của chúng ta, có thể bắt đầu bằng một bảng nhỏ:

Tình huốngKết quả mong đợiVì sao cần kiểm tra
Ba lần khớpThưởng 30 điểmKiểm tra trường hợp mà việc sửa cách cộng điểm hướng tới
Không có lần khớp nàoThưởng 0 điểmKiểm tra hành vi khi không có kết quả khớp
Năm lần khớpThưởng 50 điểmKiểm tra thêm một giá trị theo cùng quy tắc

Chưa cần nghĩ ra hàng chục trường hợp cùng lúc. Hãy bắt đầu từ kịch bản có vấn đề và vài trường hợp lân cận có thể bị ảnh hưởng bởi thay đổi. Nếu chưa rõ điều gì nên xảy ra, chẳng hạn khi gửi lại đơn, trước tiên hãy xác nhận quy tắc. Agent có thể đề xuất một phương án, nhưng đó chưa phải kết quả mong đợi đã được thống nhất.

Mô tả bước kiểm tra bằng lời trước

Trước khi sửa đổi, hãy nhờ Claude Code liệt kê các tình huống và kết quả mong đợi. Nhìn vào danh sách, bạn sẽ dễ phát hiện agent kiểm tra nhầm thao tác, bỏ sót điều kiện quan trọng hoặc tự đặt ra quy tắc. Sửa vài câu mô tả lúc này đơn giản hơn so với xử lý các bài kiểm thử đã viết cho một nhiệm vụ khác.

Giao việc kiểm tra cho Claude Code

Khi đã mô tả hành vi mong đợi, bạn có thể dựa vào đó để tổ chức công việc. Trước tiên, agent tìm xem dự án đã có những bước kiểm tra nào, rồi đối chiếu chúng với các kịch bản của bạn. Bước kiểm tra cần thiết có thể đã tồn tại; khi đó, hãy chạy nó và đọc kết quả. Nếu chưa có, agent sẽ cần thêm một bài kiểm thử.

Yêu cầu đầu tiên có thể viết như sau:

Hãy sửa cách tính phần thưởng.
Quy tắc: mỗi lần khớp được cộng 10 điểm.
Các trường hợp cần kiểm tra: 3 lần khớp được 30 điểm,
0 lần được 0, 5 lần được 50.

Trước khi sửa, hãy tìm các bước kiểm tra hiện có cho những trường hợp này.
Mô tả ngắn gọn những gì đã được kiểm tra, còn thiếu gì,
và bạn đề xuất thêm hoặc sửa những bài kiểm thử nào.
Nếu yêu cầu có điểm chưa rõ, hãy nêu ra.
Tạm thời đừng sửa tệp.

Hãy đọc câu trả lời như bản mô tả kết quả sẽ có. Các con số trong mọi trường hợp có đúng theo quy tắc của bạn không? Bước kiểm tra xác nhận chính xác số điểm thưởng hay một điều khác? Agent có đề xuất đổi quy tắc để phù hợp với hành vi hiện tại không? Ở bước này, bạn đang làm rõ nhiệm vụ nên chưa cần đọc phần cài đặt của bài kiểm thử.

Sau khi thống nhất các kịch bản, hãy giao việc thực hiện:

Hãy sửa phép tính theo quy tắc đã thống nhất.
Bổ sung các bước kiểm tra còn thiếu cho những trường hợp đã thống nhất,
sử dụng công cụ sẵn có trong dự án.
Đừng sửa kết quả mong đợi hiện có chỉ để bài kiểm thử chạy thành công.
Nếu cho rằng kết quả mong đợi sai, hãy giải thích lý do trước.

Chạy các bài kiểm thử cần thiết theo hướng dẫn của dự án.
Cho tôi biết lệnh, thư mục làm việc và đầu ra thực tế.
Với từng trường hợp trong ba trường hợp, hãy nêu bài kiểm thử nào đã kiểm tra
và kết quả ra sao. Liệt kê riêng các bước bị bỏ qua
và chưa được chạy.

Agent sẽ tìm lệnh chạy trong tài liệu và cấu hình của dự án. Với dự án JavaScript, lệnh này thường là npm test: npm chạy lệnh trong trường scripts.test của tệp package.json. Tên lệnh ấy có thể chạy nhiều nhóm kiểm tra khác nhau. Bạn cần xác nhận lần chạy có bao gồm đúng các kịch bản cần thiết.

Viết bài kiểm thử và chạy bài kiểm thử là hai bước khác nhau. Câu "đã thêm ba bước kiểm tra" chỉ cho biết tệp đã được sửa. Muốn đánh giá kết quả thì cần chạy thực tế. Nếu không thể chạy, chẳng hạn do thiếu dependency, agent cần nói rõ nguyên nhân và những bước kiểm tra chưa thực hiện được.

Trong câu trả lời, hãy tìm mối liên hệ "kịch bản của chúng ta → bài kiểm thử cụ thể → kết quả". Nếu trong ba trường hợp đã thảo luận chỉ có một trường hợp được chạy, thì hiện tại chỉ có trường hợp đó được xác nhận. Dù dòng tổng kết có màu xanh đi nữa.

Cách đọc kết quả

Dưới đây là kết quả chạy thực tế của một bài kiểm thử mẫu trên Node.js 20.18.0 và npm 10.8.2. Bài kiểm thử chỉ kiểm tra phép tính phần thưởng cho ba lần khớp, không kiểm tra toàn bộ các kịch bản nêu trên. Để dễ đọc, phần thời gian và một số dòng phụ của báo cáo đã được lược bỏ:

> reward-example@1.0.0 test
> node --test --test-reporter=tap

# Subtest: three matches give 30 points
ok 1 - three matches give 30 points

# tests 1
# suites 0
# pass 1
# fail 0
# cancelled 0
# skipped 0
# todo 0

Hai dòng đầu cho biết package và lệnh đã chạy. Tiếp theo là tên bài kiểm thử. Dòng ok 1 có nghĩa là bài kiểm thử số 1 đã qua; nếu thất bại, dòng này bắt đầu bằng not ok. Các dòng pass 1 và fail 0 cho biết một bài kiểm thử đã qua, không có bài nào thất bại. Đây được gọi là kết quả "xanh", dù terminal không tô màu văn bản.

Trong trường hợp có lỗi, phần thưởng cho ba lần khớp chỉ là 3 điểm, trong khi quy tắc yêu cầu 30 điểm. Bài kiểm thử đó sẽ đưa ra đoạn đầu ra như sau; đường dẫn, stack trace, thời gian và một số dòng phụ đã được lược bỏ:

not ok 1 - three matches give 30 points
  error: |-
    Expected values to be strictly equal:

    3 !== 30

  code: 'ERR_ASSERTION'
  name: 'AssertionError'
  expected: 30
  actual: 3
  operator: 'strictEqual'

# tests 1
# pass 0
# fail 1
# cancelled 0
# skipped 0
# todo 0

Trước tiên, hãy tìm tên bài kiểm thử, rồi so sánh expected với actual. Từ thứ nhất nghĩa là "mong đợi", từ thứ hai là "thực tế". Ở đây, kết quả mong đợi là 30, còn thực tế là 3. Dòng fail 1 tổng kết: có một bài kiểm thử thất bại.

Kết quả thất bại như vậy có nghĩa là giá trị thực tế không khớp với kết quả mong đợi đã ghi. Tiếp theo, cần tìm nguyên nhân: lỗi trong phép tính, dữ liệu ban đầu không đúng hay chính kết quả mong đợi trong bài kiểm thử bị sai. Đừng đổi con số trong bước kiểm tra chỉ để báo cáo thành công; trước hết hãy đối chiếu với quy tắc của nhiệm vụ.

Cũng có thể thất bại vì lý do khác, chẳng hạn bài kiểm thử không tải được tệp chứa hàm. Khi đó, việc so sánh điểm còn chưa diễn ra và báo cáo thậm chí có thể không nêu tên bài kiểm thử cần tìm: cả tệp sẽ bị đánh dấu là thất bại. Vì vậy, chỉ dựa vào dòng fail 1 thì chưa thể kết luận "phép tính sai". Hãy xem thông báo phía trên phần tổng kết.

Bài kiểm thử bị bỏ qua

Bài kiểm thử còn có một trạng thái quan trọng khác. Một bước kiểm tra có thể được đánh dấu là bỏ qua; trong Node.js, chẳng hạn, bạn có thể dùng test.skip. Bài kiểm thử đó sẽ không chạy. Tên của nó xuất hiện trong báo cáo không có nghĩa là kịch bản đã được kiểm tra.

Trong một lần chạy riêng của ví dụ, bài kiểm thử bị bỏ qua cho ra dòng và phần tổng kết như sau:

ok 1 - three matches give 30 points # SKIP

# tests 1
# pass 0
# fail 0
# cancelled 0
# skipped 1
# todo 0

Hãy để ý một chi tiết hơi lạ: dòng bắt đầu bằng ok nhưng kết thúc bằng # SKIP. Định dạng này dùng cách đó để đánh dấu bài kiểm thử bị bỏ qua. Không thể chỉ nhìn vào từ đầu tiên; phần tổng kết pass 0 xác nhận rằng không có bài kiểm thử nào đã chạy thành công.

Hãy so sánh ba lần chạy cùng một kịch bản:

Trạng tháipassfailskippedĐiều có thể kết luận
Đã qua100Bước kiểm tra nhận được 30 như mong đợi cho ba lần khớp
Thất bại khi so sánh010Giá trị thực tế không khớp với 30
Bị bỏ qua001Bài kiểm thử này không kiểm tra phép tính

Báo cáo dài hơn còn có những dòng khác. Trong các lần chạy của chúng ta, cancelled và todo đều bằng không. Nếu thấy giá trị khác không, hãy tìm hiểu bài kiểm thử nào bị hủy hoặc được đánh dấu là chưa hoàn tất; đừng tính chúng là đã kiểm tra thành công. Dòng suites nói về các nhóm kiểm thử; ví dụ này không có nhóm nào.

Còn một chi tiết nhỏ nữa. Nếu báo cáo cho biết không có bài kiểm thử nào, việc không thấy lỗi không xác nhận rằng điểm đã được cộng đúng. Bước kiểm tra cần thiết có thể chưa được chạy. Tên kịch bản và số bước kiểm tra đã thực hiện quan trọng hơn ấn tượng chung về màu sắc của terminal.

Kết quả màu xanh có nghĩa gì

Bài kiểm thử trong báo cáo trên chỉ trả lời một câu hỏi hẹp: "Phần thưởng cho ba lần khớp có được tính đúng không?" Nó không kiểm tra trường hợp không có lần khớp nào, gửi lại đơn hay con số trên trang. Việc ta nhắc đến các trường hợp này trong yêu cầu không có nghĩa là chúng đã được kiểm tra.

Để kiểm tra trường hợp không có lần khớp, cần thêm một kịch bản với kết quả mong đợi riêng là 0. Để kiểm tra việc gửi lại đơn, cần mô tả thao tác gửi lại và kết quả mong đợi cho thao tác đó. Bài kiểm thử không tự suy ra các quy tắc này từ mã ở gần. Nó chỉ kiểm tra những gì được ghi trong đó.

Không có bài kiểm thử và bài kiểm thử bị bỏ qua là hai chuyện khác nhau. Bài bị bỏ qua sẽ hiện trong báo cáo dưới dạng skipped. Còn nếu chưa hề viết bước kiểm tra cho một kịch bản nào đó, báo cáo thường không nói gì về kịch bản ấy. Tất cả bài kiểm thử hiện có đều có thể qua, dù hành vi chưa được kiểm tra vẫn có lỗi.

Cuối cùng, phép so sánh thành công không chứng minh bản thân kết quả mong đợi là đúng. Nếu quy tắc của chúng ta là 30 mà ghi nhầm 3, và phép tính cho ra 3, bài kiểm thử vẫn qua. Vì vậy, khi thảo luận về bước kiểm tra, hãy trả lời hai câu hỏi: bài kiểm thử xác nhận hành vi nào và kết quả mong đợi được lấy từ đâu?

Câu "các bài kiểm thử đã qua" hữu ích khi bạn hiểu giới hạn của nó. Câu này cho biết các bước kiểm tra cụ thể đã chạy thành công trong một lần chạy cụ thể. Nó không có nghĩa là "ứng dụng không còn lỗi nào".