Viết test case để người lạ chạy được mà không phải hỏi lại
Xem thửThứ Hai, 9:00 ở Tạp Hóa Cô Tấm. Lan viết một test case cho màn hình đăng nhập của hệ thống quản lý bán hàng. Test case chỉ có hai dòng: các bước là "vào trang đăng nhập, nhập thông tin đúng, bấm đăng nhập", kết quả mong đợi là "đăng nhập thành công". Ba bạn mới là An, Bình và Chi cùng chạy nó trong 15 phút.
An dùng tài khoản của chính mình và thấy trang Tổng quan, nên ghi Đạt. Bình lấy tài khoản mượn của đồng nghiệp đang bị khóa nên thấy thông báo lỗi, ghi Không đạt. Chi nhập đúng email nhưng gõ mật khẩu có khoảng trắng thừa ở cuối, thấy lỗi, rồi gõ lại và ghi Đạt. Một test case, ba lần chạy, hai Đạt và một Không đạt. Bình mở bug báo "không đăng nhập được", và lập trình viên trả lại hai lần với lý do "không tái hiện được". Cả nhóm mất khoảng 90 phút để biết lỗi nằm ở chính test case.
Trước khi đọc tiếp, hãy viết ra giấy: theo bạn, test case hai dòng đó thiếu những thông tin nào khiến ba người chạy ra ba kết quả khác nhau? Hãy kể tên ít nhất bốn thứ thiếu.
Sau bài này, bạn có thể
- Phân biệt test scenario và test case, và giải thích vì sao một scenario sinh ra nhiều test case.
- Viết một test case đủ các trường chuẩn (ID, tiêu đề, điều kiện tiên quyết, các bước, dữ liệu thử, kết quả mong đợi, độ ưu tiên) mà người chưa biết hệ thống vẫn chạy ra cùng kết quả.
- Đặt ID cho test case và truy vết về yêu cầu để biết yêu cầu nào đã có test, yêu cầu nào còn trống.
Cần biết trước: các kỹ thuật chọn dữ liệu thử ở chương 3 (phân vùng tương đương, giá trị biên, bảng quyết định), và cách viết bug report ở chương 2.
Giải đáp: test case của Lan thiếu gì?
Đối chiếu với câu chuyện, ta thấy ít nhất sáu thứ thiếu.
- Điều kiện tiên quyết: tài khoản phải tồn tại và đang hoạt động. Bình dùng tài khoản bị khóa nên kết quả khác.
- Dữ liệu thử cụ thể: "thông tin đúng" là gì? Không ai biết email nào, mật khẩu nào, có khoảng trắng hay không.
- Địa chỉ màn hình: "trang đăng nhập" ở đường dẫn nào, trên môi trường nào, bản build nào.
- Kết quả mong đợi đo được: "đăng nhập thành công" có nghĩa là chuyển tới đường dẫn nào, hiện lời chào nào?
- ID và độ ưu tiên: không có ID thì không thể gắn bug vào test case, không có độ ưu tiên thì không biết chạy trước hay sau.
- Liên kết yêu cầu: không biết test case này kiểm tra yêu cầu nào, nên khi yêu cầu đổi không ai biết sửa test case nào.
Cả sáu thiếu sót cùng một gốc: người viết giả định người chạy biết điều mình biết. Test case là văn bản dành cho người khác (hoặc chính bạn sáu tháng sau), nên nó phải tự đứng được.
Test scenario khác test case ở đâu?
Test scenario (kịch bản kiểm thử) trả lời câu hỏi "cần kiểm tra điều gì?" ở mức ý tưởng. Test case (ca kiểm thử) trả lời "kiểm tra cụ thể bằng dữ liệu nào, làm những bước nào, và kết quả đúng là gì?". Theo từ điển thuật ngữ của ISTQB, một test case gồm điều kiện tiên quyết, đầu vào, hành động, kết quả mong đợi và điều kiện sau khi chạy.
| Tiêu chí | Test scenario | Test case |
|---|---|---|
| Mức chi tiết | Một câu ý tưởng | Đủ bước, dữ liệu, kết quả |
| Ví dụ ở Tạp Hóa Cô Tấm | Kiểm tra khóa tài khoản khi đăng nhập sai nhiều lần | Nhập sai mật khẩu 5 lần liên tiếp với tài khoản an.nguyen@example.com, lần thứ 5 hiện "Tài khoản tạm khóa 15 phút" |
| Số lượng | Ít (vài chục cho một hệ thống nhỏ) | Nhiều (một scenario thường sinh 3 đến 20 test case) |
| Ai đọc | Quản lý, BA, khách hàng | Tester chạy và lập trình viên tái hiện lỗi |
| Khi nào viết | Sớm, ngay khi có yêu cầu | Khi đã rõ giao diện và quy tắc |
Quan hệ giữa chúng là hình phễu: một yêu cầu sinh ra vài scenario, mỗi scenario dùng kỹ thuật thiết kế (phân vùng tương đương, giá trị biên...) để sinh ra nhiều test case với dữ liệu khác nhau. Các bài sau trong chương này đều làm bước cuối: từ một ô nhập liệu, sinh ra bảng test case dùng được ngay.
Đang tải sơ đồ…
Cách đọc hình: đi từ trên xuống. Một yêu cầu (REQ-AUTH-03) sinh một scenario, scenario sinh hai test case. Cả hai test case chạy trên cùng một bản build, so kết quả thực tế với kết quả mong đợi. Khớp thì Đạt. Lệch thì Không đạt và mở bug, bug phải ghi lại cả ID test case lẫn ID yêu cầu để người đọc lần ngược về được.
Mô hình tư duy 1: test case là công thức nấu ăn
Một công thức tốt cho người chưa từng nấu vẫn ra đúng món: có nguyên liệu và lượng cụ thể (dữ liệu thử), có bếp đã sẵn sàng (điều kiện tiên quyết), có các bước theo thứ tự, và có mô tả món hoàn chỉnh trông thế nào (kết quả mong đợi). "Cho muối vừa ăn" là câu tệ nhất của một công thức, cũng như "đăng nhập thành công" là dòng tệ nhất của một test case.
Các trường chuẩn của một test case, chia thành phần soạn trước và phần ghi lúc chạy:
| Trường | Ý nghĩa | Ví dụ (đăng nhập) |
|---|---|---|
| ID | Mã duy nhất, không đổi | TC-LOGIN-001 |
| Tiêu đề | Một câu nói rõ kiểm tra gì | Đăng nhập thành công với tài khoản hợp lệ |
| Điều kiện tiên quyết | Trạng thái cần có trước khi chạy | Tài khoản tồn tại, đang hoạt động, chưa bị khóa |
| Các bước | Hành động đánh số, mỗi dòng một việc | 1. Mở trang /login. 2. Nhập email và mật khẩu. 3. Bấm Đăng nhập |
| Dữ liệu thử | Giá trị cụ thể, không viết "hợp lệ" chung chung | an.nguyen@example.com và Matkhau#2026 |
| Kết quả mong đợi | Quan sát được, đo được | Chuyển tới /dashboard, hiện "Xin chào, Nguyễn Văn An" |
| Độ ưu tiên | Chạy trước hay sau | P1 (cao nhất) đến P3 |
| Yêu cầu liên quan | Truy vết | REQ-AUTH-01 |
| Kết quả thực tế, trạng thái, người chạy, ngày, bản build, mã bug | Điền lúc chạy | Đạt, An, 31/10/2026, build 1.4.2 |
Hai trường hay bị bỏ qua nhất là điều kiện tiên quyết và dữ liệu thử, và đó cũng là hai nguyên nhân chính khiến một test case cho kết quả khác nhau giữa các người chạy.
Mô hình tư duy 2: năm câu hỏi kiểm tra một test case
Trước khi chốt test case, hỏi năm câu. Mỗi câu ứng với một quy tắc viết.
| Quy tắc | Câu hỏi tự kiểm | Dấu hiệu vi phạm | Cách sửa |
|---|---|---|---|
| Một mục đích | Test case này chỉ nên fail vì một lý do? | Tiêu đề có chữ "và": "đăng nhập và đổi mật khẩu và đăng xuất" | Tách thành ba test case |
| Độc lập | Chạy riêng nó có được không? | Bước 1 ghi "dùng tài khoản đã tạo ở TC-LOGIN-005" | Đưa trạng thái đó vào điều kiện tiên quyết |
| Lặp lại được | Hôm nay và tuần sau chạy ra giống nhau? | Dữ liệu "email chưa đăng ký" (lần sau đã đăng ký rồi) | Ghi cách tạo dữ liệu mới mỗi lần, ví dụ thêm hậu tố ngày |
| Kết quả đo được | Người khác nhìn màn hình có phán Đạt hay Không đạt ngay được? | "Hệ thống hoạt động bình thường" | Ghi chính xác văn bản, đường dẫn, giá trị hiển thị |
| Không mơ hồ | Có từ nào hiểu theo hai nghĩa? | "nhập thông tin hợp lệ", "một lúc sau", "mật khẩu mạnh" | Thay bằng dữ liệu cụ thể và con số |
Test case tốt và kém cho đăng nhập nhân viên
Quy tắc nghiệp vụ của màn hình đăng nhập (đây là quy tắc của hệ thống ví dụ Tạp Hóa Cô Tấm, không phải chuẩn bên ngoài):
- Đăng nhập bằng email và mật khẩu. Email không phân biệt hoa thường, mật khẩu phân biệt hoa thường.
- Thành công: chuyển tới
/dashboardvà hiện lời chào "Xin chào" kèm họ tên nhân viên. - Sai email hoặc sai mật khẩu: ở lại trang, hiện đúng một thông báo chung "Email hoặc mật khẩu không đúng".
- Sai 5 lần liên tiếp: khóa tài khoản 15 phút, hiện "Tài khoản tạm khóa 15 phút. Thử lại sau."
- Để trống ô: hiện "Vui lòng nhập email" hoặc "Vui lòng nhập mật khẩu". Email sai định dạng: "Email không hợp lệ".
Bản kém (của Lan lúc đầu):
ID: (trống)
Tiêu đề: Kiểm tra đăng nhập
Các bước: Vào trang đăng nhập, nhập thông tin đúng, bấm đăng nhập
Kết quả mong đợi: Đăng nhập thành công
Bản tốt (cùng ý, viết lại):
ID: TC-LOGIN-001 Yêu cầu: REQ-AUTH-01 Ưu tiên: P1
Tiêu đề: Đăng nhập thành công với tài khoản hợp lệ
Tiên quyết: Tài khoản an.nguyen@example.com tồn tại, đang hoạt động, chưa bị khóa
Các bước:
1. Mở trang /login trên môi trường kiểm thử
2. Nhập email vào ô Email, nhập mật khẩu vào ô Mật khẩu
3. Bấm nút Đăng nhập
Dữ liệu thử: Email = an.nguyen@example.com, Mật khẩu = Matkhau#2026
Kết quả mong đợi: Trình duyệt chuyển tới /dashboard trong 3 giây, đầu trang hiện "Xin chào, Nguyễn Văn An"
Hãy đếm những gì bản tốt có mà bản kém không: ID, yêu cầu, ưu tiên, tiên quyết, đường dẫn, dữ liệu thật, thời hạn 3 giây (đo được) và lời chào cụ thể. Một người chưa từng thấy hệ thống vẫn chạy ra cùng một phán quyết.
Đặt ID và truy vết về yêu cầu
ID có dạng TC-<MODULE>-<SỐ>: ví dụ TC-LOGIN-001, TC-CUST-014, TC-ORDER-007. Ba nguyên tắc: ID duy nhất trong cả dự án, không bao giờ đổi và không dùng lại khi test case bị xóa (để bug cũ không trỏ nhầm sang test case mới), và số thứ tự không mang ý nghĩa nào khác ngoài thứ tự tạo.
Truy vết (traceability) là một bảng ghép yêu cầu với test case:
| Yêu cầu | Nội dung rút gọn | Test case | Độ phủ |
|---|---|---|---|
| REQ-AUTH-01 | Đăng nhập thành công | TC-LOGIN-001, 007, 009 | Có |
| REQ-AUTH-02 | Thông báo lỗi chung khi sai | TC-LOGIN-002, 003, 008 | Có |
| REQ-AUTH-03 | Khóa sau 5 lần sai, mở sau 15 phút | TC-LOGIN-010 đến 013 | Có |
| REQ-AUTH-04 | Ô mật khẩu che ký tự | TC-LOGIN-014 | Có |
| REQ-AUTH-05 | Đăng xuất hủy phiên | (chưa có) | Trống |
Một dòng "Trống" là việc cần làm ngay. Ngược lại, khi REQ-AUTH-03 đổi từ 5 lần thành 3 lần, bạn tra bảng và biết đúng bốn test case cần sửa. Không có bảng này, ta chỉ phát hiện thiếu sót khi bug lọt vào sản xuất.
Bảng test case mẫu: đăng nhập nhân viên
Quy ước chung cho cả bảng: môi trường kiểm thử, mặc định các tài khoản sau đã có. Tài khoản A an.nguyen@example.com mật khẩu Matkhau#2026, đang hoạt động. Tài khoản B binh.tran@example.com đã bị khóa lúc 09:00. Chú thích: Tiên quyết "Ở trang /login" nghĩa là đã mở sẵn trang đăng nhập, chưa nhập gì.
Nhóm 1: đường chính và ô bỏ trống
| ID | Tiêu đề | Tiên quyết | Các bước | Dữ liệu thử | Kết quả mong đợi | Ưu tiên |
|---|---|---|---|---|---|---|
| TC-LOGIN-001 | Đăng nhập thành công | Tài khoản A hoạt động, ở /login | Nhập email, mật khẩu, bấm Đăng nhập | A, Matkhau#2026 | Chuyển tới /dashboard, hiện "Xin chào, Nguyễn Văn An" | P1 |
| TC-LOGIN-002 | Sai mật khẩu | Tài khoản A hoạt động, ở /login | Nhập email đúng, mật khẩu sai, bấm Đăng nhập | A, Sai#12345 | Ở lại /login, hiện "Email hoặc mật khẩu không đúng" | P1 |
| TC-LOGIN-003 | Email không tồn tại | Ở /login | Nhập email lạ, mật khẩu bất kỳ, bấm Đăng nhập | khong.co@example.com, Matkhau#2026 | Cùng thông báo như TC-LOGIN-002, không lộ email có tồn tại hay không | P1 |
| TC-LOGIN-004 | Bỏ trống cả hai ô | Ở /login | Để trống hai ô, bấm Đăng nhập | (trống) | Hiện "Vui lòng nhập email" và "Vui lòng nhập mật khẩu", không gửi yêu cầu | P2 |
| TC-LOGIN-005 | Bỏ trống mật khẩu | Ở /login | Nhập email, để trống mật khẩu, bấm Đăng nhập | A, (trống) | Chỉ hiện "Vui lòng nhập mật khẩu" | P2 |
| TC-LOGIN-006 | Email sai định dạng | Ở /login | Nhập email thiếu tên miền, bấm Đăng nhập | an.nguyen@ | Hiện "Email không hợp lệ" | P2 |
| TC-LOGIN-007 | Email không phân biệt hoa thường | Tài khoản A hoạt động, ở /login | Nhập email viết hoa, mật khẩu đúng | AN.NGUYEN@EXAMPLE.COM, Matkhau#2026 | Đăng nhập thành công như TC-LOGIN-001 | P2 |
Nhóm 2: dữ liệu nhập và khóa tài khoản
| ID | Tiêu đề | Tiên quyết | Các bước | Dữ liệu thử | Kết quả mong đợi | Ưu tiên |
|---|---|---|---|---|---|---|
| TC-LOGIN-008 | Mật khẩu phân biệt hoa thường | Tài khoản A hoạt động, ở /login | Nhập mật khẩu viết thường toàn bộ | A, matkhau#2026 | Hiện "Email hoặc mật khẩu không đúng" | P1 |
| TC-LOGIN-009 | Khoảng trắng thừa ở email | Tài khoản A hoạt động, ở /login | Nhập email có dấu cách đầu và cuối | an.nguyen@example.com có một dấu cách ở đầu và một dấu cách ở cuối, mật khẩu Matkhau#2026 | Đăng nhập thành công (hệ thống cắt khoảng trắng) | P3 |
| TC-LOGIN-010 | Khóa sau 5 lần sai | Tài khoản A hoạt động, chưa sai lần nào | Nhập mật khẩu sai 5 lần liên tiếp | A, Sai#12345 | Lần 1 đến 4 hiện thông báo chung, lần 5 hiện "Tài khoản tạm khóa 15 phút. Thử lại sau." | P1 |
| TC-LOGIN-011 | Mật khẩu đúng khi đang bị khóa | Tài khoản B bị khóa lúc 09:00, giờ hiện tại 09:05 | Nhập email và mật khẩu đúng của B | B, mật khẩu đúng | Vẫn hiện "Tài khoản tạm khóa 15 phút. Thử lại sau." | P1 |
| TC-LOGIN-012 | Tự mở khóa sau 15 phút | Tài khoản B bị khóa lúc 09:00, giờ hiện tại 09:16 | Nhập email và mật khẩu đúng của B | B, mật khẩu đúng | Đăng nhập thành công | P2 |
| TC-LOGIN-013 | Bộ đếm sai được đặt lại | Tài khoản A hoạt động, chưa sai lần nào | Sai 4 lần, đăng nhập đúng, đăng xuất, sai thêm 4 lần | A, sai rồi đúng rồi sai | Không bị khóa ở lần sai thứ 4 của đợt hai | P2 |
| TC-LOGIN-014 | Mật khẩu được che | Ở /login | Gõ vào ô Mật khẩu | Matkhau#2026 | Ô hiển thị dấu chấm, không hiện chữ | P2 |
| TC-LOGIN-015 | Bấm Enter để gửi | Tài khoản A hoạt động, ở /login | Nhập đủ hai ô, nhấn phím Enter | A, Matkhau#2026 | Hành vi giống bấm nút Đăng nhập | P3 |
Vì sao mỗi nhóm cần có
Bảng trên không phải 15 ca ngẫu nhiên. Mỗi nhóm trả lời một câu hỏi khác nhau.
- Đường chính (001, 007, 009): nếu đường này hỏng, không ai làm được việc nào, nên gắn P1. Các biến thể email viết hoa và khoảng trắng thuộc cùng lớp tương đương "đầu vào hợp lệ sau khi chuẩn hóa" nên chỉ cần một ca đại diện cho mỗi biến thể.
- Đường sai (002, 003, 008): kiểm tra cả hai nửa của thông báo chung. Nếu hệ thống báo "email không tồn tại" riêng, kẻ xấu dò được danh sách nhân viên. Đây là đoán lỗi (error guessing) ở chương 3.
- Ô bỏ trống và sai định dạng (004, 005, 006): thuộc nhóm lớp không hợp lệ của phân vùng tương đương, mỗi lớp một ca.
- Khóa tài khoản (010 đến 013): đây là giá trị biên của bộ đếm. Ca 4 lần sai chưa khóa và 5 lần sai thì khóa kiểm đúng hai bên biên.
Đang tải Animation…
Hiểu lầm thường gặp
- "Test case càng dài, càng kỹ." Sai: test case dài thường có nhiều mục đích, fail một chỗ là không biết chỗ nào. Một test case nên fail vì đúng một lý do.
- "Kết quả mong đợi ghi 'hoạt động đúng' là đủ vì ai cũng hiểu." Sai: "đúng" là phán quyết, không phải quan sát. Người chạy không thể đối chiếu với thứ không được ghi.
- "Viết test case sau khi chạy thử cho nhanh." Sai ở chỗ kết quả mong đợi lúc này bị ảnh hưởng bởi kết quả thật, bạn dễ ghi lại đúng điều hệ thống đang làm kể cả khi nó sai.
- "Phải ghi từng cú click." Mức chi tiết phụ thuộc người chạy: người đã quen hệ thống chỉ cần "Đăng nhập bằng tài khoản A", người mới cần đường dẫn và tên nút. Thước đo là: người chưa biết hệ thống có chạy ra cùng kết quả không.
Điều kiện và giới hạn
Test case viết sẵn phù hợp khi cần lặp lại nhiều lần (kiểm thử hồi quy), cần chứng minh độ phủ cho khách hàng, hoặc giao cho nhiều người. Với kiểm thử khám phá (exploratory testing), người kiểm thử vừa học vừa thử nên chỉ ghi mục tiêu phiên và ghi chép lại sau. Hai cách bổ sung cho nhau, không thay thế nhau.
Tự kiểm tra (có đáp án)
Câu 1. Tiêu đề "Kiểm tra thêm khách hàng" là test scenario hay test case? Hãy viết một test case cho nó.
Đáp án: đó là test scenario vì chỉ nêu ý tưởng. Một test case: "Thêm khách hàng thành công với họ tên hợp lệ", tiên quyết đã đăng nhập bằng tài khoản A và mở form Thêm khách hàng, dữ liệu Nguyễn Văn An, 0912345678, kết quả mong đợi là hiện "Đã thêm khách hàng" và dòng mới xuất hiện đầu danh sách.
Câu 2. Test case "Bước 1: dùng khách hàng đã thêm ở TC-CUST-001" vi phạm quy tắc nào và sửa thế nào? Đáp án: vi phạm quy tắc độc lập vì chạy riêng thì không có khách hàng nào. Sửa bằng cách đưa vào điều kiện tiên quyết: "Khách hàng có SĐT 0912345678 đã tồn tại", kèm cách tạo nếu chưa có.
Câu 3. Hôm nay TC-LOGIN-010 đạt, tuần sau yêu cầu đổi thành khóa sau 3 lần sai. Bảng truy vết giúp ích gì?
Đáp án: tra hàng REQ-AUTH-03 thấy ngay các test case 010 đến 013 cần cập nhật (giá trị biên đổi từ 5 sang 3), thay vì rà toàn bộ mười lăm test case của màn hình đăng nhập.
Điều cần nhớ
- Test scenario nói "kiểm tra gì", test case nói "kiểm tra bằng dữ liệu nào, bước nào, kết quả đúng ra sao".
- Kiểm tra mỗi test case bằng năm câu hỏi: một mục đích, độc lập, lặp lại được, kết quả đo được, không mơ hồ.
- ID không đổi, không dùng lại, và luôn gắn với yêu cầu để lần ngược được.
- Các bài tiếp theo áp dụng đúng khuôn này cho từng loại ô nhập liệu, bắt đầu từ chính giao diện.
Nguồn tham khảo
- ISTQB Glossary, mục test case, test scenario, traceability: https://glossary.istqb.org/
- ISTQB, chương trình Foundation Level (nội dung về thiết kế và tài liệu hóa test case): https://www.istqb.org/certifications/certified-tester-foundation-level
- OWASP Authentication Cheat Sheet, phần thông báo lỗi chung khi đăng nhập (dùng làm quy tắc mẫu): https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html