Mở đầu — vì sao bài này quan trọng
Hãy tưởng tượng bạn vừa viết một dòng acceptance criteria thế này: "Người dùng có thể thêm sản phẩm vào giỏ hàng từ livestream." Nghe có vẻ rõ ràng đúng không? Nhưng khi bạn đưa cho ba người khác nhau đọc — một developer, một QA, một designer — họ sẽ hình dung ra ba thứ khác nhau. Developer băn khoăn: thêm xong thì giỏ hàng cập nhật ngay hay đợi reload? QA hỏi: nếu sản phẩm hết hàng giữa lúc đang xem live thì sao? Designer thắc mắc: bấm "Thêm" xong người dùng có bị đưa ra khỏi màn hình live không?
Một câu mô tả mơ hồ như vậy sẽ sinh ra hàng loạt cuộc họp, hàng loạt tin nhắn "anh ơi cái này ý là sao", và tệ nhất là sản phẩm ship ra không đúng ý. Đây chính là lý do tồn tại của Gherkin — một cú pháp viết acceptance criteria (tiêu chí nghiệm thu) theo cấu trúc Given–When–Then. Gherkin không phải là phát minh để làm khó PM; nó là ngôn ngữ chung giúp PM, dev, QA và cả người không kỹ thuật cùng đọc một câu và hiểu giống hệt nhau.
Ở Bài 4, bạn đã học user stories và acceptance criteria ở mức khái niệm. Bài này đi sâu vào một định dạng cụ thể, có kỷ luật để viết acceptance criteria sao cho không thể hiểu nhầm, và quan trọng hơn — viết để QA có thể biến thẳng thành test case. Đây là kỹ năng phân biệt một PM viết spec "tàm tạm" với một PM mà cả team tin tưởng.
Khái niệm cốt lõi
Gherkin là gì
Gherkin là một cú pháp gần với ngôn ngữ tự nhiên, dùng để mô tả hành vi mong muốn của hệ thống theo từng kịch bản (scenario). Nó ra đời từ phong trào BDD (Behavior-Driven Development — phát triển dựa trên hành vi) và được dùng phổ biến trong các công cụ như Cucumber. Nhưng bạn không cần biết Cucumber để dùng Gherkin: rất nhiều PM dùng nó thuần túy như một cách viết acceptance criteria trong PRD.
Cấu trúc xương sống của Gherkin chỉ có ba từ khóa:
Given [tiền điều kiện — bối cảnh trước khi hành động]
When [hành động — điều người dùng/hệ thống làm]
Then [kết quả mong đợi — điều phải xảy ra sau đó]
- Given: trạng thái hệ thống trước khi sự việc xảy ra. Ví dụ: "người dùng đã đăng nhập", "giỏ hàng đang trống", "sản phẩm còn 2 cái trong kho".
- When: sự kiện kích hoạt — thường là một hành động duy nhất của người dùng. Ví dụ: "người dùng bấm nút Thêm vào giỏ".
- Then: kết quả có thể quan sát được. Ví dụ: "giỏ hàng hiển thị số lượng là 1", "hệ thống hiện toast 'Đã thêm vào giỏ'".
And, But và cách nối nhiều điều kiện
Khi một bước cần nhiều điều kiện, bạn dùng And (và) hoặc But (nhưng) để nối, thay vì lặp lại Given/When/Then:
Given người dùng đã đăng nhập
And giỏ hàng đang trống
When người dùng bấm "Thêm vào giỏ" trên sản phẩm A
Then giỏ hàng hiển thị số lượng 1
And badge giỏ hàng đổi sang màu đỏ
But người dùng vẫn ở lại màn hình livestream
Đọc lên gần như một câu tiếng Việt mạch lạc. Đó chính là sức mạnh: ai cũng đọc được, nhưng cấu trúc lại đủ chặt để không mơ hồ.
Scenario và Scenario Outline
Mỗi kịch bản cụ thể được gói trong một Scenario có tên. Tên scenario nên mô tả tình huống, không phải mô tả tính năng:
Scenario: Thêm sản phẩm còn hàng vào giỏ từ livestream
Scenario: Thêm sản phẩm vừa hết hàng vào giỏ từ livestream
Scenario: Thêm sản phẩm khi chưa đăng nhập
Khi nhiều scenario chỉ khác nhau ở dữ liệu đầu vào, bạn dùng Scenario Outline kèm bảng Examples để tránh lặp:
Scenario Outline: Áp mã giảm giá theo giá trị đơn hàng
Given giỏ hàng có tổng giá trị <tong_tien>
When người dùng nhập mã "<ma>"
Then hệ thống áp giảm <giam> đồng Examples:
tong_tien ma giam
500000 FREESHIP 25000
200000 FREESHIP
1000000 SALE10 100000
Một dòng spec gọn gàng thay cho ba scenario riêng lẻ. QA nhìn vào bảng này là viết được ngay ba test case.
Tại sao một AC tốt phải "kiểm chứng được"
Nguyên tắc vàng: mỗi mệnh đề Then phải mô tả thứ quan sát được hoặc đo được. "Hệ thống xử lý đúng" không phải AC — vì không ai biết "đúng" trông như thế nào. "Hệ thống hiển thị thông báo 'Sản phẩm đã hết hàng' và nút Thêm chuyển sang trạng thái mờ" mới là AC — vì QA mở app lên là nhìn thấy được, đúng thì pass, sai thì fail. Gherkin ép bạn viết theo kiểu này một cách tự nhiên.
Tình huống thực tế
Ví dụ 1 — Shopee Live: "Add to cart from live"
Giả sử bạn là PM của tính năng mua hàng trong livestream trên một sàn TMĐT kiểu Shopee. Người dùng đang xem live, người bán ghim một sản phẩm, và người xem có thể bấm thêm thẳng vào giỏ mà không rời khỏi luồng video. Yêu cầu ban đầu từ stakeholder chỉ vỏn vẹn: "Cho phép thêm sản phẩm vào giỏ từ live."
Nếu để nguyên như vậy, team sẽ ship một phiên bản hời hợt. Hãy xem cách biến nó thành Gherkin đầy đủ:
Scenario: Thêm sản phẩm còn hàng vào giỏ mà không rời live
Given người dùng đã đăng nhập
And đang xem một phiên livestream
And sản phẩm được ghim còn ít nhất 1 sản phẩm trong kho
When người dùng bấm "Thêm vào giỏ" trên thẻ sản phẩm ghim
Then sản phẩm được thêm vào giỏ với số lượng 1
And badge giỏ hàng tăng thêm 1
And video livestream tiếp tục phát không bị gián đoạn
And hệ thống hiện toast "Đã thêm vào giỏ" trong 2 giâyScenario: Thêm sản phẩm vừa hết hàng giữa lúc live
Given người dùng đang xem livestream
And sản phẩm được ghim đã hết hàng sau lần làm mới gần nhất
When người dùng bấm "Thêm vào giỏ"
Then hệ thống hiện thông báo "Sản phẩm đã hết hàng"
And nút "Thêm vào giỏ" chuyển sang trạng thái vô hiệu hóa
And giỏ hàng không thay đổi
Scenario: Thêm sản phẩm khi chưa đăng nhập
Given người dùng chưa đăng nhập
And đang xem livestream
When người dùng bấm "Thêm vào giỏ"
Then hệ thống mở màn hình đăng nhập dưới dạng bottom sheet
And livestream vẫn phát ở chế độ thu nhỏ phía sau
And sau khi đăng nhập thành công, sản phẩm tự động được thêm vào giỏ
Diễn giải: Chỉ với một yêu cầu một dòng, ta đã khai mở ra ba kịch bản: đường hạnh phúc (happy path), trạng thái hết hàng (một edge case kinh điển trong live vì kho biến động theo giây), và trạng thái chưa đăng nhập. Điều then chốt là chi tiết "video livestream tiếp tục phát không bị gián đoạn" — nếu không viết ra, rất có thể dev sẽ làm nút Thêm điều hướng người dùng sang trang giỏ hàng, phá vỡ toàn bộ trải nghiệm mua hàng trong live.
Bài học rút ra: Gherkin buộc bạn nghĩ tới bối cảnh (Given) trước khi nghĩ tới kết quả. Chính bước viết Given "sản phẩm đã hết hàng giữa live" mới làm lộ ra một kịch bản mà yêu cầu gốc hoàn toàn bỏ qua.
Ví dụ 2 — MoMo: chuyển tiền với số dư không đủ
Đổi sang bối cảnh ví điện tử kiểu MoMo. Bạn là PM của luồng chuyển tiền P2P (người gửi người). Đây là tính năng tiền-thật, nên acceptance criteria phải cực kỳ chặt — sai một ly là mất tiền của người dùng.
Scenario Outline: Chuyển tiền với các mức số dư khác nhau
Given người dùng có số dư <so_du> đồng
And đã liên kết ngân hàng thành công
When người dùng chuyển <so_tien> đồng cho người nhận hợp lệ
Then kết quả giao dịch là "<ket_qua>"
And số dư còn lại là <con_lai> đồng Examples:
so_du so_tien ket_qua con_lai
500000 200000 Thành công 300000
500000 500000 Thành công
100000 200000 Từ chối 100000
50000 Từ chối
Scenario: Chuyển tiền nhưng phiên xác thực OTP hết hạn
Given người dùng đã nhập số tiền và bấm "Chuyển"
And mã OTP đã được gửi 5 phút trước
When người dùng nhập OTP sau khi mã đã hết hạn
Then hệ thống hiện thông báo "Mã OTP đã hết hạn, vui lòng yêu cầu mã mới"
And giao dịch chưa được thực hiện
And số dư của người dùng không thay đổi
Diễn giải: Bảng Examples ở đây là vũ khí mạnh nhất. Bốn dòng phủ kín bốn ranh giới quan trọng: số dư dư, số dư đúng bằng (boundary), số dư thiếu, và số dư bằng 0. Đây là tư duy boundary testing (kiểm thử biên) được nhúng thẳng vào spec. Dòng "con_lai = 100000 khi bị từ chối" tưởng nhỏ nhưng cực quan trọng — nó khẳng định rằng giao dịch thất bại tuyệt đối không được trừ tiền, một bug nguy hiểm trong fintech.
Bài học rút ra: Với feature liên quan đến tiền, bảo mật hay dữ liệu nhạy cảm, mệnh đề Then phải bao gồm cả "điều gì KHÔNG được xảy ra" (số dư không đổi), chứ không chỉ điều xảy ra. Gherkin cho bạn không gian để nói rõ điều đó.
Ví dụ 3 — Tiki: tìm kiếm trả về 0 kết quả
Một edge case mà 90% PM mới quên: empty state của tìm kiếm. Bạn là PM tìm kiếm sản phẩm trên một sàn kiểu Tiki.
Scenario: Tìm kiếm có kết quả
Given người dùng ở màn hình tìm kiếm
When người dùng nhập "tai nghe bluetooth" và bấm Tìm
Then hệ thống hiển thị danh sách sản phẩm khớp
And hiển thị tổng số kết quả ở đầu trangScenario: Tìm kiếm không có kết quả nào
Given người dùng ở màn hình tìm kiếm
When người dùng nhập "asdfghjkl" và bấm Tìm
Then hệ thống hiển thị trạng thái rỗng với thông điệp "Không tìm thấy sản phẩm nào"
And gợi ý 4 từ khóa phổ biến để người dùng thử lại
And không hiển thị lưới sản phẩm trống
Diễn giải: Scenario thứ hai chính là thứ làm nên khác biệt giữa một spec nghiệp dư và chuyên nghiệp. PM thiếu kinh nghiệm chỉ viết happy path. PM giỏi luôn tự hỏi: "Còn khi không có gì để hiển thị thì sao?" Gherkin, với cấu trúc một-scenario-một-tình-huống, khuyến khích bạn liệt kê các nhánh này thành từng khối riêng biệt, dễ rà soát.
Bài học rút ra: Mỗi feature đều có ít nhất ba trạng thái cần spec — có dữ liệu, không có dữ liệu, và lỗi. Gherkin giúp bạn không gộp chúng lại thành một câu mơ hồ.
Hướng dẫn từng bước
Đây là quy trình thực dụng để viết acceptance criteria bằng Gherkin cho một feature bất kỳ.
Bước 1 — Bắt đầu từ user story. Lấy user story dạng "Là [vai trò], tôi muốn [hành động], để [giá trị]". Ví dụ: "Là người mua, tôi muốn thêm sản phẩm vào giỏ từ live, để không bỏ lỡ ưu đãi flash sale."
Bước 2 — Viết happy path trước. Đừng vội nghĩ edge case. Viết một scenario cho luồng thuận lợi nhất, đầy đủ Given–When–Then. Điều này định hình "hành vi mặc định" mà mọi người đồng thuận.
Bước 3 — Liệt kê các nhánh rẽ. Với mỗi Given, tự hỏi: điều kiện này có thể sai khác thế nào? (chưa đăng nhập, hết hàng, mất mạng). Với mỗi When, tự hỏi: hành động có thể thất bại ra sao? Mỗi câu trả lời thường là một scenario mới.
Bước 4 — Gom các scenario giống nhau về dữ liệu thành Scenario Outline. Nếu bạn thấy ba scenario chỉ khác con số, hãy chuyển sang dạng bảng Examples. Code spec gọn hơn, dễ bảo trì hơn.
Bước 5 — Kiểm tra từng Then có "quan sát được" không. Đọc lại mỗi mệnh đề Then và hỏi: "QA mở app lên, họ thấy/đo được điều này không?" Nếu câu trả lời mơ hồ, viết lại cụ thể hơn. Thay "xử lý đúng" bằng "hiển thị thông báo X" hoặc "trả về mã trạng thái Y".
Bước 6 — Đưa cho một dev và một QA đọc thử. Hỏi họ: "Có chỗ nào đọc xong vẫn phải đoán không?" Mỗi câu hỏi của họ là một lỗ hổng trong spec. Vá nó, lặp lại cho đến khi không còn câu hỏi.
Lỗi thường gặp & mẹo
Lỗi 1 — Một scenario nhồi nhiều When. Nếu bạn viết "When người dùng đăng nhập And thêm sản phẩm And thanh toán", bạn đang mô tả cả một quy trình chứ không phải một hành vi. Khi test fail, không ai biết bước nào hỏng. Mẹo: mỗi scenario chỉ nên có một When chính. Tách quy trình dài thành nhiều scenario.
Lỗi 2 — Then mô tả chi tiết kỹ thuật triển khai. Ví dụ: "Then hệ thống gọi API POST /cart và ghi vào bảng cart_items." Đây là cách làm, không phải hành vi quan sát được, và nó trói tay developer. Mẹo: Then nên mô tả thứ người dùng hoặc người kiểm thử nhìn thấy ("giỏ hàng tăng lên 1"), để dev tự do chọn cách triển khai. (Bài 18 sẽ nói sâu hơn về nguyên tắc tả hành vi trước kiến trúc.)
Lỗi 3 — Given chứa hành động thay vì trạng thái. "Given người dùng bấm đăng nhập" là sai — bấm là một hành động, thuộc về When. Given phải là trạng thái tĩnh: "Given người dùng đã đăng nhập". Mẹo: Given thường dùng thì hoàn thành ("đã..."), When dùng thì hiện tại của hành động ("bấm...").
Lỗi 4 — Quên các trạng thái phụ. Chỉ viết happy path. Mẹo: với mỗi feature, ép mình viết tối thiểu ba scenario — thành công, rỗng/không có dữ liệu, và lỗi. (Chi tiết về các state này nằm ở Bài 23.)
Lỗi 5 — Đặt tên scenario theo tính năng, không theo tình huống. "Scenario: Giỏ hàng" vô nghĩa. Mẹo: tên scenario nên đọc như một tình huống cụ thể: "Scenario: Thêm sản phẩm hết hàng vào giỏ". Khi danh sách scenario được liệt kê ra, người đọc lướt qua tên là nắm được độ phủ.
Mẹo bonus — Đừng Gherkin hóa mọi thứ. Gherkin tỏa sáng với các feature có logic phân nhánh rõ (điều kiện, trạng thái, ranh giới). Với những thay đổi thuần thẩm mỹ (đổi màu nút, chỉnh khoảng cách), một câu mô tả thường gọn hơn và phù hợp hơn. Hãy dùng đúng công cụ cho đúng việc.
Bài tập thực hành
Chọn một trong hai đề sau và viết acceptance criteria bằng Gherkin.
Đề A — Đặt món trên app giao đồ ăn (kiểu GrabFood/ShopeeFood). User story: "Là khách, tôi muốn áp mã giảm giá vào đơn hàng để tiết kiệm tiền." Hãy viết tối thiểu:
- 1 Scenario happy path (mã hợp lệ, đơn đủ điều kiện).
- 1 Scenario mã hết hạn.
- 1 Scenario đơn chưa đạt giá trị tối thiểu để áp mã.
- 1 Scenario Outline với bảng Examples cho ít nhất ba mức giá trị đơn hàng khác nhau.
- 1 Scenario happy path.
- 1 Scenario nhập sai OTP 3 lần (hệ thống nên làm gì?).
- 1 Scenario OTP hết hạn.
- 1 Scenario số điện thoại sai định dạng.
Tóm tắt
Gherkin với cấu trúc Given–When–Then là cách viết acceptance criteria có kỷ luật, giúp PM, dev và QA cùng đọc một câu và hiểu giống nhau. Given mô tả trạng thái trước hành động, When mô tả một hành động kích hoạt duy nhất, Then mô tả kết quả quan sát được. Dùng And/But để nối điều kiện, Scenario Outline + Examples để gom các trường hợp chỉ khác nhau ở dữ liệu.
Sức mạnh thật sự của Gherkin không nằm ở cú pháp, mà ở chỗ nó ép bạn tư duy đầy đủ: viết Given làm lộ ra các bối cảnh bạn quên (hết hàng, chưa đăng nhập, mất mạng); viết Then quan sát được ép bạn bỏ những từ mơ hồ như "xử lý đúng". Quy tắc sống còn: mỗi Then phải kiểm chứng được, mỗi scenario một tình huống, và luôn viết tối thiểu ba nhánh — thành công, rỗng, lỗi. Khi spec của bạn được viết kỹ đến mức QA biến thẳng thành test case mà không cần hỏi lại, đó là lúc bạn đã viết acceptance criteria ở đẳng cấp khiến cả team tin tưởng.