Kiểm thử là quá trình kiểm tra chương trình với mục đích phát hiện lỗi. Kiểm thử phần mềm cần nhiều thời gian và chi phí của dự án, thông thường chiếm 50% chi phí của dự án và 35% tổng thời gian phát triển phần mềm. Bài viết này tóm tắt các kỹ thuật chính trong việc sinh dữ liệu kiểm thử tự động từ mã nguồn và một số hướng nghiên cứu cải tiến.
Trang 1TỔNG QUAN VỀ PHƯƠNG PHÁP
SINH DỮ LIỆU KIỂM THỬ TỰ ĐỘNG
TỪ MÃ NGUỒN
Trần Nguyên Hương
Trường Cao đẳng Sư phạm Trung ương
Email: huongtw@gmail.com
Tóm tắt: Kiểm thử là quá trình kiểm tra chương trình với mục đích phát hiện lỗi Kiểm thử phần mềm cần nhiều thời gian và chi phí của dự án, thông thường chiếm 50% chi phí của dự án và 35% tổng thời gian phát triển phần mềm Bước quan trọng của kiểm thử phần mềm là tự động sinh các bộ dữ liệu kiểm thử từ mã nguồn một cách tối ưu dựa trên các tiêu chuẩn cho trước Bài báo này tóm tắt các kỹ thuật chính trong việc sinh dữ liệu kiểm thử tự động từ mã nguồn và một số hướng nghiên cứu cải tiến.
Từ khóa: Kiểm thử phần mềm, sinh dữ liệu kiểm thử tự động, kiểm thử tĩnh, kiểm thử động, mã nguồn.
Vũ Mạnh Điệp Trường Cao đẳng Sư phạm Trung ương Email: diepvm@gmail.com
I TỔNG QUAN VỀ SINH DỮ LIỆU KIỂM
THỬ DỰA TRÊN MÃ NGUỒN
Trong quá trình phát triển phần mềm, kiểm
thử là một giai đoạn quan trọng và thực sự
cần thiết để tạo ra phần mềm có chất lượng
cao Có nhiều mức kiểm thử trong giai đoạn
này, bao gồm kiểm thử đơn vị, kiểm thử tích
hợp, kiểm thử hệ thống và kiểm thử chấp
nhận Trong các mức trên thì kiểm thử đơn
vị (unit test) là một trong những pha quan
trọng nhất để đảm bảo chất lượng của phần
mềm Hai phương pháp được sử dụng phổ
biến trong kiểm thử đơn vị gồm kiểm thử
hộp đen (black-box testing) và kiểm thử hộp
trắng (white-box testing) Kiểm thử hộp đen
chỉ kiểm tra tính đúng đắn của đầu ra với đầu
vào cho trước mà không quan tâm đến mã
nguồn chương trình Ngược lại, phương pháp kiểm thử hộp trắng đánh giá chất lượng mã nguồn bằng cách sử dụng các kĩ thuật phân tích mã nguồn Do kiểm thử hộp trắng đi sâu vào phân tích mã nguồn nên kĩ thuật này cho phép phát hiện các lỗi tiềm ẩn mà kiểm thử hộp đen không phát hiện được Tuy nhiên, chi phí kiểm thử hộp trắng lớn hơn rất nhiều so với kiểm thử hộp đen Đặc biệt, trong các dự
án công nghiệp, chi phí kiểm thử hộp trắng
có thể chiếm hơn 50% tổng chi phí phát triển phần mềm Nguyên nhân của tình trạng này
là do số lượng hàm cần kiểm thử lên tới hàng nghìn, thậm chí hàng chục nghìn Kết quả là chi phí để kiểm thử hết những hàm này khá lớn, ảnh hưởng khá nhiều đến tốc độ phát triển dự án Vì thế, quá trình kiểm thử hộp
Trang 2trắng cần được tự động hóa để giải quyết bài
toán về chi phí Hiện nay, đa số quá trình thực
hiện tự động hóa đều tập trung vào việc thực
thi ca kiểm thử (test case) mà không quan tâm
đến việc thiết kế ca kiểm thử (việc phát hiện lỗi
phần mềm phụ thuộc chủ yếu vào chất lượng
các ca kiểm thử) Hai thành phần chính trong
thiết kế ca kiểm thử là thiết kế dữ liệu kiểm
thử và kết quả đầu ra mong đợi (expected
output) Tuy nhiên, thiết kế các kết quả đầu ra
mong đợi là khó khăn, khó tự động hóa Do
vậy trong thiết kế ca kiểm thử người ta quan
tâm nhiều đến sinh dữ liệu kiểm thử
Cho đến nay, có hai kĩ thuật chính để
sinh dữ liệu kiểm thử là kĩ thuật kiểm thử tĩnh
(static testing) và kiểm thử động (dynamic
testing) Điểm chung của các kĩ thuật là sử
dụng kĩ thuật thực thi tượng trưng (symbolic
execution) và sinh dữ liệu kiểm thử qua giải
hệ ràng buộc sử dụng kĩ thuật sinh ngẫu
nhiên hoặc bộ giải SMT-Solver Kĩ thuật thực
thi tượng trưng, nêu trong do James C King
giới thiệu lần đầu tiên vào năm 1976, sau đó
đã có một số cải tiến trong [5][6] và là một
kĩ thuật phổ biến để sinh dữ liệu kiểm thử tự
động Trong bài toán sinh dữ liệu kiểm thử,
từ đầu vào là đường thi hành, kỹ thuật này sẽ
thay thế các giá trị đầu vào cụ thể bằng các
giá trị tượng trưng để đại diện cho một miền
các mà hành vi chương trình là như nhau
Tư tưởng chính của kỹ thuật kiểm thử
tĩnh là sinh dữ liệu kiểm thử bằng phân tích
mã nguồn (không thực thi mã nguồn) sử
dụng kĩ thuật thực thi tượng trưng Quy trình
thực hiện như sau: (1) mã nguồn được phân
tích và chuyển thành đồ thị dòng điều khiển
(control flow graph - CFG) theo tiêu chuẩn
bao phủ (coverage criteria) cho trước; (2) sinh
các đường kiểm thử (test path) bằng cách
duyệt đồ dòng điều khiển; (3) sinh ra hệ ràng
buộc từ đường kiểm thử; (4) sinh dữ liệu kiểm
thử (ngẫu nhiên hoặc sử dụng bộ giải
SMT-solver) Các bước (2), (3), (4) được lặp lại cho
đến khi đạt tiêu chí độ phủ hoặc đã duyệt hết
các đường kiểm thử
Kỹ thuật kiểm thử tĩnh có ưu điểm là tốc
độ thực thi nhanh so với kỹ thuật kiểm thử động, số dữ liệu kiểm thử sinh ra ít (đặc biệt là trong trường hợp chương trình có vòng lặp) Tuy nhiên có hạn chế là độ phức tạp cao vì phải phân tích toàn bộ mã nguồn, kỹ thuật này khó áp dụng cho các dự án công nghiệp bởi
vì hỗ trợ tất cả mọi cú pháp là điều không thể Trái ngược với kỹ thuật kiểm thử tĩnh, kỹ thuật kiểm thử động không yêu cầu phải phân tích mọi cú pháp của chương trình để sinh dữ liệu kiểm thử Để giảm chi phí phân tích mã nguồn mà vẫn đạt độ phủ cao, kỹ thuật kiểm thử động kết hợp quá trình phân tích cú pháp chương trình với trình biên dịch [1] [2] [3] [10] Bởi thế, kỹ thuật kiểm thử động dễ dàng đạt được độ phủ cao mà không cần phải phân tích chương trình nhiều
Kỹ thuật kiểm thử động gồm hai kĩ thuật kiểm thử được sử dụng phổ biến gồm kĩ thuật EGT (execution generated testing) và kĩ thuật kiểm thử tự động định hướng (concolic testing):
Kĩ thuật EGT được áp dụng trong công cụ sinh dữ liệu kiểm thử tự động nổi tiếng KLEE [2] – một công cụ được đánh giá cao bởi tính hiệu quả của nó Tư tưởng chính của kĩ thuật EGT là vừa chạy chương trình vừa sinh dữ liệu kiểm thử trực tiếp Chẳng hạn, khi gặp một điều kiện (điểm quyết định trên đồ thị CFG),
dữ liệu kiểm thử tương ứng nhánh đúng và nhánh sai của điều kiện này được sinh ra Tại đây, với mỗi dữ liệu kiểm thử, một tiến trình mới được tạo ra sẽ phân tích chương trình theo nhánh đúng/sai đó Quá trình sinh dữ liệu kiểm thử chỉ dừng khi một trong ba điều kiện sau thỏa mãn (i) đạt độ phủ tối đa (ii) không còn nhánh đúng/sai nào để phân tích tiếp, (iii) đạt đến giới hạn thời gian cho phép Nhược điểm chính của kĩ thuật EGT là hiệu suất thấp khi kiểm thử hàm chứa vòng lặp có số lần lặp lớn, hoặc chứa lời gọi đệ quy Khi đó, số tiến
Trang 3trình được tạo ra có thể từ hàng trăm tới hàng
nghìn Kĩ thuật này thể hiện tính hiệu quả khi
tìm các lỗi tiềm ẩn trong chương trình bởi vì
EGT xem xét mọi trường hợp có thể xảy ra
Tuy nhiên, kĩ thuật EGT không phù hợp với
bài toán sinh dữ liệu kiểm thử đạt độ phủ tối
đa bởi vì chúng ta không cần xem xét hết mọi
trường hợp khi sinh dữ liệu kiểm thử
Kĩ thuật kiểm thử tự động định hướng
được đề xuất vào năm 2005 và cài đặt trong
công cụ DART [3] Tư tưởng của kĩ thuật
kiểm thử tự động định hướng là sinh dữ liệu
kiểm thử kế tiếp từ các dữ liệu kiểm thử trước
đó Sau này, kĩ thuật kiểm thử tự động định
hướng liên tục được cải tiến trong các công
cụ PathCrawler [10], CUTE [4], CAUT [8][9],
và CREST [1] Quy trình kiểm thử tự động
định hướng do Koushik Sen cùng các cộng
sự đề xuất DART [3] gồm các bước như sau:
Bước 1: Chèn các câu lệnh đánh dấu vào
hàm cần kiểm thử (instrument function) Các
câu lệnh đánh dấu giúp xác định được danh
sách câu lệnh được thực thi khi chạy chương
trình
Bước 2: Sinh ngẫu nhiên một bộ dữ liệu
kiểm thử đầu tiên dựa trên tham số truyền vào
hàm (kiểu cơ sở, con trỏ, mảng, dẫn xuất)
Bước 3: Thực thi chương trình với dữ liệu
kiểm thử vừa tìm được Nếu không thực thi
được (lỗi xảy ra) thì quay lại bước 2 để sinh
bộ giá trị khác
Bước 4: Tìm tập các câu lệnh đã được đi
qua với dữ liệu kiểm thử ở bước 3 (đường thi
hành – test path) để xây dựng được hệ ràng
buộc tương ứng
Bước 5: Tính độ phủ đạt được với dữ liệu
kiểm thử mới nhất Nếu độ phủ đạt được tối
đa hoặc hết thời gian, quá trình sinh dữ liệu
kiểm thử kết thúc Ngược lại sang bước 6
Bước 6: Phủ định hệ ràng buộc thu được
ở bước 4 để sinh các hệ ràng buộc mới có tác
dụng sinh các dữ liệu kiểm thử kế tiếp Nếu không thể sinh hệ phủ định nào khác, thuật toán kết thúc
Bước 7: Giải hệ ràng buộc thu được ở bước 6 để sinh dữ liệu kiểm thử kế tiếp Nếu không có dữ liệu kiểm thử nào thỏa mãn, quay
về bước 6 để tìm hệ ràng buộc phủ định mới sao cho khác hệ ràng buộc hiện tại Ngược lại, quay lại bước 3 để chạy dữ liệu kiểm thử
kế tiếp này
II NHỮNG HƯỚNG NGHIÊN CỨU HIỆN NAY
2.1 Phân tích chương trình, tiền xử lý mã nguồn
Trước khi thực thi chương trình để sinh dữ liệu kiểm thử tự động từ mã nguồn, chương trình cần phải phân tích, tiền xử lý mã nguồn Tuy nhiên, phân tích đầy đủ mã nguồn cho một ngôn ngữ lập trình là điều rất khó khăn nhất là khi ngôn ngữ lập trình thường xuyên
có sự nâng cấp thành phiên bản mới Hiện nay, các ngôn ngữ lập trình được phân tích nhiều là ngôn ngữ C/C++, Java, C# Tuy nhiên, việc phân tích mã nguồn chủ yếu tập trung hỗ trợ cú pháp và các kiểu dữ liệu cơ bản, kiểu con trỏ, kiểu mảng, xử lý vòng lặp
Kỹ thuật thường được áp dụng là sử dụng thư viện phân tích mã nguồn, chẳng hạn Eclipse CDT cho C/C++ Đầu vào của Eclipse CDT là
mã nguồn, đầu ra là cây cú pháp trừu tượng (Abstract Syntax Tree – AST) ứng với mã nguồn đó Từ AST, người ta sẽ xây dựng đồ thị CFG làm cơ sở cho việc thực thi các bước tiếp theo của quá trình kiểm thử tự động Hiện nay đã có một số nghiên cứu quan tâm đến giải quyết tính hướng đối tượng của ngôn ngữ lập trình, chẳng hạn chương trình
có tính đa hình động, khuôn hình lớp
Vấn đề phân tích mã nguồn cần tiếp tục cải tiến để có thể hỗ trợ phân tích đầy đủ cho các chương trình C/C++, Java… và nhiều
Trang 4ngôn ngữ khác Các vấn đề còn đang được
nghiên cứu như vấn đề quản lý bộ nhớ, phân
tích các chương trình có tính kế thừa, chồng
toán tử, chồng hàm, khuôn hình v.v Mặt khác,
tối ưu hóa quá trình phân tích mã nguồn là
một vấn đề mở cần được nghiên cứu
2.2 Chiến lược tìm đường thi hành
Sau khi thực thi bộ dữ liệu kiểm thử, tập
hợp các câu lệnh được thực thi sẽ tạo thành
đường thi hành (test path) Hiện tại, nhiều
công trình nghiên cứu đưa ra nhiều chiến
lược chọn đường thi hành khác nhau để sinh
dữ liệu kiểm thử kế tiếp càng tăng độ phủ
càng tốt như [1], [8], [10] Theo tư tưởng của
kĩ thuật kiểm thử tự động định hướng, bộ dữ
liệu kiểm thử kế tiếp được sinh ra từ nhánh/
câu lệnh chưa được đi qua bởi các bộ dữ liệu
kiểm thử trước đó Có hai loại chiến lược tìm
đường thi hành chính bao gồm chiến lược
truyền thống và dựa trên đồ thị dòng điều
khiển (CFG-based) Chiến lược truyền thống
được được Patrice Godefroid đề xuất vào
năm 2005 và được áp dụng trong công cụ
DART Các kĩ thuật tìm kiếm trong chiến lược
truyền thống gồm: tìm kiếm theo chiều sâu
(DFS), tìm kiếm theo chiều rộng (BFS) và tìm
kiếm ngẫu nhiên Theo chiến lược này, điều
kiện cuối cùng của đường thi hành mới nhất
được phủ định để sinh dữ liệu kiểm thử tiếp
theo mà không xét đến trạng thái của đồ thị
luồng điều khiển Tuy nhiên, việc phủ định này
có thể khiến quá trình sinh dữ liệu kiểm thử bị
lặp vô hạn trong trường hợp hàm có vòng lặp
Sau này, các nghiên cứu trong PathCrawler
[10] và CUTE [4] giải quyết vấn đề này bằng
cách hạn chế số lần lặp tối đa của vòng lặp
Tiếp nối với các nghiên cứu trước đó, số
lượng bộ dữ liệu kiểm thử được giảm thiểu hơn
nữa bởi các nghiên cứu của nhóm CREST [1],
nhóm CAUT [8] [9] do áp dụng chiến lược dựa
trên đồ thị dòng điều khiển để chọn nhánh phủ
định tốt nhất Cụ thể là CREST sử dụng thuật
toán Dijkstra để tìm đường thi hành ngắn nhất
từ các câu lệnh/nhánh đã thăm tới khối lệnh chưa được thăm; CAUT cố gắng tìm đường thi hành tốt nhất từ câu lệnh đã được thăm đến khối lệnh chưa được khám phá
Các tác giả Nguyễn Đức Anh và Phạm Ngọc Hùng đã đề xuất kĩ thuật xếp hạng đường thi hành theo độ ưu tiên trong [7] Đường thi hành tăng độ phủ càng lớn thì độ ưu tiên càng cao Mức độ tăng độ phủ được đánh giá qua trạng thái đồ thị dòng điều khiển (CFG) Trong trường hợp hai đường thi hành cùng tăng độ phủ bằng nhau thì đường thi hành ngắn hơn được ưu tiên hơn Nguyên nhân bởi vì chi phí phân tích mã nguồn khá lớn, những đường thi hành ngắn hơn được ưu tiên hơn các đường thi hành khác để giảm chi phí phân tích mã nguồn
Ngoài các chiến lược ở trên, hai nhóm chiến lược sau đã được nghiên cứu và sử dụng là nhóm chiến lược tìm kiếm heuristic (Heuristic Search Strategy) và nhóm chiến lược loại bỏ dư thừa (Pruning Redundance Strategy) Đây là các nghiên cứu có nhiều kết quả tốt và phù hợp
2.3 Tối ưu hóa và giải 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 đó
+ Loại bỏ ràng buộc không liên quan: Trước khi giải hệ ràng buộc cần xem xét
để loại bỏ các ràng buộc không liên quan, nhằm tối ưu hóa hệ ràng buộc Một vài kĩ thuật đơn giản để giảm độ phức tạp ràng buộ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
Trang 5Một chú ý quan trọng là mỗi đường thi
hành chỉ phụ thuộc vào các điều kiện với một
số lượng nhỏ các tham biến Do vậy một cách
tối ưu hóa hiệu quả là cố gắng loại bỏ hết các
ràng buộc không liên quan đến kết quả của
đường thi hành hiện thời Ví dụ: Xem xét ràng
buộc của một đường thi hành hiện thời của
thực thi là: (x+y>0)^(z>0)^(y<12)^(z-x=0) Giả
sử chúng ta muốn sinh các giá trị đầu vào
mới từ giải ràng buộc (x+y>0)^(z>0)& ¬(y<12)
trong đó ¬(y<12) là phủ định của nhánh khả
thi được xây dựng từ ràng buộc trên Ta có thể
an toàn loại bỏ z vì ràng buộc này không ảnh
hưởng đến kết quả của nhánh ¬(y<12) Loại
bỏ ràng buộc thừa với tham chiếu con trỏ và
mảng đã được trình bày chi tiết trong các tài
liệu [4]
+ Kỹ thuật giải hệ ràng buộc:
Kĩ thuật tối ưu đầu tiên là kĩ thuật giải hệ ràng
buộc tăng dần (incremental solving technique)
được đề xuất trong CUTE [4], EXE [11], KLEE
[2] và CAUT [8] 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 Ví
dụ: Xem xét ràng buộc của một đường đi hiện
thời của thực thi là: ((x+y<10)^(x>5), một bộ
dữ liệu thỏa mãn là {x=6, y=3} Chúng ta có
thể nhanh chóng kiểm tra {x=6, y=3} vẫn thỏa
điều kiện (x+y<10)^(x>5)^(y≥0) bằng cách chỉ
cần quan tâm đến điều kiện y≥0
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 A C 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
B C 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
Gần đây, Cristian Cadar và cộng sự đã đề xuất kĩ thuật tối ưu hóa ràng buộc cho mảng [12], đó là cách chuyển đổi dựa trên chỉ số (index-based transformation) và chuyển đổi
dự trên giá trị (value-based transformation) để giảm sự phức tạp của các ràng buộc mảng Nghiên cứu này là mở rộng cho công cụ KLEE + Lựa chọn và kết hợp các bộ giải SMT-Solver:
Một trong những thách thức chính của việc thực thi tượng trưng là giải hệ ràng buộc Mỗi bộ giải đều có điểm mạnh khác nhau và chỉ giải có hiệu quả một số ràng buộc Chẳng hạn các công cụ EXE, KLEE, Rwset sử dụng STP-Solver (Simple Theorem Prover) [2] [11]; công cụ Pex, CFT4Cpp sử dụng Z3-Solver; công cụ DART, SMART [13], CUTE sử dụng lp_solver; CREST sử dụng Yices-Solver, SAGE sử dụng Disolver, CFT4CUnit sử dụng SmtInterpol v.v
Cristian Cadar và cộng sự đã đưa ra ý tưởng kết hợp các SMT-Solvers để cùng giải
hệ ràng buộc nhanh nhất có thể (được gọi là parallel portfolio solver) Mặc dù công cụ SMT-Solver ngày càng phát triển mạnh, tốc độ giải
hệ ràng buộc ngày càng nhanh hơn, tuy nhiên hiện nay vẫn gặp phải một số thách thức như SMT-solver chưa hỗ trợ giải các ràng buộc phi tuyến tính, xử lý biểu thức số thực dấu phẩy động và các ràng buộc phức tạp
2.4 Giảm thời gian biên dịch
Chi phí tạo dữ liệu kiểm thử có thể giảm đáng kể dữ liệu bước biên dịch Khi một dữ liệu kiểm thử mới được tìm ra, nó được thực
Trang 6thi trên môi trường thời gian chạy (runtime
environment) để thu thập các câu lệnh đã đi
qua Hiện tại, có hai cách phổ biến để thực thi
dữ liệu kiểm thử Dữ liệu kiểm thử được thực
hiện có thể được lưu trữ trực tiếp trong bộ thực
thi dữ liệu thử (test driver) hoặc trong một tệp
ngoài Trước đây, với các kiểu dẫn xuất, dữ
liệu kiểm thử được phân tích để tạo ra một mã
nguồn tương ứng nhằm khởi tạo dữ liệu kiểm
thử Nói cách khác, một trình điều khiển kiểm
thử mới được tạo ra mỗi khi có một bộ dữ liệu
kiểm thử tiếp theo được tạo ra Tổng thời gian
là thời gian thực hiện ba bước: (i) tạo ra một
trình điều khiển kiểm thử mới, (ii) biên dịch dự
án kiểm thử với trình điều khiển kiểm thử mới,
và (iii) thực thi tệp dữ liệu (tệp dữ liệu có thể
lớn đặc biệt là trong các dự án công nghiệp)
Chỉ DART, CUTE và CREST mới áp dụng một
kĩ thuật để tăng tốc thời gian biên dịch dữ liệu
thử nghiệm Ý tưởng chính của kĩ thuật này là
lưu trữ tất cả dữ liệu kiểm thử trong một file
bên ngoài và một trình điều khiển kiểm thử
phân tích file này để nạp các giá trị của dữ
liệu kiểm thử khi thực thi [1] [3] Bằng cách đó,
chỉ có một trình điều khiển kiểm thử duy nhất
được tạo ra dùng chung để xử lý tất cả dữ liệu
kiểm thử thay vì nhiều phiên bản như trong ý
tưởng trước đó Một ưu điểm lớn của chiến
lược này là trình điều khiển kiểm thử chung
chỉ cần biên dịch một lần
Tuy nhiên, đề xuất của CREST, DART chỉ
áp dụng cho các chương trình C với các dữ
liệu cơ bản, con trỏ và mảng mà chưa mở rộng
sang các kiểu dữ liệu dẫn xuất của C/C++ (ví
dụ cấu trúc, lớp) hoặc mở rộng sang chồng
toán tử, kế thừa, đa hình của các chương
trình C++
2.5 Kiểm thử tích hợp
Kiểm thử tích hợp (compositional testing)
đã được đề xuất trong [13][15][16][17][18][14]
Patrice Godefroid và cộng sự đã mở rộng
DART để kiểm thử tích hợp bằng cách đề
xuất thuật toán SMART (Systematic Modular
Automated Random Testing) [13] Phương pháp này kiểm thử từng hàm riêng biệt sau
đó tóm tắt mỗi hàm thành một biểu thức logic mệnh đề với các điều kiện tiên quyết đầu vào
và kết quả đầu ra Tóm tắt của hàm là phép tuyển của các tóm tắt con, trong đó mỗi tóm tắt con là một phép hội của các điều kiện thu thập được trên mỗi đường thi hành Các tóm tắt hàm này được sử dụng để kiểm thử các hàm mức cao hơn Từ nghiên cứu mở đầu này, đã có nhiều cải tiến kỹ thuật kiểm thử tích hợp, tiêu biểu là các nghiên cứu sau: Thứ nhất là cải tiến kỹ thuật sinh tóm tắt hàm bằng công thức logic vị từ cấp 1, sau đó sử dụng SMT để giải [15]; Tiếp theo là cải tiến sinh tóm tắt bắt buộc (must sumaries) để kiểm thử tích hợp khi chương trình có sự tiến hóa [16] Một
số kỹ thuật khác như sinh đồ thị gọi hàm (call graph), kĩ thuật Forward Slicing, kỹ thuật tóm tắt từng khối mã nguồn để lưu trong bộ nhớ, mỗi đường thi hành được tạo bằng cách ghép nối các tóm tắt đã được lưu trữ [18], kỹ thuật loại bỏ các đoạn mã dư thừa (lặp lại) [14] Các nghiên cứu đã cố gắng cải tiến kỹ thuật sinh tóm tắt hàm và cải tiến chiến lược chọn đường thi hành, tuy nhiên cũng còn một
số hạn chế đó là sự phức tạp của các tóm tắt Thông thường, một dự án có nhiều hàm được gọi nhau, mỗi hàm được tóm tắt thành một biểu thức logic, do đó dự án sẽ sinh ra biểu thức logic cồng kềnh và việc giải quyết bài toán sẽ trở lên phức tạp Một vấn đề đặt ra cần phải có kỹ thuật tối ưu hóa tóm tắt hoặc cải tiến bộ giải để tăng tốc độ kiểm thử
III ĐỀ XUẤT MỘT SỐ HƯỚNG NGHIÊN CỨU CẢI TIẾN
Từ các phân tích tổng quan về các phương pháp sinh dữ liệu kiểm thử tự động
từ mã nguồn hiện nay trên thế giới và những
ưu, nhược điểm của chúng, bài báo đề xuất một vài hướng nghiên cứu cải tiến như sau:
o Một là: Cải thiện chiến lược tìm kiếm
Trang 7theo chiều rộng đã đề xuất trong DART [3] để
sinh ra ít bộ dữ liệu đầu vào hơn nhưng vẫn
đạt được độ phủ cao Cố gắng tăng độ phủ
mã nguồn với chiến lược chọn đường thi hành
hiện tại, sinh dữ liệu kiểm thử tĩnh với kỳ vọng
rằng sẽ có nhiều nhánh được phủ sớm hơn
Mở rộng kỹ thuật biên dịch dữ liệu kiểm thử
trong DART, hiện chỉ hỗ trợ các dự án được
viết bằng C, sang sinh dữ liệu kiểm thử cho
các dự án C ++ Cụ thể là tạo ra một bộ thực
thi dữ liệu kiểm thử (test driver) chung cho
C++ mà nó hỗ trợ nhiều kiểu tham số khác
nhau bao gồm các kiểu cơ bản, mảng, con trỏ
và các kiểu dẫn xuất
o Hai là: Cải tiến kỹ thuật sinh tóm tắt
hàm và chiến lược chọn đường thi hành để để
tăng tốc độ sinh dữ liệu kiểm thử trong kiểm
thử tích hợp Trong các nghiên cứu gần đây,
tóm tắt hàm được sinh ra dưới dạng một công
thức logic mệnh đề hoặc công thức logic vị từ
cấp 1, tuy nhiên công thức này chưa được tối
ưu Do đó cần xem xét để loại ràng buộc thừa,
cải tiến kỹ thuật sinh tóm tắt, kỹ thuật giải hệ
ràng buộc hoặc cải tiến chiến lược tìm đường
thi hành hiệu quả hơn dựa vào các nghiên
cứu mới về kỹ thuật thực thi tượng trưng động
[6][5]
o Ba là: Hiện nay các nghiên cứu về phân
tích mã nguồn tập trung nhiều vào việc hỗ trợ
các cú pháp, kiểu dữ liệu cơ bản, mảng của
chương trình C/C++ ở mức đơn vị (hàm) mà
chưa hướng đến dự án C++ hoàn chỉnh Do
đó, Bài báo sẽ tiếp tục cải tiến kỹ thuật phân
tích mã nguồn để có thể xử lý được các vấn đề
khuôn hình (template), kế thừa (inheritance),
đa hình (polymorphism) Tìm hiểu các nghiên
cứu mới thực về thi tượng trưng trong [5][6]
để cải tiến kỹ thuật phân tích mã nguồn nhằm
xử lý các vấn đề trên Cố gắng sử dụng các
kỹ thuật phân tích mã nguồn, kỹ thuật chọn
đường thi hành, kỹ thuật tối ưu hóa và giải
hệ ràng buộc trong các nghiên cứu mới để
hướng tới xây dựng hoàn thiện công cụ kiểm
thử tự động cho các dự án C++
o Ngoài các hướng nghiên cứu trên, có thể phát triển các chiến lược tìm kiếm mới
có hiệu quả dựa trên các công cụ machine learning, deep learning, mạng nơ-ron, tìm kiếm tiến hóa v.v Tìm hiểu các bộ giải SAT/ SMT-solver (Z3, SMTInterpol, STP, v.v.) để tìm ra các thế mạnh của mỗi bộ giải Lựa chọn hoặc kết hợp các bộ giải để giải nhằm tận dụng các thế mạnh của chúng với mục tiêu giảm chi phí về thời gian thực thi và không gian bộ nhớ
Để chứng minh tính hiệu quả của cải tiến trong các hướng nghiên cứu trên, cần phát triển công cụ kiểm thử tự động cho các dự án bằng ngôn ngữ lập trình C++
IV KẾT LUẬN
Bài báo đã trình bày một cách tổng quan các vấn đề về sinh dữ liệu kiểm thử tự động từ
mã nguồn Hai kỹ thuật chính được sử dụng
là kiểm thử tĩnh và kiểm thử động định hướng Bài báo đã tổng hợp và trình bày được các ưu
và nhược điểm của các nghiên cứu đã được công bố và đề xuất ba hướng nghiên cứu cải tiến chính với bài toán sinh dữ liệu kiểm thử tự động từ mã nguồn cho các dự án C++
TÀI LIỆU THAM KHẢO
[1] J Burnim and K Sen 2008 Heuristics for Scalable Dynamic Test Generation In Proceedings of the 2008 23rd IEEE/ACM International Conference on Automated Software Engineering (ASE ’08) IEEE Computer Society, Washington, DC, USA, 443–446
[2] Cristian Cadar, Daniel Dunbar, and Dawson Engler 2008 KLEE: Unassisted and Automatic Generation of High-coverage Tests for Complex Systems Programs In Proceedings of the 8th USENIX Conference on Operating Systems Design and Implementation (OSDI’08) USENIX
Trang 8Association, Berkeley, CA, USA, 209–224
[3] Patrice Godefroid, Nils Klarlund,
and Koushik Sen 2005 DART: Directed
Automated Random Testing In Proceedings
of the 2005 ACM SIGPLAN Conference
on Programming Language Design and
Implementation (PLDI ’05) ACM, New York,
NY, USA, 213–223
[4] Koushik Sen, Darko Marinov, and Gul
Agha 2005 CUTE: A Concolic Unit Testing
Engine for C In Proceedings of the 10th
European Software Engineering Conference
Held Jointly with 13th ACM SIGSOFT
International Symposium on Foundations of
Software Engineering (ESEC/FSE-13) ACM,
New York, NY, USA, 263–272
[5] David Trabish, Andrea Mattavelli,
Noam Rinetzky, and Cristian Cadar 2018
Chopped Symbolic Execution InICSE ’18:
40th International Conference on Software
Engineering, May 27-June 3, 2018, Gothenburg,
Sweden.ACM, New York, NY, USA, 11 pages
https://doi.org/10.1145/3180155.3180251
[6] Roberto Baldoni, Emilio Coppa,
Daniele Cono D’Elia, Camil Demetrescu, and
Irene Finocchi 2018 A Survey of Symbolic
Execution Techniques ACM Comput Surv.51,
3, Article 50 (May 2018), 39 pages https://doi
org/10.1145/318265
[7] Duc-Anh Nguyen and Pham Ngoc
Hung 2017 A Test Data Generation Method
for C/C++ Projects In SoICT ’17: Eighth
International Symposium on Information
and Communication Technology, December
7–8, 2017, Nha Trang City, Viet Nam ACM,
New York, NY, USA, 8 pages https://doi
org/10.1145/3155133.3155144
[8] T Su, G Pu, B Fang, J He, J
Yan, S Jiang, and J Zhao 2014 Automated
Coverage-Driven Test Data Generation Using
Dynamic Symbolic Execution In 2014 Eighth
International Conference on Software Security and Reliability (SERE) 98–107
[9] Z Wang, X Yu, T Sun, G Pu, Z Ding, and J Hu 2009 Test Data Generation for Derived Types in C Program In 2009 Third IEEE International Symposium on Theoretical Aspects of Software Engineering 155–162 [10] Nicky Williams, Bruno Marre, Patricia Mouy, and Muriel Roger 2005 PathCrawler: Automatic Generation of Path Tests by Combining Static and Dynamic Analysis In Proceedings of the 5th European Conference on Dependable Computing (EDCC’05) Springer-Verlag, Berlin, Heidelberg, 281–292
[11] Cristian Cadar, Vijay Ganesh, Peter M Pawlowski, David L Dill, and Dawson R Engler 2008 EXE: Automatically Generating Inputs of Death ACM Trans Inf Syst Secur 12, 2, Article 10 (Dec 2008), 38 pages
[12] David M Perry, Andrea Mattavelli, Xiangyu Zhang, and Cristian Cadar, Accelerating array constraints in symbolic execution, in Proceedings of the 26th ACM SIGSOFT International Symposium on Software Testing and Analysis, New York, USA, 2017, pp 68-78
[13] Patrice Godefroid, Compositional dynamic test generation, in Proceedings of the 34th annual ACM SIGPLAN-SIGACT symposium on Principles of programming languages, 2007, pp 47-54
[14] Y Lin, T Miller and H Sondergaard,
"Compositional Symbolic Execution: Incremental Solving Revisited," 2016 23rd Asia-Pacific Software Engineering Conference(APSEC), Hamilton, New Zealand,
2016, pp 273-280
doi:10.1109/APSEC.2016.046
Trang 9[15] S Anand, P Godefroid, and N
Tillmann, “Demand-driven compositional
symbolic execution” in Tools and Algorithms
for the Construction and Analysis of Systems
(TACAS), ser LNCS, C R Ramakrishnan
and J Rehof, Eds Springer, 2008, vol 4963,
pp 367–381
[16] P Godefroid, S.K Lahiri, C
Rubio-González, "Statically validating must
summaries for incremental compositional
dynamic test generation," in Proceedings of
the 18th International Conference on Static
Analysis, pp 112-128, 2011
[17] M Christakis and P Godefroid,
“IC-Cut: A compositional search strategy for dynamic test generation,” Proceeding SPIN
2015 Proceedings of the 22nd International Symposium on Model Checking Software - Volume 9232 Pages 300-318
[18] Y Lin, T Miller, H Sandergaard,
"Compositional symbolic execution using fine-grained summaries", Proc 24th Australasian Softw Eng Conf IEEE, pp 213-222, 2015