Software Testing - Common Testing Techniques (P1)


PHẦN 1 – STATE TESTING, CLASS DEFINITION BASED TESTING & COMBINATION TESTING

🔹 Mô tả ngắn – Phần 1

Phần này giới thiệu ba kỹ thuật kiểm thử phổ biến dựa trên cấu trúc và đặc tả của hệ thống, bao gồm State Testing, Class Definition Based TestingCombination Testing. Nội dung giúp người học hiểu cách xây dựng test case từ trạng thái của hệ thống, từ các điều kiện ràng buộc của lớp và từ sự kết hợp giữa nhiều biến đầu vào. Trọng tâm của phần học là lựa chọn test case một cách có hệ thống để tăng độ bao phủ nhưng vẫn kiểm soát được số lượng trường hợp kiểm thử.

1. State Testing – Kiểm thử trạng thái

1.1. State Testing là gì?

Không phải mọi hệ thống đều phản ứng giống nhau trước cùng một hành động. Trong nhiều trường hợp, kết quả của một thao tác còn phụ thuộc vào trạng thái hiện tại của hệ thống.

Ví dụ, thao tác nhấn nút Submit có thể cho kết quả khác nhau nếu:

  • người dùng chưa đăng nhập,
  • đã đăng nhập,
  • tài khoản bị khóa,
  • hoặc phiên làm việc đã hết hạn.

State Testing được xây dựng chính từ đặc điểm này. Đây là kỹ thuật tạo test case dựa trên State Diagram hoặc State Transition Diagram. Kỹ thuật có thể được sử dụng ở nhiều mức độ khác nhau: nếu biểu đồ mô tả toàn bộ hệ thống, ta có thể tạo system test case; nếu biểu đồ chỉ mô tả một component hoặc một class, test case sẽ tập trung vào component hoặc class đó.

Điểm quan trọng của State Testing không chỉ nằm ở việc kiểm tra từng trạng thái riêng lẻ, mà còn phải quan sát quá trình chuyển đổi giữa các trạng thái.

1.2. Bắt đầu từ State Transition Diagram

Để thực hiện State Testing, bước đầu tiên là có được một biểu đồ chuyển trạng thái.

Có hai trường hợp:

  • Nếu State Transition Diagram đã có trong tài liệu hệ thống, tester có thể sử dụng trực tiếp.
  • Nếu chưa có, tester cần tự xây dựng dựa trên đặc tả và hành vi của hệ thống.

Một state diagram thường giúp trả lời ba câu hỏi cơ bản:

  • Hệ thống hiện đang ở trạng thái nào?
  • Sự kiện nào khiến hệ thống thay đổi trạng thái?
  • Sau sự kiện đó, hệ thống chuyển sang trạng thái nào?

Ví dụ, với một tài khoản người dùng:

Logged Out → Logged In → Locked → Logged Out

Mỗi mũi tên giữa các trạng thái có thể trở thành một nguồn để thiết kế test case.

1.3. Không thể kiểm thử mọi đường đi

Trong một hệ thống nhỏ, chúng ta có thể cố gắng đi qua tất cả các trạng thái. Tuy nhiên, khi số trạng thái và transition tăng lên, việc đi qua mọi đường dẫn có thể thường trở nên không khả thi.

Tài liệu đề xuất giảm số lượng state và transition cần kiểm thử bằng cách ưu tiên một số nhóm quan trọng:

  • Đi qua mỗi trạng thái ít nhất một lần.
  • Kiểm thử các transition phổ biến nhất.
  • Kiểm thử những đường đi ít phổ biến.
  • Kiểm thử tất cả error states và cách hệ thống quay trở lại từ trạng thái lỗi.
  • Thử một số state transition ngẫu nhiên.

Đây là điểm khá giống tư duy Equivalence Partitioning: thay vì cố gắng kiểm thử mọi thứ, tester lựa chọn những trạng thái và đường đi đại diện hoặc có rủi ro cao.

