3.4. Biên dịch và thực thi dữ liệu kiểm thử trong môi trường chạy
Phần này trình bày hàm execute() tại dòng 10 của thuật toán LDFS. Hàm execute()
biên dịch và thực thi dữ liệu kiểm thử (solution) để tính danh sách các câu lệnh thật sự được đi qua.
Chú ý rằng, từ một đường thi hành sinh từ đồ thị CFG, một bộ dữ liệu kiểm thử cần được tìm kiếm thỏa mãn đi qua đường thi hành đó. Để tìm được bộ giá trị này, đường thi hành được biểu diễn về hệ ràng buộc bằng cách áp dụng kĩ thuật thực thi tượng trưng. Sau đó, SMT-Solver giải hệ ràng buộc này để sinh dữ liệu kiểm thử đi qua đường thi hành đó. Tuy nhiên, dữ liệu kiểm thử này chưa chắc sẽ đi qua đường thi hành ban đầu bởi vì hai lí do. Thứ nhất, quá trình thực thi tượng trưng là một bước phân tích mã nguồn khá phức tạp và có thể có nhiều sai sót. Thứ hai, trường hợp một câu lệnh chưa được hỗ trợ trong bước quá trình thực thi tượng trưng, hệ ràng buộc chỉ sinh ra từ câu lệnh đầu tiên đến trước câu lệnh hiện tại. Điều đó có nghĩa là, nghiệm của hệ ràng buộc không chắc chắn đi qua đường thi hành ban đầu mà chỉ đi qua một phần. Bởi vì hai lý do này, dữ liệu kiểm thử sinh ra cần được thực thi thật sự trong trình biên dịch để thu thập chính xác danh sách câu lệnh được đi qua.
Bộ thực thi dữ liệu kiểm thử của một hàm (test driver) cung cấp cơ chế khởi tạo giá trị các biến truyền vào hàm và chạy chương trình dưới bộ giá trị đó. Về bản chất, bộ thực thi dữ liệu kiểm thử này là một hàm main() – điểm bắt đầu chạy chương trình. Trong hàm main() này, danh sách biến truyền vào hàm được khởi tạo. Sau đó, hàm cần kiểm thử được mô tả lời gọi ở cuối cùng hàm main(). Cuối cùng, bộ thực thi dữ liệu kiểm thử này được đặt trong dự án và chạy tự động.
Sau khi bộ thực thi dữ liệu kiểm thử này chạy xong, danh sách các câu lệnh thực thi được thu thập. Kế tiếp, đồ thị CFG của hàm cần kiểm thử được cập nhật trạng thái độ phủ, cũng một trạng thái viếng thăm của các cạnh/câu lệnh/điều kiện con (dòng 11 thuật toán LDFS). Chú ý rằng, nếu độ phủ đồ thị CFG đạt được tại bước này đạt 100% thì quá trình sinh dữ liệu kiểm thử kết thúc. Ngược lại, sinh dữ liệu kiểm thử tiếp tục được sinh ra sao cho đi qua các cạnh/câu lệnh/điều kiện con chưa được viếng thăm.
3.5. Tối ưu hóa pha sinh dữ liệu kiểm thử
Để tăng tốc thời gian sinh dữ liệu kiểm thử, nhiều kĩ thuật khác nhau được áp dụng có thể kể đến kĩ thuật đơn giản hóa hệ ràng buộc, kĩ thuật biên dịch và thực thi dữ liệu kiểm thử cải tiến. Tư tưởng các kĩ thuật này được trình bày sau đây.
3.5.1. Đơn giản hóa hệ ràng buộc
Kích thước của hệ ràng buộc có thể khá lớn, và cấu trúc khá phức tạp làm tăng thời gian giải hệ ràng buộc. Điều đó dẫn đến bài toán tối ưu hệ ràng buộc trước khi sử dụng SMT-Solver để giải hệ ràng buộc đó. Phương pháp đề xuất áp dụng một vài kĩ thuật tối ưu hóa hệ ràng buộc sau đây để giảm thiểu thời gian sử dụng bộ giải SMT- Solver. Trong đó, hai kĩ thuật chính được sử dụng gồm kĩ thuật giải hệ ràng buộc tăng dần và kĩ thuật kiểm tra tính thỏa mãn dựa trên bộ nhớ đệm.
Hiện tại, nhiều kĩ thuật tối ưu hệ ràng buộc đã được đề xuất. Kĩ thuật tối ưu đầu tiên đề xuất trong CUTE, EXE, KLEE, và CAUT thu gọn hệ ràng buộc cần giải bằng cách loại bỏ các hệ ràng buộc không cần thiết. Tư tưởng chính của kĩ thuật này là chỉ những hệ ràng buộc liên quan đến hệ ràng buộc phủ định cuối cùng được giải. Kĩ thuật này gọi là kĩ thuật giải hệ ràng buộc tăng dần (incremental solving technique). Kĩ thuật tối ưu hệ ràng buộc thứ hai đề xuất bởi Cristian Cadar và cộng sự được gọi là kĩ thuật kiểm tra tính thỏa mãn dựa trên bộ nhớ đệm (cache-based unsatisfiability check). Kĩ thuật này được áp dụng trong EXE and KLEE. Theo như tư tưởng kĩ thuật này, tất cả các hệ ràng buộc đã được giải đều được lưu lại trong bộ nhớ. Tính luận lý của hệ ràng buộc kế tiếp được xác định nhanh qua đánh giá tập các hệ ràng buộc trước đó. Cụ thể, giả sử trong bộ nhớ lưu hai hệ ràng buộc đã được giải gồm A, B. Trong đó, hệ ràng buộc A có nghiệm, hệ ràng buộc B vô nghiệm. Cho hệ ràng buộc C, tính luận lý hệ ràng buộc này được kiểm tra nhanh như sau. Nếu 𝐴 ⊂ 𝐶 thì hệ ràng buộc C có thể có nghiệm, và nghiệm của A có thể là một phần nghiệm trong C. Nếu 𝐵 ⊂ 𝐶 thì hệ ràng buộc C vô nghiệm, tức hệ ràng buộc C không cần đưa vào bộ giải SMT-Solver để tìm nghiệm.
Bên cạnh hai phương pháp chính nêu trên, phương pháp đề xuất áp dụng một vài kĩ thuật đơn giản khác như kĩ thuật đơn giản hóa biểu thức (ví dụ: x+0 > 1 đơn giản hóa thành x >1), kĩ thuật suy biến nhanh giá trị biến (ví dụ: x+1=10 đơn giản
hóa thành x=9), kĩ thuật loại bỏ hệ ràng buộc hiển nhiên (ví dụ: x<10, x==5 đơn giản hóa thành x==5), v.v.
3.5.2. Tăng tốc thời gian biên dịch và thực thi dữ liệu kiểm thử
Theo như phương pháp truyền thống, sau khi từng bộ dữ liệu kiểm thử sinh ra, một bộ thực thi dữ liệu kiểm thử đó được tạo. Sau đó, bộ thực thi dữ liệu kiểm thử này được biên dịch và chạy để lấy tập câu lệnh đi qua. Vì thế, nếu có n bộ dữ liệu kiểm thử sinh ra thì quá trình biên dịch và chạy được tiến hành n lần. Phương pháp đề xuất tăng tốc thời gian chạy các dữ liệu kiểm thử sinh ra bằng cách xây dựng bộ thực thi dữ liệu kiểm thử tổng quát cho mọi loại giá trị đầu vào. Bởi thế, quá trình biên dịch và chạy nêu trên có thể rút ngắn xuống 1 lần biên dịch và n lần chạy dữ liệu kiểm thử.
Hình 3.8. Kĩ thuật tạo bộ thực thi ca kiểm thử tổng quát.
Hình 3.8 trình bày quá trình xây dựng bộ thực thi dữ liệu kiểm thử tổng quát. Đầu vào gồm hàm cần kiểm thử đã được chèn câu lệnh đánh dấu (instrumented function) và tệp lưu dữ liệu kiểm thử (test data external file). Đầu ra là mã nguồn bộ thực thi dữ liệu kiểm thử tổng quát cho mọi loại giá trị đầu vào gồm biến kiểu cơ bản (số nguyên, số thực, kí tự), biến mảng, biến con trỏ, biến kiểu dẫn xuất (class, struct). Quá trình tạo bộ mã nguồn tổng quát gồm ba pha như sau. Trong pha đầu tiên, khung xương bộ thực thi dữ liệu kiểm thử được tạo chứa đầy đủ các hàm cần thiết cho hai pha sau (ví dụ: hàm đọc dữ liệu dữ liệu kiểm thử từ tệp, v.v.). Pha thứ hai tạo mã nguồn nạp dữ liệu biến kiểu cơ bản từ tệp lưu dữ liệu kiểm thử dựa trên khung mã nguồn tạo ở pha trước. Pha cuối cùng tạo mã nguồn đọc biến kiểu dẫn xuất từ tệp dữ liệu kiểm thử. Trong pha cuối này, tham số độ sâu depth xác định độ sâu tối đa khi duyệt danh sách liên kết để tránh trường hợp duyệt danh sách liên kết vô hạn. Một
Kĩ thuật tăng tốc quá trình thực thi dữ liệu kiểm thử thể hiện ưu điểm rõ ràng khi số lượng bộ dữ liệu kiểm thử thỏa mãn tiêu chí độ phủ đủ lớn. Tất cả mọi dữ liệu kiểm thử sinh ra từ đường thi hành đều được lưu trong một tệp cố định ở ngoài. Sau đó, bộ thực thi dữ liệu kiểm thử tổng quát đọc tệp này để tự động khởi tạo giá trị các biến.
3.6. Xuất mã nguồn kiểm thử theo chuẩn Google Test
Google Test4 là một famework được dùng rộng rãi trong các công ty phần mềm C/C++ để viết unit test. Sau khi tập dữ liệu kiểm thử được sinh ra bởi thuật toán LDFS, tập mã nguồn kiểm thử theo chuẩn Google Test được tạo ra tự động. Điều này đặc biệt có ý nghĩa vì lập trình viên không cần tốn thời gian viết unit test với tập dữ liệu kiểm thử. Từ đó chi phí phát triển phần mềm giảm đi, đặc biệt đối với các dự án lớn khi số lượng bộ dữ liệu kiểm thử lên tới hàng chục nghìn, thậm chí hàng trăm nghìn.
Cấu trúc của một bộ mã nguồn kiểm thử theo chuẩn framework Google Test như sau. Phần đầu tiên sẽ nạp chỉ thị tiền xử lý header của framework này để có thể sử dụng các hàm hỗ trợ bởi Google Test. Phần thứ hai là danh sách các hàm, trong đó mỗi hàm tương ứng với một bộ dữ liệu kiểm thử. Cụ thể, nếu 3 bộ dữ liệu kiểm thử được sinh ra thì số lượng mã nguồn kiểm thử Google Test cần sinh tự động tương ứng bằng 3. Cấu trúc một hàm gồm ba phần. Phần đầu tiên chứa mã nguồn khởi tạo giá trị các biến với các giá trị lưu trong dữ liệu kiểm thử. Phần thứ hai là lời gọi đến hàm cần kiểm thử. Các tham số truyền vào hàm này là danh sách biến được ở tạo ở phần đầu tiên. Phần cuối cùng là phép so sánh đầu ra mong muốn (expected output) với đầu ra thực tế (real output).
CÔNG CỤ VÀ THỰC NGHIỆM
Chương này trình bày tổng quan kiến trúc công cụ CFT4Cpp được xây dựng dựa trên phương pháp đề xuất. Bên cạnh đó, thực nghiệm so sánh với KLEE, CAUT, PathCrawler, CREST theo tiêu chí độ phủ và số bộ dữ liệu kiểm thử để chứng minh tính hiệu quả phương pháp. Ngoài ra, thực nghiệm cũng thể hiện tính hiệu quả về tiêu chí thời gian thực thi dữ liệu kiểm thử so với phương pháp thực thi dữ liệu kiểm thử truyền thống. Cuối cùng, kết quả sinh tập dữ liệu kiểm thử kiểm tra tính đúng đắn vòng lặp được trình bày chi tiết trong thực nghiệm.
4.1. Giới thiệu công cụ kiểm thử tự động CFT4Cpp
Để chứng minh tính hiệu quả của phương pháp đề xuất, công cụ CFT4Cpp5 được phát triển trên ngôn ngữ Java. Công cụ CFT4Cpp cung cấp một giải pháp sinh dữ liệu kiểm thử tự động khá hiệu quả cho các dự án viết bằng C/C++. Phiên bản hiện tại của CFT4Cpp hỗ trợ chạy trên môi trường Window đã cài sẵn JDK 8 trở lên và trình biên dịch MingW64.