Kiểm thử đơn vị là mức kiểm thử nhỏ nhất để kiểm tra lại quá trình viết một hàm. Kiểm thử đơn vị động sử dụng kỹ thuật kiểm thử hộp trắng cụ thể là kiểm thử luồng điều khiển luôn đặt ra yêu cầu xây dựng bộ test case (test suite) với số lượng test case nhỏ nhất nhưng đảm bảo kiểm thử hàm một cách tốt nhất. Đo độ phủ của bộ test là một tiêu chí nhằm đánh giá chất lượng của bộ test đã viết. Chúng tôi tiến hành thực nghiệm trong hai trường hợp đó là: Trường hợp thứ nhất chúng tôi đo độ phủ với bộ test thỏa mãn tiêu chuẩn bao phủ câu lệnh. Trường hợp thứ hai chúng tôi đo độ phủ của bộ test thỏa mãn tiêu chuẩn bao phủ nhánh. Ngoài ra công cụ hỗ trợ đếm số vòng lặp khi hàm có chứa vòng lặp để hỗ trợ kiểm thử vòng lặp.
Đo độ phủ với bộ test thỏa mãn tiêu chuẩn bao phủ câu lệnh
Chúng tôi lấy ví dụ hàm đầu vào foo() và đồ thị luồng điều khiển như hình 5.10
public static float foo(int a, int b, int c, int d) {
1: if (a == 0) 2: return 0; 3: int x = 0; 4: if ((a != b) && (c != d)) 5: x = 1; 6: return 1 / x; }
Hình 5.10: Đồ thị luồng điều khiển cho hàm foo
Xây dựng bộ test case theo tiêu chuẩn bao phủ câu lệnh:
Bảng 5.1: Bộ test theo tiêu chuẩn bao phủ câu lệnh của hàm foo
ID Test Path Input EO
Tc1 1(T), 2 0,1,2,3 0
Tc2 1,3,4(T),5,6 1,2,2,3 1
Hàm foo nằm trong lớp MyMath.java. Xây dựng Test drive điều khiển quá trình kiểm thử MyTest.java
public class MyTest {
public static void main(String[] args) {
1 2 3 4 6 5
45
MyMath.foo(0,1,2,3);
MyMath.foo(1,2,2,3);
throw new MyException(""); }
- File kết quả thu được nằm trong JPF_HOME\report. Mở file MyTest.html bằng web browser thu được kết quả như hình 5.11:
Hình 5.11: Kết quả kiểm tra độ phủ của hàm foo thực thi bộ test ở bảng 5.2
Kết quả thu được từ hình 5.11 nhận thấy bộ test đã phủ 100% câu lệnh, thỏa mãn đảm bảo tiêu chuẩn phủ câu lệnh. Công cụ đã kiểm tra được độ phủ của bộ test thỏa tiêu chuẩn bao phủ câu lệnh.
Bên cạnh đó công cụ cũng chỉ ra trên đồ thị tồn tại một nhánh màu đỏ từ 4->6 tương ứng với trường hợp false của câu lệnh rẽ nhánh if((a != b) && (c != d)), nhánh này chưa được phủ hết khi thực thi hàm với bộ test thứ nhất, chứng tỏ trong hàm có thể tiềm ẩn yếu tố gây lỗi nếu chạy qua nhánh này. Công cụ đã phát hiện được nhánh chưa phủ trong hàm và cảnh báo.
Xây dựng các ca kiểm thử theo tiêu chuẩn bao phủ nhánh của hàm foo như trong bảng 5.2
Bảng 5.2: Bộ test theo tiêu chuẩn phủ nhánh của hàm foo
ID Test Path Input EO
Tc1 1(T), 2 0,1,2,3 0
Tc2 1(F),3,4(T),5,6 1,2,2,3 1
Tc3 1(F),3,4(F),6 1,1,2,3 Lỗi chia cho 0
Kết quả thu được sau khi thực thi chương trình trong hình 5.12 cho thấy khi kiểm tra độ phủ của chương trình với bộ test trong bảng 5.2 chương trình gặp lỗi chia cho 0, đây là lỗi nghiêm trọng khi viết chương trình. Nhận thấy khi
46
chương trình thực thi với test case đầu vào là foo(1,1,2,3) sẽ đi vào nhánh false của câu lệnh rẽ nhánh if((a != b) && (c != d)) chính là nhánh chưa được phủ khi dùng bộ test ở bảng 5.1. Chương trình sau đó dừng ngay và cảnh báo trong file vết MyTest.log lỗi chia cho 0. Nhờ công cụ lập trình viên đã tìm thấy một lỗi còn sót trong quá trình viết code.
Hình 5.12: Kết quả thông báo lỗi khi thực thi hàm foo với bộ test tại bảng 5.2
Đo độ phủ với bộ test thỏa mãn tiêu chuẩn bao phủ nhánh
Để minh họa cho trường hợp này chúng tôi lấy ví dụ hàm getAverage thực hiện công việc: Tính giá trị trung bình các phần tử trong mảng số nguyên thỏa mãn điều kiện các phần tử của mảng nằm trong khoảng [Min,Max] và số lượng các phần tử của mảng nhỏ hơn hoặc bằng 30. Phần tử cuối cùng của mảng có giá trị -999.
Mã nguồn hàm getAverage được viết như sau:
public static double getAverage(int value[], int MIN, int MAX) {
1: int i = 0;
2: int ti = 0;
3: int tv = 0;
4: int sum = 0;
5: double av = 0;
6: while (ti < 30 && value[i] != -999) {
47
8: tv++; sum = sum + value[i];
} 9: ti++; 10: i++; } 11: if (tv > 0) 12: av = (double) sum / tv; else 13: av = (double) -999; 14: return (av); }
Xây dựng bộ test thỏa mãn tiêu chuẩn bao phủ nhánh trong bảng 5.3
Bảng 5.3: Bộ test theo tiêu chuẩn bao phủ nhánh của hàm getAverage
ID Test Path Output EO
TC1 1,2,3,4,5,6(T),7(T),8,9,10,6(F),11(T),12,14 [5,-999],5,20 5
TC2 1,2,3,4,5,6(T),7(F),9,10,6(F),11(F),13,14 [20,-999], 10,15 -999
TC3 1,2,3,4,5,6(F),11(F),13,14 [-999] ,5 , 20 -999
Với mỗi ca kiểm thử, bộ dữ liệu đầu vào (inputs) gồm ba thành phần: mảng
int value[], giá trị nhỏ nhất int MIN, và giá trị lớn nhất int MAX. Xây dựng Test drive điều khiển thực thi bộ test case:
public class MyTest {
public static void main(String[] args) {
int value[] = {5,-999}; MyMath.getAverage(value, 5, 20); int value1[]= {20,-999}; MyMath.getAverage(value1, 10, 15); int value2[] = {-999}; MyMath.getAverage(value2, 5, 20);
throw new MyException(""); }
Kết quả thu được tại hình 5.13 với bộ test case sinh ra tại bảng 5.3 đã bao phủ hết các nhánh thỏa mãn tiêu chuẩn bao phủ nhánh (branch coverage) trong đồ thị luồng điều khiển của hàm getAverage(), tất cả các nhánh trong đồ thị đều màu xanh. Bộ test tại bảng 5.3 đã thỏa mãn tiêu chuẩn bao phủ nhánh. Công cụ đã kiểm tra được độ phủ của bộ test thỏa tiêu chuẩn bao phủ nhánh.
48
Hình 5.13: Kết quả kiểm tra độ phủ của hàm getAverage thực thi với bộ test case tại bảng 5.3
Kiểm thử vòng lặp
Vòng lặp là cấu trúc rất hay được sử dụng khi viết chương trình vì tính chất mô tả thực tế của nó. Mặc dù vậy kiểm thử vòng lặp không hề đơn giản, lỗi rất hay xảy ra ở các vòng lặp. Có nhiều loại vòng lăp như vòng lặp đơn, vòng lặp lồng nhau và vòng lặp liền kề. Mỗi loại có cách sinh các ca kiểm thử là khác nhau. Nhưng tựu chung lại kiểm thử vòng lặp đều phải xây dựng các ca kiểm thử với số lần lặp đa dạng nhằm phát hiện lỗi tiềm ẩn có thể có tại vòng lặp bất kỳ. Minh họa cho việc kiểm thử vòng lặp đơn chúng ta quay lại ví dụ của hàm
getAverage. Bộ test trong bảng 5.3 đã thỏa mãn tiêu chuẩn bao phủ nhánh đối với hàm getAverage.Thực thi bộ test case trong bảng 5.3 vòng lặp while chỉ được thực hiện tối đa một lần lặp nên rất khó để phát hiện các lỗi tiềm ẩn có thể có bên trong vòng lặp này. Các lỗi này có thể xảy ra khi vòng lặp này được thực hiện nhiều lần lặp. Đây là hạn chế của phương pháp kiểm thử luồng điều khiển khi áp dụng cho các hàm hay phương thức có chứa vòng lặp. Để giải quyết vấn đề này, chúng tôi sinh thêm 7 ca kiểm thử ứng với bảy trường hợp lặp như sau: số lần lặp bằng 0, lặp 1, lặp 2, lặp M lần trong đó M < N, lặp N-1 lần, lặp N lần, lặp N+1 lần. Tuy nhiên với hàm trên sẽ chạy tối đa 30 lần vì vậy không sinh ca kiểm thử có N+1 lần lặp, bộ test case kiểm thử cho vòng lặp while được cho trong bảng 5.4
49
Bảng 5.4: Bộ test kiểm thử vòng lặp while trong hàm getAverage
ID Số lần lặp Output EO TC1 30 [1..30,-999], 1, 30 7 TC2 29 [1..29,-999],5,9 7 TC3 4 [5,6,3,4,-999], 5,9 5.5 TC4 2 [5,9,-999],5, 9 7 TC5 1 [9,-999],5,9 9 TC6 0 [-999,5,9],5,9 -999
Chạy kiểm thử qua ứng dụng kết quả hiển thị trong hình 5.14
Hình 5.14: Kết quả kiểm thử vòng lặp while trong hàm getAverage
Kết quả thu được trong hình 5.14 nhận thấy được số lần lặp của câu lệnh while trong hàm getAverage thực thi với bộ test case tại bảng 5.5. Hàm
getAverage không xảy ra lỗi nào khi thực thi với bộ test case trên. Cũng từ kết quả trên lập trình viên có thể kiểm tra một cách nhanh chóng bộ test của mình đã đạt tiêu chuẩn kiểm thử vòng lăp hay chưa, với số lần lặp đã đạt yêu cầu đề ra chưa.
Tổng hợp các kết quả kiểm thử với công cụ
Trong mục thực nghiệm chúng tôi tiến hành kiểm thử với hàm khác nhau, chi tiết mã nguồn các hàm và bộ dữ liệu kiểm thử được trình bày trong phần phụ lục.
50
Nhóm hàm thứ nhất chúng tôi kiểm thử để đánh giá bộ dữ liệu kiểm thử có đạt tiêu chuẩn bao phủ nhánh. Chúng tôi xây dựng các bộ test thứ nhất thỏa mãn tiêu chuẩn bao phủ câu lệnh, bộ test thứ hai thỏa mãn tiêu chuẩn bao phủ nhánh. Chúng tôi sử dụng công cụ kiểm tra độ phủ với bộ test thứ nhất có thỏa mãn tiêu chuẩn bao phủ câu lệnh không, sau đó kiểm tra với bộ test thứ hai thỏa mãn tiêu chuẩn bao phủ nhánh, kết quả tổng hợp trong bảng 5.5.
Kết quả trong bảng 5.5 cho thấy tại cột 2 là phần trăm bao phủ của bộ test thứ nhất thỏa mãn tiêu chuẩn bao phủ câu lệnh, tại cột 3 thể hiện tỉ lệ phủ của bộ dữ liệu thỏa mãn tiêu chuẩn bao phủ câu lệnh so với tiêu chuẩn bao phủ nhánh và cột 4 là phần trăm bao phủ khi thực thi với bộ test thứ 2 thỏa mãn tiêu chuẩn bao phủ nhánh.
Bảng 5.5: Kết quả thực nghiệm đánh giá độ phủ của bộ dữ liệu
Danh sách hàm Bộ test bao phủ câu lệnh
Tỉ lệ phủ so với tiêu chuẩn bao
phủ nhánh Bộ test phủ nhánh foo 100% 3/4 (75%) 100% sumodd 100% 3/4 (75%) 100% triangle 100% 4/4 (100%) 100% maxsum 100% 7/8 (87.5%) 100% search 100% 3/4 (75%) 100%
Nhóm hàm thứ hai sử dụng để kiểm thử vòng lặp, chúng tôi xây dựng bộ test kiểm thử cho các vòng lặp thỏa mãn số lần lặp là không, một, hai, k, n-1 và n lần. Sử dụng công cụ kiểm tra độ phủ với bộ test, kết quả tổng hợp trong bảng 5.6.
Bảng 5.6: Kết quả thực nghiệm kiểm thử vòng lặp
Hàm getAverage sumodd search maxsum
Lặp 0 lần Lặp 1 lần Lặp 2 lần Lặp k lần Lặp n-1 lần Lặp n lần (n=30) (n=50) Số lần lặp
51
Kết quả trong bảng 5.6 cho thấy các vòng lặp trong các hàm đã thực thi số lần lặp theo tiêu chuẩn xây dựng bộ test cho vòng lặp, trong quá trình kiểm thử thực tế các hàm không có lỗi xảy ra.
Kết quả tổng hợp thực nghiệm cho thấy (i) công cụ đã kiểm tra được độ phủ của các bộ dữ liệu kiểm thử thỏa tiêu chuẩn bao phủ câu lệnh và bao phủ nhánh nhằm phát hiện nhánh chưa phủ trong hàm (ii) kiểm tra được số lần thực hiện vòng lặp trong bộ test kiểm tra vòng lặp.
52
KẾT LUẬN
Trong luận văn, chúng tôi đã tìm hiểu lý thuyết cơ bản về kiểm chứng phần mềm, một trong những công việc quan trọng giúp phát hiện và sửa lỗi nhằm đảm bảo chất lượng phần mềm. Hiện nay có rất nhiều công cụ hỗ trợ kiểm chứng phần mềm tuy nhiên trong luận văn chúng tôi đã đi vào tìm hiểu công cụ Java PathFinder (JPF). JPF là một máy ảo Java (JVM) được sử dụng để phát hiện lỗi trong các chương trình Java. JPF lưu vết và phân tích tất cả các đường đi thực thi của một chương trình Java để tìm ra sự vi phạm như khóa chết hay các ngoại lệ chưa được xử lý. Từ những nghiên cứu về JPF giúp chúng tôi nhận thấy có thể sử dụng JPF như một công cụ hỗ trợ kiểm tra độ phủ trong các hàm các phương thức ở mức kiểm thử đơn vị khi sử dụng tính năng trong lớp CoverageAnalyzer. Kết quả thu được đo số điều kiện đã được phủ trong hàm, tuy nhiên kết quả này không thực sự trực quan. Vì vậy phần tiếp theo trong luận văn chúng tôi đã đề xuất phương pháp kiểm tra độ phủ bằng cách xây dựng một công cụ tự động kiểm tra độ phủ có sử dụng tính năng lưu lại vết thực thi mã nguồn của JPF với các ca kiểm thử theo tiêu chuẩn bao phủ câu lệnh, bao phủ nhánh. Công cụ xây dựng đã hiển thị kết quả kiểm thử một cách trực quan giúp lập trình viên đánh giá lại bộ dữ liệu kiểm thử cũng như mã nguồn đã viết.
Tuy nhiên, chúng tôi cũng gặp một số khó khăn trong quá trình xây dựng công cụ. Thứ nhất, khi sử dụng JPF để ghi lại quá trình thực thi chương trình, JPF không hỗ trợ ghi lại đến từng điều kiện con trong một điều kiện phức tạp có chứa các phép toán logic mà chỉ ghi lại đã kiểm tra toàn bộ điều kiện phức tạp đó. Vì vậy công cụ chỉ dừng lại ở khả năng kiểm tra độ phủ sử dụng tiêu chuẩn bao phủ nhánh mà chưa đạt đến tiêu chuẩn cao hơn như bao phủ điều kiện. Thứ hai, công cụ có giao diện dòng lệnh nên chưa thực sự tiện ích cho người dùng.
Từ những tồn tại của luận văn trong tương lai chúng tôi sẽ tiếp tục nghiên cứu nhằm hoàn thiện công cụ kiểm tra độ phủ trong kiểm thử đơn vị. Hướng phát triển gần là chúng tôi sẽ chỉnh sửa chương trình để có giao diện thân thiện hơn với người dùng. Xa hơn, chúng tôi sẽ nghiên cứu xử lý để ứng dụng có thể kiểm tra độ phủ tiêu chuẩn cao hơn.
53
TÀI LIỆU THAM KHẢO
Tiếng Việt
[1] Nguyễn Ngọc Hùng, Trương Anh Hoàng, Đặng Văn Hưng (2014). Giáo trình kiểm thử phần mềm
Tiếng Anh
[2] Beizer, B.(1990). Software Testing Techniques. Boston, International Thompson Comtuter Press
[3] Cem Kaner, Jack Falk, Hung Quoc Nguyen(1999), Testing Computer Software, John Weley&Sons, Inc.,p. 27-141
[4] Jiantao Pan(1999), Japan- Software Testing, Carnegeie Mellon University [5] E.Yourdon(1979), Structured Walkthroughs, Prentice-Hall, Englewood Cliffs, NJ
[6] John Wiley & Sons. (2008), Inc, Software Testing and Quality Assurance- Theory and Practice, Hoboken, New Jersey
[7] B. Hailpern and P. Santhanam(2002). Software Debugging, Testing and Verification. IBM Systems Journal.
[8] The Standish Group(2000). CHAOS Report. The Standish Group.
[9] R. S. Pressman(2001). Software Engineering, A Practitioner’s Approach, 5th edtion. Thomas Casson.
[10] RTI Health Social and Economics Research(2002). The Economic Impacts of Inadequate Infrastructure for Software Testing. Final Report. NIST - National Institute of Standards and Technology.
[11] Ian Sommerville(2004). Software Engineering, 7th edtion. Thomas Casson. [12] Brat, Guillaume, Havelund, Klaus, Park, SeungJoon, Visser, Wille (2000),
Java PathFinder: A Tool for Verifying and Valiadating.
[13] Arthur H. Watson and Thomas J. McCabe (1996), Structured testing: A testing methodology using the cyclomatic complexity metric, NIST Special Publication 500-235, National Institute of Standards and Technology, Computer Systems Laboratory, NIST, Gaithersburg, MD 20899-0001.
[14] E. Gamma, R. Helm, R. Johnson, and J. M. Vlissides (1994). Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley.
54
Website
[15]http:/ /babelfish.arc.nasa.gov/
55
PHỤ LỤC
Hàm kiểm tra ba cạnh a, b, c có là ba cạnh tam giác hay không.
publicstaticint triangle(float a, float b, float c) { 1: int t = -1; 2: if (a <= 0 || b <= 0 || c <= 0) 3: return t; 4: if (a + b > c && b + c > a && a + c > b) 5: return 1; 6: return t; }
Bộ test thỏa mãn tiêu chuẩn bao phủ câu lệnh và bao phủ nhánh
TC 1: Đường đi 1->2(F)->4(F)->6 Dữ liệu kiểm thử: a=1, b=2, c=3 TC 2: Đường đi 1->2->3(T)
Dữ liệu kiểm thử: a=2, b=0, c=3 TC 3: Đường đi 1->2(F)->4(T)->5
Dữ liệu kiểm thử: a=4, b=2, c=3 Hàm maxsum
publicstaticint maxsum(int max, int x) { 1: int kq = 0, i = 0; 2: if (x < 0) 3: x = -x; 4: while (i < x && kq <= x) { 5: i++; 6: kq = kq + i; } 7: if (kq < max) 8: return kq; 9: return -1; }
Bộ test thỏa mãn tiêu chuẩn bao phủ câu lệnh
TC 1: Đường đi 1-> 2(T)-> 3-> 4(T)-> 5-> 6->4-> 7-> 8 Dữ liệu kiểm thử: max=10, x=-1
TC 2: Đường đi 1-> 2(F)-> 3-> 4(T)-> 5-> 6-> 4->7-> 9 Dữ liệu kiểm thử: max=0, x=-1
56
Bộ test thỏa mãn tiêu chuẩn bao phủ nhánh
TC 1: Đường đi 1-> 2(F)-> 4(T)-> 5-> 6-> 4-> 7-> 8 Dữ liệu kiểm thử: max=10, x=3
TC 2: Đường đi 1-> 2(F)-> 3-> 4(T)-> 5-> 6-> 4->7-> 9 Dữ liệu kiểm thử: max=1, x=-1
Bộ test kiểm thử vòng lặp while trong hàm maxsum
TC 1: Đường đi 1-> 2(F)-> 4(F)-> 7-> 8 Dữ liệu kiểm thử: max=15, x=0 Số lần lặp 0
TC 2: Đường đi 1-> 2(T)-> 3-> 4(T)-> 5-> 6-> 7-> 8 Dữ liệu kiểm thử: max=15, x=-1
Số lần lặp 1
TC 3: Đường đi 1-> 2(F)-> 4(T)-> 5-> 6-> 4->7-> 8 Dữ liệu kiểm thử: max=15, x=2
Số lần lặp 2
TC 4: Đường đi 1-> 2(F)-> 4(T)-> 5-> 6-> 4-> 7-> 8