chƣơng trình SXQSort.java
Khi dùng công cụ MuJava để tạo đột biến, số lượng chương trình đột biến được tạo ra là rất lớn, tốn rất nhiều thời gian để thực thi các đột biến với các dữ liệu thử (như đã phân tích ở chương 3). Do đó, chương trình sắp xếp dãy số tăng dần theo thuật toán QuickSort (SXQSort.java) được lựa chọn để kiểm thử đột biến, là chương trình đơn giản có thời gian thực thi nhanh với thời gian thực hiện trung bình O(nlog2n), trong trường hợp xấu nhất O(n2) khi dãy A đã được sắp xếp.
4.4.1. Đặc tả của chƣơng trình sắp xếp dãy số tăng dần
Với P là chương trình sắp xếp dãy số tăng dần, S là bảng đặc tả cho P như sau:
P nhận đầu vào một số nguyên N (1 N 30) và một dãy gồm N số nguyên gọi là các phần tử của dãy. 0 K e - 1 với e nào đó.
K là phần tử bất kỳ của dãy.
Chương trình P sắp xếp dãy theo thứ tự tăng dần và xuất ra dãy đã sắp xếp.
P được xem là đúng với đặc tả S nếu và chỉ nếu: với mỗi đầu vào hợp lệ, đầu ra tương ứng với đặc tả S.
4.2.2. Thuật toán của chƣơng trình SXQSort.java
Chúng ta xét dãy A gồm N phần tử A[1] .. A[N]. Giả sử v là một giá trị khóa mà ta gọi là chốt (Pivot). Ta phân hoạch dãy A[1] .. A[N] thành hai dãy con "bên trái" và "bên phải". Dãy con "bên trái" bao gồm các phần tử có khóa nhỏ hơn chốt, dãy con "bên phải" bao gồm các phần tử có khóa lớn hơn hoặc bằng chốt. Sắp xếp dãy con “bên trái” và dãy con “bên phải” thì dãy đã cho sẽ được sắp bởi vì tất cả các khóa trong dãy con “bên trái” đều nhỏ hơn các khóa trong dãy con “bên phải”. Việc sắp xếp các dãy con “bên trái” và “bên phải” cũng được tiến hành bằng phương pháp nói trên.
Với ý tưởng trên, chương trình sắp xếp dãy số tăng dần theo thuật toán QuickSort được chia làm ba module có cấu trúc như sau:
FindPivot: Chọn khóa lớn nhất trong hai phần tử có khóa khác nhau đầu tiên kể từ trái qua. Nếu dãy chỉ gồm một phần tử hay gồm nhiều phần tử có khóa bằng nhau thì không có chốt.
Partiton: Ðể phân hoạch dãy ta dùng 2 "con nháy" L và R. Trong đó L từ bên trái và R từ bên phải, ta cho L chạy sang phải cho tới khi gặp phần tử có khóa >= chốt và cho R chạy sang trái cho tới khi gặp phần tử có khóa < chốt. Tại chỗ dừng của L và R nếu L < R thì hoán vị a[L], a[R]. Lặp lại quá trình dịch sang phải, sang trái của 2 "con nháy" L và R cho đến khi L > R. Khi đó L sẽ là điểm phân hoạch, cụ thể là a[L] là phần tử đầu tiên của dãy con “bên phải”.
QuickSort: Sử dụng biến PivotIndex để lưu giữ kết quả trả về của hàm FindPivot, nếu biến PivotIndex nhận được một giá trị khác -1 thì mới tiến hành phân hoạch dãy. Ngược lại, dãy không có chốt và do đó đã có thứ tự. Biến Pivot sẽ được sử dụng để lưu giữ giá trị chốt và biến k để lưu giữ giá trị của điểm phân hoạch do hàm Partition trả về. Sau khi dãy A đã phân hoạch xong ta sẽ gọi đệ quy QuickSort cho dãy con “bên trái” A[i] .. A[k-1] và dãy con “bên phải” A[k] .. A[j].
Như vậy, để sắp xếp dãy A gồm N phần tử, ta chỉ cần gọi QuickSort(0,n-1).
4.2.3. Thiết kế các trƣờng hợp kiểm thử cho chƣơng trình SXQSort.java
Các trường hợp kiểm thử sẽ được thiết kế cho từng module của chương trình SXQSort.java, dựa trên kỹ thuật kiểm thử hộp trắng, kỹ thuật kiểm thử hộp đen, và kết hợp với sự suy đoán để tìm ra các trường hợp kiểm thử có khả năng phát hiện các lỗi có thể của chương trình.
4.2.3.1. Thiết kế các trường hợp kiểm thử cho module FindPivot
Module FindPivot có chức năng là tìm chốt cho dãy A gồm N phần tử A[1] .. A[N]. Có hai khả năng xảy ra đối với dãy A:
Dãy A không có chốt nếu dãy chỉ có một phần tử hoặc nhiều phần tử có khóa bằng nhau.
QuickSort
Partition
Dãy A có chốt thì chốt sẽ là khóa lớn nhất trong hai phần tử có khóa khác nhau đầu tiên kể từ trái qua.
Dựa trên điều kiện này, các trường hợp kiểm thử cho module FindPivot được thiết kế như sau:
STT Tên kiểm thử Đầu vào
Kết quả mong đợi
N Dãy A 1 FindPivot1 6 6 6 5 8 7 4 6 6 5 8 7 Chốt là 6 (khóa của phần tử đầu tiên) 2 FindPivot2 6 6 6 7 5 7 4 6 6 7 5 7 4 Chốt là 7 (khóa của phần tử thứ 3) 3 FindPivot3 5 9 4 1 6 7 9 4 1 6 7 Chốt là 9 (khóa của phần tử đầu tiên) 4 FindPivot4 5 2 8 2 5 5 2 8 2 5 5 Chốt là 8 (khóa của phần tử thứ 2) 5 FindPivot5 5 2 2 2 2 2 Không có chốt (vì các phần tử có khóa bằng nhau) 6 FindPivot6 1 9 Không có chốt (vì có một phần tử)
4.2.3.2. Thiết kế các trường hợp kiểm thử cho Module Partition
Đoạn chương trình phân hoạch dãy A thành hai dãy con A[i] .. A[L-1] và A[L] .. A[j] với điểm phân hoạch là L như sau:
public int Partition(int i,int j,int Pivot) { int L,R;
L = i; //1
R = j; //1
while (L <= R) { //2
while (A[L] < Pivot) //4
L++; //5
while (A[R] >= Pivot) //6
R--; //7 if (L < R) { //8 int tmp; tmp = A[L]; //9 A[L] = A[R]; //9 A[R] = tmp; //9 } } //10 return L; //3 }
Áp dụng phương pháp đường dẫn cơ sở để thiết kế các trường hợp kiểm thử cho module Partition, được thực hiện theo các bước sau:
Xây dựng đồ thị luồng điều khiển cho đoạn chương trình Partition (với
các đỉnh của đồ thị tương ứng với các dòng lệnh được đánh số ở phần chú thích của đoạn chương trình trên )
Hình 4.8 - Đồ thị luồng điều khiển của Partiton
(Đồ thị có 4 đỉnh điều kiện: 2, 4, 6, 8 )
Đối chiếu với hình 4.8, xác định độ phức tạp chu trình V(G) như sau: V(G) = P (số đỉnh điều kiện) + 1 = 4 + 1 = 5
Xác định tập cơ sở của các đường dẫn độc lập. Vì V(G) =5 nên đồ thị luồng điều khiển ở hình 4.8 có 5 đường dẫn độc lập như sau:
+ Đường dẫn 1: 1 2 3
+ Đường dẫn 2: 1 2 4 6 8 10 2 …
+ Đường dẫn 3: 1 2 4 6 8 9 10 2 …
+ Đường dẫn 4: 1 2 4 5 4 …
+ Đường dẫn 5: 1 2 4 6 7 6 …
Các dấu chấm lửng (…) phía sau các đường dẫn có nghĩa là một đường dẫn bất kỳ đi qua phần còn lại của đồ thị đều có thể chấp nhận được.
Thiết kế các trường hợp kiểm thử cho các đường dẫn 3, 4, 5 như sau:
1 2 4 5 6 7 8 9 10 3 F T T T T F F F
STT Tên kiểm thử
Đầu vào
Kết quả mong đợi
N Dãy A
1 PartitionPath3 5 15 2 9 7 3 3 2 9 7 15
Điểm phân hoạch: 4 2 PartitionPath4 6 2 2 6 1 8 7 2 2 1 6 8 7
Điểm phân hoạch: 3 3 PartitionPath5 6 6 2 8 4 7 10 4 2 8 6 7 10
Điểm phân hoạch: 2
Bảng 4.4 – Mô tả các trường hợp kiểm thử cho module Partition
(Lưu ý: Đường dẫn 1 và 2 không thể kiểm thử một mình, mà phải được kiểm thử như là một phần của các kiểm thử đường dẫn 3, 4, 5.)
4.2.3.3. Thiết kế các trường hợp kiểm thử cho Module QuickSort
Đây là module cuối cùng gọi thực hiện FindPivot, Partiton và gọi đệ quy QuickSort. Do đó, khả năng xảy ra lỗi ở module này có thể xảy ra. Vì vậy, chúng ta cần thiết kế các trường hợp kiểm thử cho module này để thực hiện việc kiểm thử tích hợp khi các module con được tích hợp lại thành module QuickSort.
Dựa vào phương pháp kiểm thử hộp đen bằng cách sử dụng phân hoạch tương đương và phân tích giá trị biên đối với các giá trị đầu vào:
Số phần tử của dãy N (1 N 30): 0, 1, 5, 8, 30, 31, chữ cái, 2.5 Dãy A:
+ Các phần tử của dãy là ngẫu nhiên
+ Các phần tử của dãy đều có giá trị bằng nhau
+ Dãy đã theo thứ tự tăng dần
+ Dãy theo thứ tự giảm dần
+ Dãy có một phần tử là chữ cái
+ Dãy có một phần tử là số thực
Từ việc phân tích các giá trị đầu vào của thuật toán sắp xếp QuickSort, chúng ta thiết kế được các trường hợp kiểm thử như sau:
STT Tên kiểm thử
Đầu vào
Kết quả mong đợi
N Dãy A 1 QuickSort1 0 [] Thất bại 2 QuickSort2 1 5 5 3 QuickSort3 5 4 5 7 2 2 2 2 4 5 7 4 QuickSort4 4 4 4 4 4 4 4 4 4 4 5 QuickSort5 1 5 10 17 30 1 5 10 17 30 6 QuickSort6 16 12 3 1 0 0 1 3 12 16 7 QuickSort7 8 8 15 6 2 5 4 12 7 2 4 5 6 7 8 12 15 8 QuickSort8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 9 QuickSort9 2 3 6 8 12 16 19 25 2 3 6 8 12 16 19 25 10 QuickSort10 17 10 8 5 4 2 1 -3 -3 1 2 4 5 8 10 17 11 QuickSort11 30 8 15 6 2 5 … 10 2 5 6 8 10 15… 12 QuickSort12 6 6 6 6 6 6… 6 6 6 6 6 6… 13 QuickSort13 2 6 8 12 …15 2 6 8 12 15… 14 QuickSort14 17 10 8 5 ….-3 -3 5 8 10 17… 15 QuickSort15 31 5 -4 3 20 1…… Thất bại 16 QuickSort16 a Thất bại 17 QuickSort17 3 5 6 a Thất bại 18 QuickSort18 2.5 Thất bại 19 QuickSort19 5 3 6 7 2 1.5 Thất bại
Bảng 4.5 – Mô tả các trường hợp kiểm thử cho module QuickSort
Như vậy, có tất cả 28 trường kiểm thử được thiết kế để kiểm thử cho chức năng của từng module và cho toàn bộ hệ thống của chương trình SXQSort.java.
Trong đó, có 22 trường hợp kiểm thử được mong đợi làm cho chương trình thành công và 6 trường hợp kiểm thử được mong đợi làm cho chương trình thất bại. Chúng ta chọn 22 trường hợp kiểm thử được mong đợi làm cho chương trình SXQSort.java thành công để thực hiện kiểm thử đột biến.
4.4.4. Kiểm thử chƣơng trình SXQSort.java với JUnit
Chương trình SXQSort.java phải được hiển thị để cung cấp các đầu ra mong muốn khi thực hiện với 22 trường hợp kiểm thử (được mong đợi làm cho chương trình thành công). Vì trong quá trình thực hiện kiểm thử đột biến, bên cạnh các đột biến tương đương, tất cả các đột biến khác được hiển thị để tạo các đầu ra không đúng so với đầu ra đúng của chương trình SXQSort.java. Nếu có bất kỳ đầu ra nào là không đúng thì chứng tỏ rằng chương trình SXQSort.java chứa lỗi. Các lỗi này nên được sữa chữa để bắt đầu quá trình kiểm thử đột biến.
Chúng ta sử dụng bộ kiểm thử JUnit (phiên bản 3.8.1) để kiểm thử chương trình SXQSort.java, với 22 trường hợp kiểm thử trên được thiết kế sẵn trong QuickSortTest.java. Kết quả kiểm thử được thể hiện ở hình 4.9.
Hình 4.9 – Kết quả kiểm thử SXQSort.java bằng JUnit
Trên hình 4.9, JUnit hiển thị thanh màu xanh. Điều này chứng tỏ rằng, chương trình SXQSort.java là một chương trình tốt (tức là chương trình không có lỗi) dưới “góc nhìn” của JUnit với dữ liệu thử được xây dựng trong 22 trường hợp kiểm thử trên.
4.4.5. Tạo và phân tích đột biến cho chƣơng trình SXQSort.java bằng MuJava
Dùng MuJava để tạo các đột biến cho chương trình SXQSort.java, thực thi các đột biến với 22 trường hợp kiểm thử được xây dựng trong SXQSortTest.Java, và báo cáo tỷ lệ đột biến của các trường hợp kiểm thử.
4.4.5.1. Tạo các đột biến
MuJava tạo ra hai loại đột biến: đột biến truyền thống và đột biến lớp tương ứng với hai loại toán tử đột biến. Nếu chúng ta chọn tất cả các toán tử đột biến mức phương thức và mức lớp, MuJava tạo ra 262 đột biến cho chương trình SXQSort.Java, trong đó có 258 đột biến truyền thống và 4 đột biến lớp. Nếu MuJava thực thi tất cả những đột biến này với 22 trường hợp kiểm thử được xây dựng trong SXQSortTest.java sẽ tốn rất nhiều thời gian, đồng thời số lượng các đột biến tương đương cũng tăng. Do đó, sẽ làm tăng chi phí kiểm thử cho chương trình SXQSort.Java.
Để giảm chi phí kiểm thử cho chương trình SXQSort.Java, chúng ta sẽ sử dụng phương pháp đột biến ràng buộc bằng cách chỉ chọn một số toán tử đột
biến mức phương thức và mức lớp trong MuJava để tạo đột biến. Trong chương trình SXQSort.Java xuất hiện các toán tử: { +, -}, {++, --}, {<=, >=, <, >, = =,!=}, {&&}. Đây là các toán tử có thể bị “viết nhầm” bởi các lập trình viên, lỗi có thể xảy ra ở những toán tử này. Ngoài ra, việc khai báo và sử dụng các biến {L, R, n, i, j, k}, {a[1], a[2], …, a[n]} có thể là không chính xác. Vì vậy, một số toán tử đột biến mức phương thức và mức lớp trong MuJava được chọn để tạo các đột biến cho chương trình SXQSort.java như sau:
Toán tử đột biến mức phƣơng thức
Toán tử Mô tả Ví dụ
AORB Thay thế các toán tử số học nhị nguyên a = b + c a = b − c AORS Thay thế các toán tử số học short – cut a ++ a −−
ROR Thay thế các toán tử quan hệ if(a < b) if(a > b) COR Thay thế các toán tử điều kiện
while(a < b) && (c > d) while(a < b) || (c > d)
Toán tử đột biến mức lớp
Toán tử Mô tả Ví dụ
JTI Thêm từ khóa this n this.n
JSI Thêm từ bổ nghĩa static
public int n; public static int n; JID Xóa khởi tạo biến thành phần
public int[] a = new int[n];
public int[] a
Bảng 4.6 – Các toán tử đột biến mức phương thức và mức lớp được chọn để tạo đột biến cho SXQSort.java
Trong thành phần Mutants Genertor của MuJava, chúng ta sẽ đánh dấu
chọn các toán tử đột biến mức phương thức: AORB, AORS, ROR, COR, chọn các toán tử đột biến mức lớp: JTI, JSI, JDI, và chọn file SXQSort.java. Sau đó, bấm nút “Generate” để tạo các đột biến cho chương trình SXQSort.java, được biểu diễn ở hình 4.10.
Hình 4.10 – Giao diện tạo đột biến cho SXQSort.java
MuJava tạo ra 43 đột biến truyền thống cho chương trình SXQSort.java, được biểu diễn ở Tab Traditional Mutants Viewer trong thành phần Mutants Genertor của MuJava.
Hình 4.11 –Hiển thị đột biến truyền thống của SXQSort.java
MuJava tạo ra 4 đột biến lớp cho chương trình SXQSort.java, được biểu diễn ở Tab Class Mutants Viewer trong thành phần Mutants Genertor của
MuJava.
Như vậy, MuJava đã tạo ra 47 đột biến cho chương trình SXQSort.java, trong đó có 43 đột biến truyền thống và 4 đột biến lớp, chỉ áp dụng các toán tử đột biến mức phương thức: AORB, AORS, ROR, COR và các toán tử đột biến mức lớp: JTI, JSI, JID.
4.4.5.2. Phân tích đột biến
Chúng ta sử dụng thành phần Mutants executor của MuJava để thực thi
chương trình SXQSort.java và 47 đột biến của nó, với 22 trường hợp kiểm thử được xây dựng trong SXQSortTest.Java, kết quả của quá trình thực thi được biểu diễn ở hình 4.13.
Hình 4.13 –Kết quả thực thi các đột biến của SXQSort.java
Đối với 4 đột biến lớp, trong quá trình MuJava thực thi với 22 trường hợp kiểm thử được thiết kế sẵn trong SXQSortTest.Java, chỉ có 1 đột biến bị diệt và 3 đột biến “còn sống”. Tỷ lệ đột biến ở đây là xấp xỉ 25%. Đối với 43 đột biến truyền thống, có 38 đột biến bị diệt và 5 đột biến “còn sống”. Tỷ lệ đột biến ở đây là xấp xỉ 88%. Cụ thể với từng trường hợp kiểm thử được thể hiện trong bảng 4.7.
STT Tên kiểm thử Số đột biến diệt đƣợc Số đột biến không diệt đƣợc 1 FindPivot1 37 10 2 FindPivot 2 38 9 3 FindPivot 3 35 12 4 FindPivot 4 36 11 5 FindPivot 5 15 32 6 FindPivot 6 14 33 7 PartitionPath3 37 10 8 PartitionPath 4 39 8 9 PartitionPath 5 38 9 10 QuickSort 2 14 33 11 QuickSort 3 39 8 12 QuickSort 4 15 32 13 QuickSort 5 28 19 14 QuickSort 6 36 11 15 QuickSort 7 38 9 16 QuickSort 8 15 32 17 QuickSort 9 28 19 18 QuickSort 10 36 11 19 QuickSort 11 39 8 20 QuickSort 12 15 32 21 QuickSort 13 28 19 22 QuickSort 14 36 11
Bảng 4.7 - Chất lượng 22 trường hợp kiểm thử cho SXQSort.java
Quá trình MuJava phân tích các đột biến của chương trình SXQSort.java cho thấy rằng: chất lượng bộ dữ liệu thử mà chúng ta tạo ra trong 22 trường hợp kiểm thử ở trên là khá cao (tỷ lệ đột biến 88%). Nó có khả năng phát hiện được
hầu hết các lỗi có thể có trong chương trình SXQSort.java. Bên cạnh đó, đối với