Menu
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Đang tải...

Pairwise Testing (Combinatorial)

Software Testing Fundamentals Bài 12/60

Mở đầu — vì sao bài này quan trọng

Hãy tưởng tượng bạn là tester cho một ứng dụng đặt vé máy bay. Form tìm chuyến bay có 5 tham số: điểm đi (Hà Nội, TP.HCM, Đà Nẵng, Nha Trang), điểm đến (4 lựa chọn), loại vé (một chiều / khứ hồi), hạng ghế (phổ thông / thương gia), và hình thức thanh toán (thẻ / MoMo / ZaloPay / VNPay). Nếu bạn muốn kiểm thử mọi tổ hợp có thể xảy ra, con số là 4 × 4 × 2 × 2 × 4 = 512 test case. Mà đây mới chỉ là một form đơn giản. Trong thực tế, một tính năng thật có thể có hàng chục tham số, và số tổ hợp dễ dàng vọt lên hàng triệu, hàng tỷ.

Không đội QA nào trên đời có đủ thời gian để chạy hết. Đây chính là bùng nổ tổ hợp (combinatorial explosion) — kẻ thù thầm lặng của mọi tester. Và Pairwise Testing (còn gọi là All-pairs Testing) ra đời để giải quyết đúng bài toán này.

Điều làm Pairwise trở nên đặc biệt không phải là nó "chạy ít test case hơn cho nhanh". Nó dựa trên một phát hiện thực nghiệm rất mạnh: phần lớn lỗi phần mềm được kích hoạt bởi sự tương tác của chỉ một hoặc hai tham số, chứ không phải bởi tổ hợp phức tạp của nhiều tham số cùng lúc. Nếu bạn đảm bảo rằng mọi cặp giá trị của hai tham số bất kỳ đều xuất hiện ít nhất một lần trong bộ test, bạn đã đánh trúng phần lớn lỗi mà chỉ tốn một phần rất nhỏ công sức. Bài này sẽ dạy bạn hiểu bản chất, tự tay dựng bộ test pairwise, và biết khi nào nên (và không nên) dùng nó.

Khái niệm cốt lõi

Bùng nổ tổ hợp và ý tưởng "cắt góc" thông minh

Khi bạn có n tham số, mỗi tham số có v giá trị, tổng số tổ hợp đầy đủ (exhaustive) là tích của số giá trị các tham số. Con số này tăng theo cấp số nhân. Pairwise không cố phủ tất cả tổ hợp — nó chỉ đảm bảo phủ mọi cặp giá trị giữa từng đôi tham số.

Nói cách khác: với hai tham số A và B bất kỳ, mọi kết hợp (giá trị của A, giá trị của B) phải xuất hiện ít nhất một lần đâu đó trong bộ test. Bạn không cần từng bộ test phải chứa mọi cặp — mà tổng thể cả bộ test cộng lại phải phủ hết các cặp.

Vì sao "cặp" lại đủ tốt?

Đây là phần cốt lõi cần nhớ. Các nghiên cứu thực nghiệm nổi tiếng — đặc biệt là công trình của NIST (Viện Tiêu chuẩn và Công nghệ Quốc gia Hoa Kỳ) do Rick Kuhn và cộng sự thực hiện trên nhiều hệ thống thực tế (thiết bị y tế, trình duyệt, phần mềm NASA) — cho thấy:

  • Khoảng 20–70% lỗi được kích hoạt bởi một tham số duy nhất (single-mode fault).
  • Cộng dồn với tương tác hai tham số (pairwise), tỉ lệ lỗi phát hiện được lên tới 70–95%, và trong nhiều hệ thống là trên 80%.
  • Rất hiếm khi cần đến tương tác của 4–6 tham số mới lộ ra lỗi.
Kết luận thực tế: nếu bạn phủ hết mọi cặp, bạn tóm được đại đa số bug với chi phí cực thấp. Đây là một ví dụ đẹp của quy luật 80/20 — 20% công sức bắt 80% lỗi.

Pairwise là một dạng của Combinatorial Testing

Pairwise thực chất là trường hợp đặc biệt của khái niệm rộng hơn: t-way testing (hay N-wise). Ở đây "t" là "strength" — độ mạnh của việc phủ tổ hợp:

  • t = 1: mỗi giá trị của mỗi tham số xuất hiện ít nhất một lần (each-choice) — yếu.
  • t = 2: phủ mọi cặp — đây chính là pairwise.
  • t = 3: phủ mọi bộ ba (3-way), mạnh hơn nhưng nhiều test case hơn.