1.4. Test case trong State Testing cần kiểm tra điều gì?

Khi đã xác định state và transition, test case không chỉ kiểm tra việc “đã chuyển trạng thái hay chưa”. Tài liệu còn yêu cầu chú ý đến những thành phần liên quan như:

  • State variables.
  • Các điều kiện tĩnh.
  • Thông tin của trạng thái.
  • Giá trị.
  • Chức năng liên quan.

Ví dụ, nếu một tài khoản chuyển từ Active sang Locked sau 5 lần nhập sai mật khẩu, tester không chỉ kiểm tra trạng thái cuối là Locked. Cần kiểm tra thêm:

  • số lần đăng nhập sai có được ghi đúng không,
  • thông báo hiển thị có đúng không,
  • tài khoản có thực sự bị chặn truy cập hay không.

1.5. Testing State to Fail – chủ động đẩy hệ thống vào tình huống xấu

Một phần đáng chú ý trong State Testing là chủ động thiết kế các test case để hệ thống có khả năng thất bại.

Một số tình huống được đề cập gồm:

Race conditions và bad timing

Hệ thống có thể gặp lỗi khi nhiều tiến trình chạy đồng thời hoặc các sự kiện xảy ra không đúng thời điểm. Tester có thể xem từng state và tự hỏi:

“Điều gì sẽ xảy ra nếu một tác động bên ngoài làm gián đoạn hệ thống ngay tại trạng thái này?”

Repetition

Thực hiện cùng một hành động lặp đi lặp lại để xem hệ thống có suy giảm hoặc xuất hiện lỗi không.

Stress

Chạy phần mềm trong điều kiện kém lý tưởng.

Load

Đẩy hệ thống đến khối lượng tối đa mà nó có thể xử lý.

State Testing vì thế không chỉ là đọc sơ đồ rồi tạo vài test case. Nó còn đòi hỏi tester suy nghĩ về những gì có thể làm gián đoạn trạng thái bình thường của hệ thống.

2. Class Definition Based Testing – Kiểm thử dựa trên định nghĩa lớp

2.1. Từ đặc tả lớp đến test case

Trong lập trình hướng đối tượng, một class thường không chỉ được mô tả bằng thuộc tính và phương thức. Các phương thức có thể đi kèm những điều kiện quy định khi nào chúng được phép thực hiện và kết quả sau khi thực hiện phải như thế nào.

Class Definition Based Testing là kỹ thuật tạo test case dựa trên specification của một class.

Một cách phổ biến để biểu diễn specification là sử dụng contract, trong đó hai khái niệm quan trọng là:

  • Precondition – điều kiện phải đúng trước khi phương thức được thực hiện.
  • Postcondition – điều kiện phải đúng sau khi phương thức thực hiện thành công.

Tài liệu sử dụng OCL – Object Constraint Language, một phần của UML – như một cách thể hiện những điều kiện này.

2.2. Ví dụ với phương thức setDirection

Tài liệu đưa ra một phương thức có điều kiện như sau:

Precondition

0 <= dir AND dir < 360

Nghĩa là giá trị direction được truyền vào phải nằm trong khoảng từ 0 đến nhỏ hơn 360.

Postcondition

direction = dir AND speed = speed@pre

Sau khi phương thức thực hiện:

  • direction phải bằng giá trị dir được truyền vào.
  • speed phải giữ nguyên giá trị trước khi gọi phương thức.

Từ đây, tester có thể bắt đầu suy ra test case thay vì chỉ nhìn vào code để đoán dữ liệu kiểm thử.

2.3. Quy trình tạo test case

Tài liệu đưa ra một quy trình khá hệ thống:

  • Xác định các thành phần của precondition.
  • Xác định các thành phần của postcondition.
  • Tạo các tổ hợp có thể từ những thành phần đó.
  • Loại bỏ những tổ hợp không có ý nghĩa.

