Mở đầu — vì sao bài này quan trọng
Hãy hình dung bạn vừa viết xong một User Story rất đẹp: "Là khách hàng, tôi muốn đăng nhập bằng số điện thoại để tiện theo dõi đơn hàng." Story rõ ràng, ai đọc cũng gật gù. Nhưng đến sprint review, lập trình viên giao ra một màn hình đăng nhập, QA thử và phát hiện: không có giới hạn số lần nhập sai, không có thông báo khi số điện thoại chưa đăng ký, OTP không hết hạn. Lập trình viên nói "anh có yêu cầu mấy cái đó đâu". QA nói "không có thì sao test". Và bạn — BA — đứng giữa, nhận ra rằng Story chỉ nói cái gì người dùng muốn, chứ chưa nói khi nào thì coi là làm xong.
Đó chính là khoảng trống mà Acceptance Criteria (AC) — tiêu chí chấp nhận — lấp đầy. Nếu User Story là lời hứa, thì AC là hợp đồng. AC định nghĩa ranh giới chính xác giữa "đã hoàn thành" và "chưa hoàn thành", giữa "đúng yêu cầu" và "thiếu yêu cầu". Trong một dự án phần mềm, phần lớn tranh cãi giữa BA, Dev và QA không đến từ chuyện ai giỏi hơn ai, mà đến từ việc không ai viết rõ tiêu chí nghiệm thu trước khi code.
Trong bài này, chúng ta tập trung vào hai phong cách viết AC phổ biến nhất trong nghề BA: Gherkin (Given–When–Then) và checklist (danh sách kiểm). Bạn sẽ học cách viết, khi nào dùng cái nào, và cách tránh những lỗi khiến AC trở nên vô dụng. Lưu ý: bài này không bàn về cách viết toàn bộ User Story (đã có bài riêng) hay viết toàn bộ Use Case spec — chúng ta chỉ đào sâu vào phần tiêu chí chấp nhận.
Khái niệm cốt lõi
Acceptance Criteria là gì?
Acceptance Criteria là tập hợp các điều kiện mà một tính năng (hoặc User Story) phải thỏa mãn để được các bên liên quan chấp nhận là "hoàn thành". Nó trả lời câu hỏi: "Làm thế nào để chúng ta biết tính năng này đã đúng?"
Một AC tốt có những đặc tính sau:
- Có thể kiểm chứng được (testable): mỗi tiêu chí phải dẫn tới một kết quả đúng/sai rõ ràng. "Hệ thống phải nhanh" thì không kiểm chứng được; "Trang tải xong trong vòng 3 giây với mạng 4G" thì kiểm chứng được.
- Tập trung vào cái gì, không phải làm thế nào: AC mô tả hành vi mong đợi, không mô tả giải pháp kỹ thuật. "Mật khẩu phải được mã hóa bằng bcrypt" là chi tiết kỹ thuật, không nên nằm trong AC; "Mật khẩu không được hiển thị dưới dạng văn bản thuần ở bất kỳ đâu" mới là AC.
- Độc lập với nhau: mỗi tiêu chí đứng riêng, không phụ thuộc tiêu chí khác để hiểu.
- Bao phủ cả luồng thuận và luồng nghịch: không chỉ "khi đúng thì sao" mà còn "khi sai, khi thiếu, khi quá tải thì sao".
Phong cách 1 — Gherkin (Given–When–Then)
Gherkin là một cú pháp viết AC theo kịch bản, dùng ba từ khóa chính:
- Given (Cho trước): bối cảnh, trạng thái ban đầu trước khi hành động xảy ra.
- When (Khi): hành động kích hoạt mà người dùng hoặc hệ thống thực hiện.
- Then (Thì): kết quả mong đợi sau hành động.
Scenario: Xác minh CMND/CCCD thành công ở bước 2
Given người dùng đang ở bước 2 của quy trình KYC
And người dùng đã tải lên ảnh CCCD hợp lệ
And ảnh CCCD rõ nét, không bị che góc
When người dùng nhấn nút "Tiếp tục"
Then hệ thống chuyển sang bước 3 (chụp ảnh khuôn mặt)
And trạng thái hồ sơ được cập nhật thành "Đang xác minh"
Scenario: Ảnh CCCD bị mờ
Given người dùng đang ở bước 2 của quy trình KYC
And người dùng đã tải lên ảnh CCCD bị mờ
When người dùng nhấn nút "Tiếp tục"
Then hệ thống hiển thị thông báo "Ảnh chưa đủ rõ, vui lòng chụp lại"
And người dùng vẫn ở bước 2
Điểm mạnh của Gherkin: nó là ngôn ngữ vừa con người vừa máy đọc được. Cú pháp này chính là nền tảng của BDD (Behavior-Driven Development) và các công cụ như Cucumber, SpecFlow. QA có thể biến mỗi Scenario thành một bài test tự động gần như nguyên văn. Vì viết theo kịch bản cụ thể nên Gherkin cực kỳ phù hợp với các tính năng có logic luồng — nhiều bước, nhiều rẽ nhánh, nhiều trạng thái.
Phong cách 2 — Checklist (danh sách điều kiện)
Checklist là cách viết AC dưới dạng danh sách các điều kiện rời rạc cần thỏa mãn, thường ở dạng câu khẳng định hoặc gạch đầu dòng. Ví dụ cho cùng tính năng đăng nhập bằng số điện thoại:
Acceptance Criteria — Đăng nhập bằng OTP:
- [ ] Người dùng nhập số điện thoại hợp lệ (10 số, đầu 0) thì nhận được OTP qua SMS trong vòng 30 giây.
- [ ] OTP gồm 6 chữ số và hết hạn sau 5 phút.
- [ ] Nhập sai OTP 5 lần liên tiếp thì khóa tính năng gửi OTP trong 15 phút.
- [ ] Số điện thoại chưa đăng ký thì hiển thị "Số điện thoại chưa có tài khoản, bạn có muốn đăng ký?".
- [ ] Mã OTP không bao giờ hiển thị trong log hệ thống.
Checklist nhanh, gọn, dễ liếc qua để biết "còn thiếu gì". Nó phù hợp với những tính năng có nhiều ràng buộc dạng quy tắc nhưng không có luồng phức tạp — ví dụ các quy tắc validate dữ liệu, các yêu cầu phi chức năng (hiệu năng, bảo mật, hiển thị).
Khi nào dùng cái nào?
| Tình huống | Nên dùng |
|---|---|
| Tính năng có nhiều bước, nhiều rẽ nhánh, nhiều trạng thái | Gherkin |
| Cần test tự động hóa theo BDD | Gherkin |
| Tập hợp các quy tắc validate, ràng buộc, yêu cầu phi chức năng | Checklist |
| Story nhỏ, đơn giản, cần viết nhanh | Checklist |
| Muốn cả hai: kịch bản chính bằng Gherkin, ràng buộc phụ bằng checklist | Kết hợp |
Tình huống thực tế
Ví dụ 1 — Ví điện tử MoMo-style: nạp tiền từ ngân hàng liên kết
Bối cảnh: Một đội sản phẩm fintech tại TP.HCM phát triển tính năng "Nạp tiền từ ngân hàng liên kết". BA ban đầu chỉ viết Story "Là người dùng, tôi muốn nạp tiền vào ví từ tài khoản ngân hàng đã liên kết". Đến sprint, Dev làm xong luồng thuận, nhưng khi giao dịch bị treo (ngân hàng phản hồi chậm), app hiển thị "Thành công" trong khi tiền chưa về ví. Một khách hàng nạp 2.000.000đ, app báo thành công, số dư không tăng — và họ lên Fanpage phàn nàn.
BA viết lại AC bằng Gherkin, đặc biệt chú ý luồng nghịch:
Scenario: Ngân hàng phản hồi chậm hoặc timeout
Given người dùng nạp 2.000.000đ từ ngân hàng đã liên kết
When hệ thống không nhận được xác nhận từ ngân hàng trong 60 giây
Then trạng thái giao dịch hiển thị "Đang xử lý", KHÔNG hiển thị "Thành công"
And người dùng nhận thông báo "Giao dịch đang được xử lý, số dư sẽ cập nhật trong vài phút"
And số dư ví chỉ tăng sau khi nhận xác nhận thành công từ ngân hàng
Diễn giải: chính từ khóa Then buộc cả đội phải nghĩ tới trạng thái trung gian "Đang xử lý" — thứ mà Story ban đầu hoàn toàn bỏ sót.
Bài học: AC không phải để mô tả lại Story bằng từ khác. Giá trị thật của AC nằm ở chỗ nó ép bạn nghĩ tới các trạng thái bất thường — timeout, lỗi mạng, dữ liệu thiếu — trước khi Dev viết dòng code đầu tiên.
Ví dụ 2 — Sàn TMĐT: áp mã giảm giá khi thanh toán
Bối cảnh: Một sàn thương mại điện tử kiểu Tiki/Shopee triển khai tính năng áp voucher. Đây là tính năng đầy ràng buộc: voucher có giá trị tối thiểu đơn hàng, có hạn dùng, có giới hạn số lần, không cộng dồn với voucher khác, chỉ áp cho một số danh mục. Nếu viết toàn bộ bằng Gherkin sẽ ra hàng chục Scenario rối rắm. BA ở đây chọn cách kết hợp.
Checklist cho các ràng buộc:
- [ ] Voucher chỉ áp được khi đơn hàng đạt giá trị tối thiểu ghi trên voucher.
- [ ] Voucher hết hạn thì hiển thị "Mã đã hết hạn" và không trừ tiền.
- [ ] Mỗi tài khoản chỉ dùng voucher tối đa số lần quy định.
- [ ] Không áp đồng thời 2 voucher cùng loại "giảm theo %".
- [ ] Voucher chỉ áp cho sản phẩm thuộc danh mục được chỉ định.
Và một Scenario Gherkin cho luồng chính:
Scenario: Áp voucher giảm 50K cho đơn đủ điều kiện
Given giỏ hàng có tổng giá trị 350.000đ
And voucher "GIAM50K" yêu cầu đơn tối thiểu 300.000đ và còn hiệu lực
When người dùng nhập mã "GIAM50K"
Then tổng thanh toán giảm còn 300.000đ
And dòng "Giảm giá voucher: -50.000đ" hiển thị trong tóm tắt đơn
Diễn giải: checklist xử lý gọn các quy tắc, còn Gherkin minh họa rõ trải nghiệm của một lần áp mã thành công. QA dựa vào cả hai để thiết kế bộ test.
Bài học: đừng cố nhồi mọi thứ vào một phong cách. Quy tắc rời rạc thì dùng checklist; hành trình người dùng thì dùng Gherkin. Sự kết hợp khôn ngoan giúp AC vừa đầy đủ vừa dễ đọc.
Ví dụ 3 — Ngân hàng số: chuyển khoản với hạn mức ngày
Bối cảnh: Một ngân hàng số tại Việt Nam ra tính năng chuyển khoản nhanh 24/7. Quy định Ngân hàng Nhà nước yêu cầu giao dịch trên 10 triệu phải xác thực bằng sinh trắc học (khuôn mặt). BA cần AC để đảm bảo tuân thủ. Đây là tình huống mà một dòng AC sai có thể dẫn tới rủi ro pháp lý.
Scenario: Chuyển khoản trên ngưỡng yêu cầu sinh trắc học
Given người dùng đã đăng nhập và có đủ số dư
And tổng giao dịch trong ngày trước đó là 5.000.000đ
When người dùng yêu cầu chuyển 8.000.000đ
Then hệ thống yêu cầu xác thực khuôn mặt trước khi thực hiện
And giao dịch chỉ hoàn tất sau khi xác thực sinh trắc học thành công
Lưu ý con số: tổng ngày trước là 5 triệu, cộng giao dịch mới 8 triệu thành 13 triệu — vượt 10 triệu, nên kích hoạt sinh trắc học. AC này buộc đội làm rõ rằng ngưỡng tính trên tổng lũy kế trong ngày, không phải trên từng giao dịch đơn lẻ — một chi tiết dễ hiểu nhầm.
Bài học: với các tính năng liên quan tuân thủ pháp lý hoặc tài chính, AC viết theo Gherkin với số liệu cụ thể giúp lộ ra các giả định ngầm về cách tính toán. Một con số trong Given/When làm sáng tỏ logic mà mười câu mô tả chung chung không làm được.
Hướng dẫn từng bước
Đây là quy trình viết AC mà bạn có thể áp dụng cho bất kỳ Story nào:
- Đọc lại Story và xác định giá trị cốt lõi. Hỏi: "Khi nào người dùng sẽ thật sự hài lòng với tính năng này?" Câu trả lời chính là luồng thuận (happy path) đầu tiên cần viết.
- Viết Scenario cho luồng thuận trước. Một Given (bối cảnh), một When (hành động), một Then (kết quả). Giữ nó đơn giản, đúng một mục đích.
- Liệt kê các luồng nghịch và biên (edge cases). Tự hỏi: dữ liệu sai thì sao? Thiếu quyền thì sao? Mạng lỗi, timeout thì sao? Vượt giới hạn thì sao? Mỗi câu trả lời là một Scenario hoặc một dòng checklist.
- Quyết định phong cách cho từng phần. Phần nào là kịch bản có bước → Gherkin. Phần nào là quy tắc/ràng buộc rời → checklist.
- Kiểm tra tính testable. Đọc lại từng tiêu chí và hỏi: "QA có thể biến dòng này thành một bài test cho ra đúng/sai không?" Nếu không, hãy viết lại cụ thể hơn (thêm con số, thêm thông báo cụ thể).
- Rà soát cùng Dev và QA. AC là tài liệu chung, không phải của riêng BA. Đưa cho Dev và QA xem trước khi sprint bắt đầu — đây là lúc rẻ nhất để phát hiện thiếu sót.
- Gắn AC vào Story trong công cụ quản lý. Đặt AC ngay trong phần mô tả của Story trên Jira/Azure DevOps để không ai phải đi tìm.
Lỗi thường gặp & mẹo
Lỗi 1 — AC chỉ chép lại Story. Viết "Người dùng đăng nhập được" rồi coi đó là AC thì vô nghĩa. AC phải cụ thể hơn Story một bậc. Mẹo: nếu AC của bạn đọc lên nghe giống Story, nghĩa là bạn chưa viết AC thật.
Lỗi 2 — Quên luồng nghịch. Đây là lỗi phổ biến nhất. Người mới chỉ viết happy path. Mẹo: với mỗi When, hãy hỏi "còn cách nào để cái này thất bại không?" và viết một Scenario cho mỗi cách thất bại quan trọng.
Lỗi 3 — Nhét chi tiết kỹ thuật vào AC. "Lưu vào bảng transactions với cột status='pending'" là việc của Dev, không phải AC. Mẹo: AC nói về hành vi quan sát được từ bên ngoài, không nói về cài đặt bên trong.
Lỗi 4 — Một Scenario làm quá nhiều việc. Khi bạn thấy "When... And người dùng cũng làm... And rồi làm tiếp...", hãy tách thành nhiều Scenario. Mỗi Scenario chỉ kiểm một hành vi.
Lỗi 5 — AC mơ hồ, không kiểm chứng được. "Hệ thống phản hồi nhanh", "giao diện thân thiện" — không ai test được. Mẹo: thay tính từ bằng con số. "Nhanh" → "dưới 2 giây"; "nhiều" → "tối đa 5 lần".
Lỗi 6 — Viết AC sau khi code xong. AC viết sau chỉ để hợp thức hóa thứ đã làm, mất hết giá trị định hướng. Mẹo: AC phải xong trước khi Story vào sprint — đây cũng là một tiêu chí trong "Definition of Ready".
Mẹo vàng — dùng AC làm cầu nối ba bên. AC viết tốt là điểm gặp của BA (hiểu nghiệp vụ), Dev (biết phải làm gì) và QA (biết test gì). Buổi "Three Amigos" — BA, Dev, QA cùng ngồi review AC trước sprint — là một trong những thực hành đáng giá nhất bạn có thể đưa vào đội.
Bài tập thực hành
Bài 1 — Viết Gherkin. Cho Story: "Là người dùng đặt xe công nghệ, tôi muốn hủy chuyến trước khi tài xế đến để không bị tính phí oan." Hãy viết tối thiểu 3 Scenario Gherkin: (a) hủy trong thời gian miễn phí, (b) hủy sau thời gian miễn phí (bị tính phí hủy), (c) hủy khi tài xế đã đến điểm đón. Nhớ ghi rõ con số (ví dụ: miễn phí trong 5 phút đầu).
Bài 2 — Viết checklist. Cho tính năng "Đổi mật khẩu". Viết một checklist AC bao phủ ít nhất 6 ràng buộc: độ dài tối thiểu, yêu cầu ký tự, không trùng mật khẩu cũ, xác nhận mật khẩu mới, thông báo khi sai, và một yêu cầu bảo mật.
Bài 3 — Chọn phong cách. Cho ba tính năng: (1) quy trình thanh toán 3 bước, (2) các quy tắc validate form đăng ký, (3) luồng quên mật khẩu qua email. Với mỗi tính năng, hãy quyết định nên dùng Gherkin, checklist, hay kết hợp — và giải thích lý do trong 1-2 câu.
Bài 4 — Sửa AC tệ. AC sau đây tệ ở đâu, sửa lại thế nào: "Hệ thống phải xử lý đơn hàng nhanh và hiển thị kết quả tốt cho người dùng." Viết lại thành ít nhất 2 tiêu chí kiểm chứng được.
Tóm tắt
Acceptance Criteria là ranh giới rõ ràng giữa "xong" và "chưa xong" — nó biến lời hứa trong User Story thành hợp đồng có thể kiểm chứng. Hai phong cách chủ đạo là Gherkin (Given–When–Then), phù hợp cho các luồng có bước và rẽ nhánh, dễ chuyển thành test tự động theo BDD; và checklist, phù hợp cho tập hợp các quy tắc, ràng buộc và yêu cầu phi chức năng. BA giỏi thường kết hợp cả hai.
Giá trị thật của AC không nằm ở chỗ mô tả lại tính năng, mà ở chỗ nó ép bạn nghĩ tới luồng nghịch và các trạng thái biên trước khi viết code. Một AC tốt phải testable, tập trung vào hành vi quan sát được (không phải chi tiết kỹ thuật), và được BA–Dev–QA cùng rà soát trước khi sprint bắt đầu. Hãy viết AC trước, viết cụ thể với con số, và biến nó thành điểm gặp của cả ba bên — đó là cách bạn ngăn những tranh cãi "anh đâu có yêu cầu cái đó" ngay từ gốc.