Với hệ thống có rủi ro cao (y tế, hàng không, thanh toán), người ta có thể tăng strength lên t = 3. Còn với đa số ứng dụng web/mobile thương mại, t = 2 (pairwise) là điểm cân bằng vàng.

Điều quan trọng: chọn tham số và giá trị cho đúng

Pairwise không phải phép màu tự động. Chất lượng của nó phụ thuộc hoàn toàn vào việc bạn định nghĩa tham số và tập giá trị như thế nào. Nếu bạn bỏ sót một giá trị quan trọng, không thuật toán pairwise nào cứu được bạn. Vì thế, bước "phân tích và chọn giá trị đại diện" — thường kết hợp với kỹ thuật chia lớp tương đương — là bước quan trọng nhất, chứ không phải bước chạy công cụ sinh test.

Ràng buộc (constraints)

Trong thực tế, một số tổ hợp là bất khả thi về mặt nghiệp vụ. Ví dụ: "hạng ghế = thương gia" không thể đi cùng "khuyến mãi vé 0 đồng của hạng phổ thông". Các công cụ pairwise tốt cho phép khai báo constraint để loại bỏ những cặp không hợp lệ, tránh sinh ra test case vô nghĩa hoặc không chạy được.

Tình huống thực tế

Ví dụ 1 — Sàn thương mại điện tử Việt Nam giảm 512 test còn 16

Một đội QA tại sàn TMĐT giả định "ShopViet" cần kiểm thử tính năng thanh toán giỏ hàng. Họ liệt kê được 5 tham số:

  • Trình duyệt: Chrome, Safari, Firefox, Cốc Cốc (4 giá trị)
  • Thiết bị: Desktop, Mobile (2)
  • Phương thức thanh toán: Thẻ nội địa, Visa/Master, MoMo, ZaloPay (4)
  • Loại tài khoản: Khách vãng lai, Thành viên, Thành viên VIP (3)
  • Áp mã giảm giá: Có, Không (2)
Tổ hợp đầy đủ: 4 × 2 × 4 × 3 × 2 = 192 test case. Đội chỉ có 2 ngày regression trước release, không thể chạy hết bằng tay.

Họ dùng công cụ pairwise (PICT của Microsoft). Kết quả: bộ test rút xuống còn 16 test case phủ đủ mọi cặp. Khi chạy, họ phát hiện đúng một lỗi nghiêm trọng: trên Safari + Mobile + MoMo, nút "Xác nhận thanh toán" bị đẩy ra khỏi màn hình do lỗi CSS. Đây là tương tác hai tham số (Safari-Mobile về layout, và MoMo về deep-link) mà pairwise bắt được ngay.

Bài học: Giảm hơn 90% khối lượng test nhưng vẫn tóm được bug tương tác thật. Pairwise cho phép đội QA nhỏ vẫn cover được ma trận cấu hình lớn trong thời gian giới hạn.

Ví dụ 2 — Fintech và cái bẫy "quên khai báo constraint"

Một startup fintech tại TP.HCM (gọi là "PayNhanh") test luồng chuyển tiền. Tham số gồm: loại giao dịch (nội bộ / liên ngân hàng), số tiền (dưới 500k / 500k–10 triệu / trên 10 triệu), phương thức xác thực (OTP SMS / Smart OTP / vân tay), và hạng tài khoản (thường / premium).

Lần đầu, họ chạy pairwise mà quên một ràng buộc nghiệp vụ: giao dịch trên 10 triệu bắt buộc phải dùng Smart OTP, không được dùng OTP SMS. Công cụ sinh ra vài test case kiểu "chuyển 15 triệu + OTP SMS" — vốn không thể xảy ra trong hệ thống. Tester tốn nửa buổi để phát hiện đây là test case "chết", không phải bug thật.

Sau khi khai báo constraint đúng, bộ test giảm còn 12 case sạch sẽ, và họ phát hiện lỗi thực: với "liên ngân hàng + số tiền 500k–10 triệu + vân tay", hệ thống không hiện màn hình xác nhận phí giao dịch.

Bài học: Constraint là phần không thể bỏ. Pairwise sinh test dựa trên tổ hợp toán học, nó không hiểu nghiệp vụ của bạn. Nếu không khai báo ràng buộc, bạn sẽ lãng phí thời gian vào những tổ hợp vô nghĩa.

Ví dụ 3 — Grab-style app và giới hạn của pairwise

Một đội QA cho ứng dụng gọi xe đặt niềm tin tuyệt đối vào pairwise cho module tính cước. Họ phủ đủ mọi cặp giữa: loại xe, khung giờ (cao điểm / thường), khu vực, và loại khuyến mãi. Test pass hết. Nhưng khi lên production, xảy ra lỗi nghiêm trọng: giờ cao điểm + khu vực sân bay + khuyến mãi "giảm 50k" + xe premium cùng lúc gây tính cước âm.