Ví dụ với:

0 <= dir AND dir < 360

có thể nghĩ đến các trường hợp:

  • Cả hai điều kiện đều đúng.
  • Điều kiện thứ nhất đúng, điều kiện thứ hai sai.
  • Điều kiện thứ nhất sai, điều kiện thứ hai đúng.
  • Cả hai đều sai.

Tuy nhiên, không phải mọi tổ hợp logic đều có thể tồn tại trong dữ liệu thực.

2.4. Vì sao phải loại bỏ test case vô nghĩa?

Với ví dụ trên, trường hợp:

NOT(0 <= dir) AND NOT(dir < 360)

tương đương với:

dir < 0 AND dir >= 360

Không có một giá trị số nào đồng thời nhỏ hơn 0 và lớn hơn hoặc bằng 360.

Vì vậy, test case này phải bị loại bỏ. Tài liệu dùng chính ví dụ này để nhấn mạnh rằng việc tạo test case từ các bảng logic cần đi kèm kiểm tra tính khả thi của điều kiện, chứ không phải cứ có tổ hợp là giữ lại.

Đây cũng là một bài học quan trọng: test case nhiều chưa chắc tốt; test case có ý nghĩa mới quan trọng.

3. Combination Testing – Kiểm thử tổ hợp

3.1. Vì sao cần Combination Testing?

Nhiều lỗi không xảy ra khi một biến đứng riêng lẻ, mà chỉ xuất hiện khi hai hoặc nhiều biến kết hợp với nhau.

Ví dụ, một chức năng có thể hoạt động đúng khi:

  • Browser = Chrome,
  • OS = Windows,

nhưng lại lỗi khi:

  • Browser = Firefox,
  • OS = Linux.

Combination Testing được sử dụng để kiểm thử hai hoặc nhiều biến cùng lúc nhằm đánh giá khả năng tương tác giữa chúng và tăng hiệu suất của quá trình kiểm thử.

3.2. Các mức Combination Testing

Tùy mức độ bao phủ mong muốn, tài liệu chia Combination Testing thành:

  • All Singles
  • All Pairs
  • All Triples
  • All-n-tuples

Trong đó n thể hiện số biến được xem xét đồng thời.

Càng tăng n, mức độ tương tác được kiểm tra càng sâu, nhưng số lượng test case cũng tăng theo.

4. All Singles – Bao phủ từng giá trị riêng lẻ

All Singles là mức cơ bản nhất.

Mục tiêu chỉ là đảm bảo mọi giá trị của mỗi biến xuất hiện ít nhất một lần trong tập test case.

Giả sử:

  • X1 = {Y, N}
  • X2 = {1, 0}
  • X3 = {A, B, C}

Một tập All Singles có thể chỉ cần:

X1 X2 X3
Y 1 A
N 0 B
Y 1 C

Bộ test chỉ có 3 test case nhưng vẫn đảm bảo:

  • Y và N đều xuất hiện.
  • 1 và 0 đều xuất hiện.
  • A, B, C đều xuất hiện.

Điểm yếu nằm ở chỗ All Singles không đảm bảo kiểm thử sự tương tác giữa các giá trị. Vì vậy tài liệu xem đây là một dạng Combination Testing yếu.

5. Full Combination – mạnh nhưng rất đắt

Nếu muốn kiểm tra mọi tổ hợp có thể của ví dụ trên thì:

  • X1 có 2 giá trị.
  • X2 có 2 giá trị.
  • X3 có 3 giá trị.

Số test case sẽ là:

2 × 2 × 3 = 12

Full Combination cho độ bao phủ mạnh nhất vì không bỏ sót bất kỳ tổ hợp nào. Nhưng khi số biến tăng, số test case tăng cực nhanh.

Đó chính là vấn đề mà All Pairs cố gắng giải quyết.

6. All Pairs – điểm cân bằng giữa độ bao phủ và chi phí

