Bản đồ công trìnhTờ này lắp: PRD & spec, Màn hình bếp, Kiểm thử
Bài nàyĐã họcChưa tớiĐã thay thế
Bạn sẽ học được gì
Tiêu chí nghiệm thu
Vì sao cần một “ngôn ngữ chung”?
Khi chị Lan nói “làm chức năng tạo đơn”, mỗi người nghe hiểu một kiểu. Hà hình dung form có ô chữ trên bánh. Anh Tuấn hình dung đơn tự hiện ở bếp. Coding agent hình dung… một form với các trường lấy từ bảng orders và nút “Lưu”. Không ai sai, nhưng cũng không ai giống ai.
User story và tiêu chí chấp nhận giống phiếu đặt bánh chuẩn của Bánh Mộc: thay vì để mỗi người tự hiểu “một cái bánh sinh nhật”, phiếu bắt buộc ghi size, vị, chữ, giờ nhận. Ba bên — bạn, người dùng, AI — cùng đọc một tờ phiếu và cùng biết thế nào là “làm đúng”.
Với agent, có thêm một lý do quan trọng: agent cần cách tự kiểm chứng. Nếu bạn chỉ nói “làm chức năng tạo đơn”, tín hiệu duy nhất để agent biết đã xong là “code trông có vẻ hợp lý” — và bạn trở thành người kiểm tra thủ công mọi thứ. Nếu bạn đưa 8 tiêu chí Given–When–Then, agent có thể viết 8 test, chạy chúng, và đưa bạn bằng chứng là chúng đều qua.
User story: ai, muốn gì, để làm gì
Mẫu user story phổ biến nhất:
Là [vai trò], tôi muốn [hành động/khả năng], để [lợi ích/mục đích].
Ví dụ: Là nhân viên bán hàng online, tôi muốn tạo đơn bánh kem đặt trước với đầy đủ chữ, size, vị và giờ nhận, để bếp làm đúng bánh mà tôi không phải gọi lại kiểm tra.
Ba phần đều quan trọng:
- Vai trò cho biết ai dùng — từ đó suy ra quyền hạn, thiết bị, bối cảnh. “Nhân viên bán hàng online” khác “quản lý chi nhánh”.
- Hành động cho biết cần làm gì — nhưng chưa nói làm bằng giao diện nào.
- Lợi ích cho biết vì sao — và là căn cứ để cắt bớt hoặc chọn giải pháp khác. Nếu không viết được vế “để…”, story đó có thể không cần.
User story không phải spec đầy đủ. Nó là lời hứa về một cuộc trò chuyện: story + tiêu chí chấp nhận đủ để mọi người hiểu giống nhau; chi tiết kỹ thuật (giao diện, dữ liệu, API) nằm trong spec tính năng mà bạn sẽ viết ở bài 03-0603-06.
Kiểm tra chất lượng bằng INVEST
INVEST là bộ sáu tiêu chí cho một user story tốt:
| Chữ cái | Nghĩa | Câu hỏi kiểm tra | Ví dụ vi phạm ở Bánh Mộc |
|---|---|---|---|
| I — Independent | Độc lập | Làm được mà không chờ story khác? | “Tạo đơn” phụ thuộc “báo cáo doanh thu” |
| N — Negotiable | Thương lượng được | Có chừa chỗ cho cách làm khác không? | “Tôi muốn một dropdown 5 lựa chọn màu xanh ở góc phải” |
| V — Valuable | Có giá trị | Người dùng hoặc doanh nghiệp được gì? | “Tôi muốn bảng orders có cột updated_at” (việc kỹ thuật, không phải story) |
| E — Estimable | Ước lượng được | Đủ rõ để ước lượng công sức? | “Tôi muốn hệ thống thông minh hơn” |
| S — Small | Nhỏ | Xong trong vài ngày (hoặc vài phiên agent)? | “Tôi muốn quản lý toàn bộ đơn hàng” |
| T — Testable | Kiểm tra được | Có thể nói đạt/không đạt? | “Tôi muốn form dễ dùng” |
Tiêu chí chấp nhận dạng Given–When–Then
Given–When–Then là cấu trúc mô tả một tình huống kiểm tra, xuất phát từ phương pháp BDD (Behavior-Driven Development — phát triển hướng hành vi) và ngôn ngữ Gherkin:
- Given (cho trước): bối cảnh, trạng thái ban đầu. “Hà đã đăng nhập với vai trò staff tại chi nhánh Phú Nhuận; bây giờ là 10:00 ngày 12/10/2026.”
- When (khi): hành động của người dùng hoặc sự kiện. “Hà tạo đơn bánh kem với giờ nhận 08:00 ngày 13/10/2026.”
- Then (thì): kết quả quan sát được. “Hệ thống từ chối và báo ‘Bánh kem tùy chỉnh cần đặt trước ít nhất 24 giờ’.”
- And (và): nối thêm điều kiện trong cùng một phần.
Bí quyết viết tốt:
- Cụ thể đến từng con số. “Giờ nhận sau 22 giờ” thay vì “giờ nhận quá gần”. Con số làm tiêu chí kiểm tra được và lộ ra câu hỏi (“đúng 24 giờ thì sao?”).
- Mô tả điều quan sát được, không mô tả cách cài đặt. “Thấy mã đơn dạng
BM-261012-xxx” thay vì “hàmcreateOrdertrả về object”. - Một tình huống, một hành vi. Đừng dồn 5 kiểm tra vào một kịch bản.
- Ngôn ngữ nghiệp vụ. Viết sao cho Hà đọc hiểu và xác nhận được.
Một lưu ý về từ khóa: Gherkin hỗ trợ từ khóa ở nhiều ngôn ngữ, nhưng giữ Given/When/Then bằng tiếng Anh, nội dung bằng tiếng Việt là cách dễ đọc cho cả người lẫn agent và công cụ.
Dán user story + kịch bản → kiểm đủ 3 vế ‘Là… tôi muốn… để…’, có Given/When/Then, có con số cụ thể, đếm luồng chính / biên / lỗi; gợi ý câu hỏi săn edge case theo 8 nhóm của bài
Bàn thử cần bật JavaScript.
Kết quả chỉ để tham khảo, không thay cho việc bạn tự duyệt.
Các quy tắc nghiệp vụ dùng trong bài
Trước khi viết story, chị Lan chốt các quy tắc rút ra từ nghiên cứu ở bài 03-0203-02. Các con số là quyết định kinh doanh của Bánh Mộc trong chuỗi bài, bạn sẽ có quy tắc riêng.
| Mã | Quy tắc |
|---|---|
| QT-01 | Đơn có bánh kem tùy chỉnh bắt buộc có: size, vị, giờ nhận; nếu khách muốn chữ thì chữ trên bánh tối đa 40 ký tự, giữ nguyên dấu tiếng Việt. |
| QT-02 | Bánh kem tùy chỉnh phải đặt trước ít nhất 24 giờ. Nhân viên (staff) không tạo được đơn gấp; quản lý (manager) hoặc chủ (owner) được tạo đơn gấp nếu nhập lý do. |
| QT-03 | Giờ nhận trong khung 07:00–21:00 (giờ mở cửa). |
| QT-04 | Đơn có bánh kem tùy chỉnh chỉ được chuyển sang da_xac_nhan khi đã cọc ít nhất 50% tiền bánh (không tính phí giao), làm tròn lên nghìn đồng. |
| QT-05 | Số điện thoại được chuẩn hóa về dạng bắt đầu bằng 0 (di động 10 chữ số, cố định 11 chữ số đầu 02); mỗi số chỉ thuộc một khách. |
| QT-06 | Giá trên dòng đơn lấy từ danh mục sản phẩm; chỉ manager/owner được sửa đơn giá. |
| QT-07 | Bếp nhóm bánh theo chuyến xe: chuyến 07:30 chở bánh có giờ nhận trước 15:00; chuyến 13:00 chở bánh có giờ nhận từ 15:00. |
| QT-08 | staff và manager chỉ xem đơn của chi nhánh mình; owner xem tất cả. Bếp trung tâm xem bánh kem của mọi chi nhánh. |
Bộ user story của Mộc OMS
Dưới đây là 6 user story cho lát 1 của MVP (xem bài 03-0403-04), mỗi story có tiêu chí chấp nhận gồm luồng chính, trường hợp biên và luồng lỗi.
US-01: Tạo đơn bánh kem đặt trước
Là nhân viên bán hàng online (Hà), tôi muốn tạo đơn bánh kem tùy chỉnh với đầy đủ chữ, size, vị, giờ nhận, để bếp làm đúng bánh mà tôi không phải gọi lại kiểm tra.
Scenario: Tạo đơn bánh kem hợp lệ
Given Hà đăng nhập với vai trò staff tại chi nhánh Phú Nhuận
And bây giờ là 10:00 ngày 12/10/2026
And khách có số điện thoại 0901234567 đã tồn tại
When Hà tạo đơn gồm 1 "Bánh kem tùy chỉnh 20cm", vị "Chanh dây", chữ "Mừng sinh nhật Mẹ Ánh"
And giờ nhận 17:00 ngày 13/10/2026, nhận tại chi nhánh, kênh Zalo
Then đơn được tạo với trạng thái "moi" và mã dạng "BM-261012-xxx"
And tổng tiền bằng đơn giá trong danh mục nhân số lượng
And chữ trên bánh được lưu đúng "Mừng sinh nhật Mẹ Ánh", giữ nguyên dấu
Scenario: Thiếu vị bánh
Given Hà đang tạo đơn có "Bánh kem tùy chỉnh 20cm"
When Hà bấm "Tạo đơn" mà chưa chọn vị
Then đơn không được tạo
And ô "Vị" được đánh dấu lỗi với thông báo "Vui lòng chọn vị bánh"
Scenario: Chữ trên bánh quá dài
When Hà nhập chữ trên bánh dài 41 ký tự
Then hệ thống báo "Chữ trên bánh tối đa 40 ký tự" và không cho tạo đơn
Scenario: Nhân viên đặt bánh kem gấp dưới 24 giờ
Given bây giờ là 10:00 ngày 12/10/2026
When Hà (staff) chọn giờ nhận 08:00 ngày 13/10/2026
Then hệ thống báo "Bánh kem tùy chỉnh cần đặt trước ít nhất 24 giờ. Liên hệ quản lý để tạo đơn gấp."
Scenario: Quản lý tạo đơn gấp có lý do
Given anh Tuấn đăng nhập với vai trò manager
When anh Tuấn tạo đơn bánh kem nhận sau 6 giờ và nhập lý do "Khách quen, bếp đã nhận"
Then đơn được tạo và được đánh dấu "Đơn gấp" kèm lý do
Scenario: Bấm "Tạo đơn" hai lần liên tiếp
When Hà bấm "Tạo đơn" hai lần trong vòng 1 giây
Then chỉ có đúng một đơn được tạo
US-02: Tìm khách theo số điện thoại
Là nhân viên nhận đơn, tôi muốn tìm khách bằng số điện thoại, để không tạo trùng khách và biết khách đã từng mua gì.
Scenario: Tìm thấy khách với số có định dạng khác
Given khách "Trần Văn B" có số 0901234567
When Hà nhập "+84 901 234 567" vào ô tìm kiếm
Then hệ thống hiển thị khách "Trần Văn B" và 3 đơn gần nhất
Scenario: Không tìm thấy khách
When Hà nhập "0912 000 111" và không có khách nào trùng
Then hệ thống mở form tạo khách mới với số "0912000111" đã điền sẵn
Scenario: Số điện thoại không hợp lệ
When Hà nhập "09123"
Then hệ thống báo "Số điện thoại cần 10 chữ số, bắt đầu bằng 0"
US-03: Ghi nhận tiền cọc
Là nhân viên nhận đơn, tôi muốn ghi nhận tiền cọc ngay trên đơn, để chi nhánh nhận bánh biết khách còn phải trả bao nhiêu và cuối ngày đối soát nhanh.
Scenario: Cọc đủ 50% thì xác nhận được đơn
Given đơn BM-261012-012 có tiền bánh 600.000đ, nhận tại tiệm (tổng 600.000đ), trạng thái "moi", chưa thanh toán
When Hà ghi nhận 300.000đ chuyển khoản, mã giao dịch "FT2610121234"
Then trạng thái thanh toán là "deposit" và số tiền còn lại hiển thị 300.000đ
And nút "Xác nhận đơn" được bật
Scenario: Cọc chưa đủ 50% với đơn bánh kem
Given đơn bánh kem tiền bánh 600.000đ, giao tận nơi phí 30.000đ (tổng 630.000đ)
When Hà ghi nhận cọc 200.000đ và bấm "Xác nhận đơn"
Then đơn vẫn ở trạng thái "moi"
And hệ thống báo "Đơn bánh kem cần cọc tối thiểu 300.000đ (50% tiền bánh) trước khi xác nhận"
Scenario: Ghi nhận vượt số tiền còn lại
Given đơn tổng 600.000đ đã cọc 300.000đ
When Hà ghi nhận thêm 400.000đ
Then hệ thống báo "Số tiền vượt quá số còn phải thu (300.000đ)"
US-04: Màn hình bếp theo chuyến xe
Là quản lý bếp trung tâm (anh Tuấn), tôi muốn xem danh sách bánh kem cần làm trong ngày, nhóm theo chuyến xe, để phân công thợ và không bánh nào lỡ chuyến.
Scenario: Nhóm bánh theo chuyến xe
Given ngày 13/10/2026 có 3 đơn bánh kem đã xác nhận: nhận 10:00, 14:30 và 17:00
When anh Tuấn mở màn hình bếp ngày 13/10/2026
Then nhóm "Chuyến 07:30" có 2 bánh (10:00 và 14:30), sắp theo giờ nhận
And nhóm "Chuyến 13:00" có 1 bánh (17:00)
And mỗi bánh hiển thị: mã đơn, chi nhánh nhận, size, vị, chữ trên bánh nguyên văn
Scenario: Đơn chưa xác nhận không hiện ở bếp
Given đơn bánh kem nhận ngày 13/10 đang ở trạng thái "moi"
Then đơn đó không xuất hiện trên màn hình bếp
Scenario: Đơn bị hủy sau khi bếp đã thấy
Given đơn BM-261012-011 đang hiển thị ở bếp
When quản lý hủy đơn này
Then lần tải lại kế tiếp, đơn hiển thị gạch ngang với nhãn "ĐÃ HỦY" thay vì biến mất lặng lẽ
US-05: Hủy đơn có lý do
Là quản lý chi nhánh, tôi muốn hủy đơn kèm lý do, để bếp không làm bánh thừa và có dấu vết khi cần tra cứu.
Scenario: Hủy đơn chưa làm
Given đơn ở trạng thái "da_xac_nhan" đã cọc 300.000đ
When quản lý hủy đơn với lý do "Khách đổi ngày, đặt đơn mới"
Then đơn chuyển "da_huy", lịch sử trạng thái ghi người hủy, thời điểm và lý do
And hệ thống nhắc "Đơn đã cọc 300.000đ — xử lý hoàn cọc theo quy định của tiệm"
Scenario: Nhân viên không được hủy đơn bếp đang làm
Given đơn ở trạng thái "dang_lam"
When Hà (staff) mở đơn
Then không có nút "Hủy đơn" và có dòng "Liên hệ quản lý để hủy đơn đang làm"
Scenario: Thiếu lý do hủy
When quản lý bấm "Hủy đơn" mà để trống lý do
Then hệ thống không hủy và báo "Vui lòng nhập lý do hủy"
US-06: Dashboard đơn hôm nay
Là chủ chuỗi (chị Lan), tôi muốn xem số đơn hôm nay theo trạng thái và chi nhánh, để phát hiện sớm chi nhánh đang quá tải hoặc có đơn bị treo.
Scenario: Chủ xem toàn chuỗi
Given hôm nay có 40 đơn ở Phú Nhuận, 35 ở Thảo Điền, 30 ở Gò Vấp
When chị Lan (owner) mở dashboard
Then thấy tổng 105 đơn và bảng số đơn theo trạng thái cho từng chi nhánh
Scenario: Quản lý chỉ thấy chi nhánh mình
When quản lý chi nhánh Gò Vấp mở dashboard
Then chỉ thấy số liệu của Gò Vấp
Scenario: Đơn treo
Given có đơn ở trạng thái "moi" đã quá 2 giờ kể từ khi tạo
Then dashboard hiển thị đơn đó trong mục "Cần xử lý"
Trường hợp biên và luồng lỗi: đi săn có hệ thống
Trường hợp biên (edge case) là tình huống nằm ở rìa của quy tắc: đúng 24 giờ, chuỗi rỗng, 999 đơn trong ngày. Luồng lỗi (error flow) là khi có gì đó hỏng: mất mạng, hai người sửa cùng lúc, người không có quyền cố truy cập. Một bảng câu hỏi gợi ý để đi săn:
| Nhóm | Câu hỏi | Ví dụ ở Mộc OMS |
|---|---|---|
| Ranh giới số | Đúng bằng ngưỡng thì sao? Ngay dưới, ngay trên? | Giờ nhận cách đúng 24 giờ; chữ đúng 40 ký tự; cọc đúng 50% |
| Thời gian | Qua nửa đêm? Múi giờ? Ngày lễ? | Đơn tạo lúc 23:59 và 00:01 có mã khác ngày; lưu timestamptz, hiển thị giờ Việt Nam |
| Định dạng | Dữ liệu có dấu, khoảng trắng, ký tự lạ? | Chữ trên bánh có emoji (bếp không viết được); số điện thoại có “+84” |
| Lặp và đồng thời | Bấm hai lần? Hai người cùng thao tác? | Hai nhân viên tạo đơn cùng giây vẫn có mã khác nhau; hai người sửa cùng đơn |
| Quyền | Người không có quyền thì thấy gì? | Staff Gò Vấp mở đường dẫn đơn của Phú Nhuận |
| Trạng thái | Hành động ở trạng thái “sai”? | Sửa đơn đã hoan_tat; ghi cọc cho đơn da_huy |
| Hạ tầng | Mất mạng, chậm, lỗi máy chủ? | Mất Wi-Fi ở quầy lúc bấm “Tạo đơn” — dữ liệu form không được mất |
| Dữ liệu cũ | Dữ liệu chuyển từ Sheet không chuẩn? | Khách cũ không có số điện thoại; số điện thoại trùng giữa hai khách |
Bạn không cần viết Given–When–Then cho mọi ô. Hãy chọn những ô có hậu quả lớn (sai tiền, sai bánh, lộ dữ liệu) để viết thành tiêu chí, còn lại đưa vào mục “câu hỏi mở” để quyết định. Ví dụ, khi chị Lan hỏi anh Tuấn về bánh nhận lúc 07:30 (trước khi chuyến sáng tới chi nhánh), anh nói “bánh đó phải đi chuyến 13:00 hôm trước” — một quy tắc QT-07 chưa bao phủ, được ghi vào câu hỏi mở của spec. Kỹ thuật săn edge case được đào sâu ở bài dữ liệu test và edge case08-07.
Từ tiêu chí chấp nhận sang test tự động
Đây là phần khiến tiêu chí chấp nhận trở thành “vũ khí” khi làm việc với agent. Mỗi kịch bản Given–When–Then gần như dịch thẳng được thành một test:
- QT-02: bánh kem tùy chỉnh đặt trước tối thiểu 24 giờ
- chấp nhận khi giờ nhận cách hiện tại đúng 24 giờ (ranh giới)
- từ chối staff khi giờ nhận chỉ cách 22 giờ
- cho manager tạo đơn gấp khi có lý do
Vuốt ngang để xem hết sơ đồ
Vuốt ngang để xem hết sơ đồ
Đọc sơ đồ dạng chữ
- User story → Tiêu chí chấp nhận Given–When–Then
- Tiêu chí chấp nhận Given–When–Then → Unit test (quy tắc thuần: 24 giờ, chuẩn hóa SĐT)
- Tiêu chí chấp nhận Given–When–Then → Test end-to-end (luồng trên giao diện)
- Tiêu chí chấp nhận Given–When–Then → Kịch bản kiểm thử thủ công / UAT
- Unit test (quy tắc thuần: 24 giờ, chuẩn hóa SĐT) → Chạy tự động mỗi lần thay đổi
- Test end-to-end (luồng trên giao diện) → Chạy tự động mỗi lần thay đổi
- Kịch bản kiểm thử thủ công / UAT → Nghiệm thu với người dùng
- Quy tắc thuần (tính toán, kiểm tra điều kiện) → unit test chạy nhanh, ví dụ bằng Vitest.
- Luồng người dùng trên giao diện → test end-to-end, ví dụ bằng Playwright.
- Những gì khó tự động (chữ có dễ đọc trên máy tính bảng ở bếp không) → kịch bản kiểm thử thủ công cho buổi nghiệm thu.
Ví dụ, kịch bản “Nhân viên đặt bánh kem gấp dưới 24 giờ” thành unit test. Để ý chú thích Given/When/Then được giữ lại trong code — người đọc test có thể đối chiếu ngược về story:
// tests/unit/orders/lead-time.test.ts
import { describe, it, expect } from "vitest";
import { checkLeadTime } from "@/features/orders/rules";
describe("QT-02: bánh kem tùy chỉnh đặt trước tối thiểu 24 giờ", () => {
// Given: bây giờ là 10:00 ngày 12/10/2026 (giờ Việt Nam)
const now = new Date("2026-10-12T10:00:00+07:00");
it("chấp nhận khi giờ nhận cách hiện tại đúng 24 giờ (ranh giới)", () => {
const result = checkLeadTime({
hasCustomCake: true,
deliveryAt: new Date("2026-10-13T10:00:00+07:00"),
now,
role: "staff",
});
expect(result.ok).toBe(true);
});
it("từ chối staff khi giờ nhận chỉ cách 22 giờ", () => {
// When: Hà chọn giờ nhận 08:00 ngày 13/10
const result = checkLeadTime({
hasCustomCake: true,
deliveryAt: new Date("2026-10-13T08:00:00+07:00"),
now,
role: "staff",
});
// Then: bị từ chối với mã lỗi rõ ràng
expect(result.ok).toBe(false);
expect(result.code).toBe("LEAD_TIME_TOO_SHORT");
});
it("cho manager tạo đơn gấp khi có lý do", () => {
const result = checkLeadTime({
hasCustomCake: true,
deliveryAt: new Date("2026-10-12T16:00:00+07:00"),
now,
role: "manager",
rushReason: "Khách quen, bếp đã nhận",
});
expect(result.ok).toBe(true);
});
});
Và quy tắc QT-05 về số điện thoại — một hàm nhỏ cùng bộ test dạng bảng:
// src/lib/phone.ts
/** Chuẩn hóa số điện thoại Việt Nam về dạng 0xxxxxxxxx; trả về null nếu không hợp lệ. */
export function normalizePhone(input: string): string | null {
// Bỏ khoảng trắng, dấu chấm, gạch ngang, ngoặc
let digits = input.trim().replace(/[\s.\-()]/g, "");
// +84xxxxxxxxx hoặc 84xxxxxxxxx -> 0xxxxxxxxx
if (digits.startsWith("+84")) digits = "0" + digits.slice(3);
else if (digits.startsWith("84")) digits = "0" + digits.slice(2);
if (!/^\d+$/.test(digits)) return null; // còn ký tự lạ → không hợp lệ
if (/^0[35789]\d{8}$/.test(digits)) return digits; // di động: 10 số
if (/^02\d{9}$/.test(digits)) return digits; // cố định: 11 số, đầu 02
return null; // thiếu số, thừa số, đầu số lạ
}
// tests/unit/customers/phone.test.ts
import { describe, it, expect } from "vitest";
import { normalizePhone } from "@/lib/phone";
describe("QT-05: chuẩn hóa số điện thoại", () => {
it.each([
["0901 234 567", "0901234567"],
["+84 901 234 567", "0901234567"],
["84901234567", "0901234567"],
["090.123.4567", "0901234567"],
])("chuẩn hóa '%s' thành '%s'", (input, expected) => {
expect(normalizePhone(input)).toBe(expected);
});
it("trả về null với số không đủ 10 chữ số", () => {
expect(normalizePhone("09123")).toBeNull();
});
});
Luồng chính của US-01 trên giao diện, viết bằng Playwright:
// tests/e2e/tao-don-banh-kem.spec.ts
import { test, expect } from "@playwright/test";
test("US-01: Hà tạo đơn bánh kem hợp lệ", async ({ page }) => {
// Given: đã đăng nhập vai trò staff (trạng thái đăng nhập chuẩn bị sẵn trong cấu hình test)
await page.goto("/orders/new");
// When: nhập thông tin đơn
await page.getByLabel("Số điện thoại").fill("0901234567");
await page.getByRole("button", { name: "Tìm khách" }).click();
await page.getByLabel("Sản phẩm").selectOption({ label: "Bánh kem tùy chỉnh 20cm" });
await page.getByLabel("Vị").selectOption({ label: "Chanh dây" });
await page.getByLabel("Chữ trên bánh").fill("Mừng sinh nhật Mẹ Ánh");
await page.getByLabel("Giờ nhận").fill("2026-10-13T17:00");
await page.getByRole("button", { name: "Tạo đơn" }).click();
// Then: thấy mã đơn và chữ trên bánh giữ nguyên dấu
await expect(page.getByText(/BM-\d{6}-\d{3}/)).toBeVisible();
await expect(page.getByText("Mừng sinh nhật Mẹ Ánh")).toBeVisible();
});
Áp dụng vào dự án Bánh Mộc
Nhật ký công trình · Bánh Mộc
BM-261013-027
13.10.2026
- Chị LanChủ chuỗi
- Anh TuấnBếp trung tâm
- HàBán hàng online
Chị Lan viết bản nháp 6 story trên trong một buổi tối, rồi làm ba việc trước khi coi là xong.
Đọc to với người dùng. Chị đọc từng kịch bản cho Hà và anh Tuấn nghe. Hà bắt lỗi ngay ở US-02: “Chị ơi, nhiều khách nhắn bằng Zalo không cho số điện thoại, em chỉ có tên Zalo.” Đây là một trường hợp biên quan trọng. Chị quyết định: MVP vẫn bắt buộc số điện thoại (Hà sẽ hỏi khách — vì bếp và người giao đều cần), nhưng ghi vào câu hỏi mở cho giai đoạn trợ lý AI nhận đơn qua Zalo.
Nhờ AI săn trường hợp biên. Chị đưa 6 story cho AI với Prompt 2 bên dưới. AI đề xuất 23 trường hợp. Chị giữ lại 7 trường hợp có hậu quả lớn (ví dụ “chữ trên bánh có emoji”, “hai nhân viên tạo đơn cùng giây”), gạch 10 cái không thực tế với tiệm bánh, và đưa 6 cái vào câu hỏi mở.
Đánh mã để truy vết. Mỗi story có mã (US-01…), mỗi quy tắc có mã (QT-01…). Chị dặn agent đặt mã này vào tên test. Sau này khi test “QT-02” thất bại, ai cũng biết ngay quy tắc nghiệp vụ nào đang bị vi phạm.
Các story được lưu vào docs/specs/user-stories.md và trở thành đầu vào cho spec “Tạo đơn hàng” ở bài tiếp theo.
Prompt mẫu
Prompt 1 — Viết user story và tiêu chí chấp nhận từ mô tả thô
Điền ô trống rồi sao chép
Bối cảnh: phần mềm quản lý đơn cho chuỗi 3 tiệm bánh. Vai trò: owner, manager, staff.
Quy tắc nghiệp vụ: """[dán bảng QT-01…QT-08]"""
Nhu cầu thô: "Hà cần ghi nhận tiền cọc ngay trên đơn để chi nhánh biết khách còn phải trả bao nhiêu."
Hãy viết:
1. Một user story theo mẫu "Là… tôi muốn… để…".
2. Tiêu chí chấp nhận dạng Given–When–Then (từ khóa tiếng Anh, nội dung tiếng Việt), gồm:
ít nhất 1 luồng chính, 2 trường hợp biên, 2 luồng lỗi; dùng số cụ thể (tiền VNĐ, giờ, ngày).
3. Tự kiểm tra story theo INVEST, ghi chữ nào chưa đạt và cách sửa.
Không mô tả giao diện chi tiết hay cách cài đặt.
Prompt 2 — Săn trường hợp biên
Điền ô trống rồi sao chép
Đây là các user story và tiêu chí chấp nhận của tôi:
"""[dán]"""
Đóng vai kiểm thử viên khó tính. Liệt kê các trường hợp biên và luồng lỗi CHƯA được bao phủ, nhóm theo:
ranh giới số, thời gian/múi giờ, định dạng dữ liệu, lặp/đồng thời, phân quyền, trạng thái, hạ tầng, dữ liệu cũ.
Với mỗi trường hợp: mô tả 1 dòng, mức hậu quả (cao/vừa/thấp) với tiệm bánh, và câu hỏi tôi cần quyết định.
Sắp xếp theo mức hậu quả giảm dần.
Prompt 3 — Giao agent viết test từ tiêu chí (trong repo)
Điền ô trống rồi sao chép
Đọc docs/specs/user-stories.md, phần US-01 và các quy tắc QT-01, QT-02, QT-03.
Nhiệm vụ: CHỈ viết test, chưa viết code tính năng.
- Unit test (Vitest) cho quy tắc thuần trong tests/unit/orders/, tên test bắt đầu bằng mã quy tắc (ví dụ "QT-02: ...").
- Một test E2E (Playwright) cho luồng chính US-01 trong tests/e2e/.
- Giữ chú thích Given/When/Then trong test.
Chạy bộ test, xác nhận các test MỚI đều thất bại vì tính năng chưa tồn tại (không phải vì lỗi cú pháp),
và dán output vào câu trả lời. Không sửa file ngoài thư mục tests/.
Sai lầm thường gặp
-
Sự cố 01
Story không có vế “để…”.
Triệu chứng“Là staff, tôi muốn có nút xuất Excel.” *
Cách tránh* bắt buộc vế lợi ích; nếu không viết được, hỏi người dùng hoặc gạch story.
-
Sự cố 02
Tiêu chí mơ hồ, không kiểm tra được.
Triệu chứng“Form phải dễ dùng, nhanh.” *
Cách tránh* dùng số và hành vi quan sát được: “Tạo một đơn bánh kem trong dưới 90 giây với khách đã có.”
-
Sự cố 03
Chỉ có happy path.
Triệu chứng*
Cách tránh* mỗi story tối thiểu 1 luồng chính, 1–2 trường hợp biên, 1–2 luồng lỗi; dùng bảng câu hỏi săn edge case.
-
Sự cố 04
Mô tả cách cài đặt thay vì hành vi.
Triệu chứng“Then gọi API POST /orders trả 201.” *
Cách tránh* viết thứ người dùng thấy; chi tiết kỹ thuật để dành cho spec và plan.
-
Sự cố 05
Story quá lớn.
Triệu chứng“Quản lý đơn hàng.” *
Cách tránh* chia theo luồng, vai trò hoặc quy tắc cho tới khi mỗi story vừa vài phiên làm việc với agent.
-
Sự cố 06
Viết xong không cho người dùng đọc.
Triệu chứng*
Cách tránh* đọc to từng kịch bản với người dùng thật; họ sẽ phát hiện những trường hợp bạn không ngờ.
Checklist
Bài tập thực hành
-
Bài 1
Cơ bản
Sửa story xấu
Viết lại 4 story sau cho đạt INVEST: “Tôi muốn hệ thống quản lý khách hàng”; “Là admin, tôi muốn bảng customers có cột birthday”; “Tôi muốn form đẹp hơn”; “Là staff, tôi muốn xuất Excel”.
Kết quả mong đợi
mỗi story có vai trò rõ, vế “để…” có ý nghĩa, đủ nhỏ, kèm ít nhất 2 tiêu chí Given–When–Then.
-
Bài 2
Cơ bản
Ranh giới
Với quy tắc “đặt trước ít nhất 24 giờ”, viết đủ các kịch bản ranh giới: 23 giờ 59 phút, đúng 24 giờ, 24 giờ 1 phút, qua nửa đêm, giờ nhận ngoài khung 07:00–21:00.
Kết quả mong đợi
5 kịch bản, mỗi kịch bản có giờ hiện tại và giờ nhận cụ thể, kết quả rõ ràng.
-
Bài 3
Nâng cao
Từ tiêu chí đến test với agent
Chọn một story của bạn, viết tiêu chí đầy đủ, rồi dùng Prompt 3 cho agent viết test trước. Đọc từng test và đánh dấu test nào không kiểm tra đúng điều tiêu chí nói.
Kết quả mong đợi
test chạy và thất bại đúng lý do; bạn tìm ra ít nhất một chỗ test lệch so với tiêu chí (ví dụ kiểm tra quá lỏng) và yêu cầu agent sửa.
Thuật ngữ trong bài
- User story
- Mô tả nhu cầu từ góc nhìn người dùng: là ai, muốn gì, để làm gì.
- Tiêu chí chấp nhận (acceptance criteria)
- Điều kiện cụ thể để coi một story là hoàn thành.
- Given–When–Then
- Cấu trúc kịch bản: bối cảnh – hành động – kết quả.
- Gherkin
- Ngôn ngữ viết kịch bản dạng Given–When–Then dùng trong BDD.
- BDD
- Phát triển hướng hành vi: mô tả hành vi mong muốn trước, dùng làm test.
- INVEST
- Sáu tiêu chí story tốt: độc lập, thương lượng được, có giá trị, ước lượng được, nhỏ, kiểm tra được.
- Happy path
- Luồng chính khi mọi thứ diễn ra suôn sẻ.
- Trường hợp biên (edge case)
- Tình huống ở rìa quy tắc hoặc ít gặp, dễ gây lỗi.
- Luồng lỗi (error flow)
- Kịch bản khi có sự cố: dữ liệu sai, mất mạng, không có quyền.
- Unit test / E2E test
- Test một quy tắc nhỏ độc lập / test toàn bộ luồng qua giao diện.
Đọc thêm
Nghiệm thu tờ 03-05
Đọc xong và tự kiểm theo phiếu nghiệm thu, rồi đánh dấu tờ này.