Đây là lỗi tương tác ba tham số (3-way), pairwise không đảm bảo phủ. Sau sự cố, đội quyết định nâng strength lên t = 3 cho riêng module tính cước — vốn là logic tài chính rủi ro cao.

Bài học: Pairwise bắt phần lớn lỗi, không phải mọi lỗi. Với logic cực kỳ nhạy cảm về tiền bạc/an toàn, hãy cân nhắc tăng strength hoặc bổ sung test có chủ đích cho các tổ hợp rủi ro.

Hướng dẫn từng bước

Đây là quy trình bạn có thể áp dụng ngay cho bất kỳ tính năng nào.

Bước 1 — Xác định các tham số đầu vào. Liệt kê tất cả yếu tố có thể ảnh hưởng đến hành vi hệ thống: cấu hình, lựa chọn của người dùng, trạng thái dữ liệu, môi trường. Đừng bỏ sót, nhưng cũng đừng đưa vào những tham số hoàn toàn độc lập, không tương tác với nhau.

Bước 2 — Chọn tập giá trị đại diện cho mỗi tham số. Đây là bước then chốt. Đừng liệt kê mọi giá trị có thể (ví dụ "số tiền" có vô hạn giá trị). Hãy chọn các giá trị đại diện — thường kết hợp tư duy chia lớp tương đương: mỗi lớp một giá trị, cộng thêm giá trị biên nếu cần. Giữ số giá trị mỗi tham số ở mức hợp lý (2–5) để bộ test không phình.

Bước 3 — Khai báo các ràng buộc (constraints). Ghi rõ những tổ hợp bất khả thi hoặc bắt buộc theo nghiệp vụ. Ví dụ: "nếu quốc gia = Việt Nam thì tiền tệ = VND", "nếu loại tài khoản = khách vãng lai thì không có điểm thưởng".

Bước 4 — Sinh bộ test pairwise. Với vài tham số nhỏ bạn có thể làm tay, nhưng thực tế nên dùng công cụ. Các lựa chọn phổ biến:

  • PICT (Microsoft, miễn phí, dòng lệnh) — mạnh, hỗ trợ constraint và seeding.
  • pairwise.teremokgames.com hoặc pairwise.yuchengkuo — công cụ web nhập bảng, xuất kết quả ngay.
  • Thư viện lập trình như AllPairs (Python), pairwise cho các ngôn ngữ khác — dùng khi muốn tích hợp vào automation.
Bước 5 — Rà soát và bổ sung. Đừng tin công cụ 100%. Xem lại bộ test: có tổ hợp rủi ro cao nào cần thêm thủ công không? Có test case "chết" nào lọt lưới không? Bổ sung các case biên hoặc case nghiệp vụ quan trọng mà pairwise có thể chưa nhấn mạnh.

Bước 6 — Chuyển thành test case đầy đủ. Mỗi dòng trong bảng pairwise mới chỉ là "bộ giá trị đầu vào". Bạn cần thêm bước thực hiện và kết quả mong đợi cho từng dòng — vì kết quả mong đợi khác nhau tùy tổ hợp. Pairwise chỉ giúp bạn chọn đầu vào nào để test, không tự sinh ra oracle.

Minh họa một cú pháp PICT đơn giản cho ví dụ ShopViet:

Browser: Chrome, Safari, Firefox, CocCoc
Device: Desktop, Mobile
Payment: TheNoiDia, VisaMaster, MoMo, ZaloPay
Account: Guest, Member, VIP
Coupon: Yes, No

IF [Account] = "Guest" THEN [Coupon] = "No";

Chạy pict model.txt sẽ cho ra bảng khoảng 16 dòng phủ đủ mọi cặp và tôn trọng ràng buộc đã khai báo.

Lỗi thường gặp & mẹo

Lỗi 1 — Nghĩ pairwise thay thế được mọi kỹ thuật khác. Không. Pairwise là kỹ thuật chọn tổ hợp, nó không thay thế phân tích biên, decision table hay exploratory. Hãy dùng nó cho các tính năng nhiều tham số, không phải cho mọi thứ.

Lỗi 2 — Chọn giá trị đại diện cẩu thả. Nếu tập giá trị đầu vào của bạn đã sai/thiếu, pairwise sẽ phủ hoàn hảo... một bộ đầu vào sai. Đầu tư thời gian cho bước chọn giá trị.