All Pairs yêu cầu:

Mỗi giá trị của một biến phải được ghép với mỗi giá trị của mọi biến khác ít nhất một lần.

Điều đặc biệt là một test case duy nhất có thể đồng thời bao phủ nhiều cặp.

Vì thế, All Pairs không kiểm tra mọi combination đầy đủ, nhưng vẫn kiểm tra mọi interaction giữa từng cặp biến.

6.1. Ví dụ với ba biến

Tiếp tục với:

  • X1 = {Y, N}
  • X2 = {1, 0}
  • X3 = {A, B, C}

Thứ tự biến được sắp theo số lượng giá trị giảm dần:

X3 → X1 → X2

Một bảng All Pairs có thể là:

X3 X1 X2
A Y 1
A N 0
B Y 0
B N 1
C Y 1
C N 0

Chỉ với 6 test case, ta đã tạo được các cặp cần thiết giữa:

  • X3 và X1,
  • X3 và X2,
  • X1 và X2.

Tài liệu trình bày quá trình này theo từng bước: sắp xếp biến, tạo section, rồi điền các giá trị sao cho mỗi biến mới tạo được tất cả các cặp với các biến trước đó.

7. Khi số biến tăng, All Pairs không còn đơn giản

Một điểm rất hay trong tài liệu là ví dụ không dừng ở 3 biến mà tiếp tục tăng lên 4, 5 rồi 6 biến.

Khi thêm biến mới, tester không thể chỉ điền các giá trị theo thứ tự lặp đơn giản. Đôi khi phải:

  • đổi thứ tự các giá trị,
  • sửa các hàng hiện tại,
  • hoặc thậm chí thêm test case mới.

Ví dụ khi tăng đến 6 biến, việc chỉ thay đổi thứ tự không còn đủ để thỏa mãn All Pairs; tập test case phải được mở rộng.

Điểm này cho thấy Pairwise Testing tuy hiệu quả nhưng vẫn cần kiểm tra lại pair coverage, chứ không phải cứ áp dụng một mẫu điền bảng là chắc chắn đúng.

8. Vì sao Combination Testing đáng học?

Ví dụ cuối của tài liệu cho thấy sự khác biệt rất rõ.

Với 6 biến có số lượng giá trị:

2, 2, 3, 2, 2, 2

Full Combination cần:

2 × 2 × 3 × 2 × 2 × 2 = 96 test cases

Trong khi All Pairs của ví dụ chỉ cần:

8 test cases.

Đây chính là giá trị lớn nhất của Combination Testing: giảm mạnh số lượng test case nhưng vẫn giữ được mức độ kiểm tra interaction có ý nghĩa.

Ngoài All Pairs, tester còn có thể kết hợp các giá trị theo:

  • rủi ro,
  • kịch bản,
  • All Triples,
  • hoặc All-n-tuples,

tùy theo mức độ quan trọng của interaction cần kiểm tra.

🎯 Tổng kết Phần 1

Ba kỹ thuật trong phần này đại diện cho ba cách nhìn khác nhau khi thiết kế test case:

State Testing đặt câu hỏi:

Hệ thống sẽ phản ứng thế nào khi trạng thái thay đổi?

Class Definition Based Testing đặt câu hỏi:

Với những precondition và postcondition đã được đặc tả, chúng ta cần kiểm thử những trường hợp nào?

Combination Testing lại đặt câu hỏi:

Điều gì xảy ra khi nhiều biến tương tác với nhau?

Có thể ghi nhớ nhanh:

Kỹ thuật Cơ sở thiết kế test
State Testing State & Transition
Class Definition Based Testing Precondition & Postcondition
Combination Testing Interaction giữa nhiều biến

Điểm chung của cả ba không phải là cố tạo thật nhiều test case, mà là sử dụng cấu trúc của bài toán để chọn ra những test case có giá trị nhất.