Lỗi 3 — Quên constraint. Như ví dụ PayNhanh, bỏ qua ràng buộc sinh ra test case vô nghĩa và làm mất niềm tin của đội vào kỹ thuật này.

Lỗi 4 — Tin rằng "pass hết là an toàn tuyệt đối". Pairwise không phủ tương tác 3+ tham số. Với module rủi ro cao, nâng strength hoặc thêm test có chủ đích.

Mẹo hay:

  • Đặt các tham số quan trọng nhất lên đầu danh sách khi dùng PICT — công cụ có xu hướng tối ưu tốt hơn cho các tham số đầu.
  • Dùng tính năng seeding: nếu bạn có sẵn vài test case "phải chạy" (ví dụ tổ hợp từng gây bug trong quá khứ), khai báo chúng làm seed để pairwise giữ lại và bổ sung xung quanh.
  • Ghép pairwise với risk-based: những cặp liên quan luồng tiền, bảo mật thì đánh dấu ưu tiên chạy trước.
  • Kiểm tra "tiết kiệm bao nhiêu" trước khi dùng: nếu tổ hợp đầy đủ chỉ vài chục case, cứ chạy full cho chắc; pairwise phát huy giá trị khi số tổ hợp lên tới hàng trăm, hàng nghìn.

Bài tập thực hành

Bài 1. Một form đăng ký tài khoản có các tham số: Quốc gia (VN, US, JP), Ngôn ngữ (Tiếng Việt, English), Nhận email marketing (Có, Không), Loại tài khoản (Cá nhân, Doanh nghiệp). Tính số test case của tổ hợp đầy đủ. Sau đó, tự tay dựng bảng pairwise (hoặc dùng công cụ) và đếm xem còn bao nhiêu test case. So sánh mức tiết kiệm.

Bài 2. Với form ở Bài 1, hãy viết ít nhất 2 ràng buộc nghiệp vụ hợp lý (ví dụ liên quan quốc gia và ngôn ngữ), rồi sinh lại bộ test và quan sát bảng thay đổi thế nào.

Bài 3. Lấy một tính năng thật trong dự án bạn đang làm (hoặc app bạn hay dùng như một ví bán hàng online). Liệt kê 4–6 tham số và tập giá trị đại diện. Chạy PICT hoặc công cụ web pairwise. Sau đó phân tích: trong bộ test sinh ra, có tổ hợp rủi ro cao nào mà bạn muốn tự bổ sung thêm vì pairwise chưa nhấn mạnh không?

Bài 4 (nâng cao). Chọn một tham số bạn cho là "nhạy cảm" (ví dụ số tiền trong luồng thanh toán). Sinh bộ test ở strength t = 2, rồi thử t = 3, so sánh số lượng test case tăng bao nhiêu. Suy nghĩ: mức tăng đó có xứng đáng với rủi ro của tính năng không?

Tóm tắt

Pairwise Testing là vũ khí giúp tester chiến thắng bài toán bùng nổ tổ hợp. Thay vì chạy hàng trăm, hàng nghìn tổ hợp đầy đủ, bạn chỉ cần đảm bảo mọi cặp giá trị giữa hai tham số bất kỳ đều xuất hiện ít nhất một lần — và nhờ đó tóm được 80%+ lỗi tương tác với chỉ một phần rất nhỏ công sức.

Những điều cốt lõi cần ghi nhớ:

  • Nền tảng thực nghiệm: phần lớn lỗi đến từ tương tác một hoặc hai tham số, hiếm khi cần tổ hợp phức tạp hơn — đó là lý do pairwise hiệu quả.
  • Bước quan trọng nhất không phải chạy công cụ, mà là chọn đúng tham số, đúng giá trị đại diện, và khai báo đầy đủ constraint.
  • Dùng công cụ (PICT, công cụ web, thư viện AllPairs) để sinh bộ test, nhưng luôn rà soát và bổ sung bằng tư duy của con người.
  • Biết giới hạn: pairwise không phủ tương tác 3+ tham số. Với module rủi ro cao về tiền bạc/an toàn, hãy nâng strength lên t = 3 hoặc thêm test có chủ đích.
  • Kết hợp, không thay thế: pairwise là kỹ thuật chọn tổ hợp đầu vào, hoạt động tốt nhất khi đi cùng các kỹ thuật thiết kế test khác.
Khi bạn đứng trước một tính năng với ma trận cấu hình khổng lồ và thời gian ít ỏi — hãy nhớ đến pairwise. Nó biến một nhiệm vụ tưởng như bất khả thi thành một bộ test gọn gàng, thông minh, và hiệu quả.