1. Trang chủ
  2. » Giáo Dục - Đào Tạo

Phương pháp phân tích mã nguồn và sinh dữ liệu kiểm thử cho các dự án CC++

68 179 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 68
Dung lượng 4,35 MB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

Tuy vậy, các vấn đề còn tồn tại cần giải quyết của kĩ thuật kiểm thử tự động định hướng gồm vấn đề sinh dữ liệu kiểm thử đầu tiên chưa đủ tốt, sinh tập dữ liệu kiểm thử có số lượng nhỏ n

Trang 1

ĐẠI HỌC QUỐC GIA HÀ NỘI TRƯỜNG ĐẠI HỌC CÔNG NGHỆ

Trang 2

ĐẠI HỌC QUỐC GIA HÀ NỘI TRƯỜNG ĐẠI HỌC CÔNG NGHỆ

Trang 3

LỜI CẢM ƠN

Đầu tiên, tôi xin gửi lời cám ơn chân thành tới Tiến sĩ Phạm Ngọc Hùng – giảng viên

bộ môn Công Nghệ Phần Mềm – người đã hướng dẫn tận tình, tỉ mỉ, chu đáo tôi trong suốt hai năm làm luận văn Quãng thời gian được thầy hướng dẫn đã giúp tôi học hỏi, đúc kết được nhiều kinh nghiệm về phương pháp nghiên cứu, kĩ năng giao tiếp, kĩ năng làm việc nhóm, kĩ năng trình bày Thầy còn truyền cho tôi ngọn lửa yêu nghiên cứu khoa học, niềm tin vượt qua những khó khăn trong cuộc sống và dạy tôi cách vượt qua những khó khăn đó Tôi cảm thấy tự hào và may mắn khi là một học viên được thầy hướng dẫn trong những năm tháng cao học

Ngoài ra, tôi xin gửi lời cám ơn chân thành đến nhóm nghiên cứu đã giúp đỡ tôi nhiệt tình để hoàn thành luận văn sao cho đạt hiệu quả cao nhất Cám ơn nhóm nghiên cứu đã giúp đỡ tôi bằng hành động, bằng lời nói mỗi khi tôi gặp khó khăn, thất bại Hai năm bên nhau không phải là dài nhưng đối với tôi, đây là quãng thời gian tuyệt vời nhất và không thể nào quên

Tiếp theo, tôi xin gửi lời cảm ơn đến các thầy cô giảng viên Trường Đại học Công Nghệ - Đại học Quốc Gia Hà Nội – những người đã tận tâm truyền đạt những kiến thức quý báu làm nền tảng để tôi tiếp tục đi xa hơn nữa trong lĩnh vực công nghệ thông tin

Cuối cùng, tôi xin được cảm ơn gia đình đã nuôi tôi khôn lớn để trở thành người

có ích cho xã hội, giúp tôi có một điểm tựa vững chắc để yên tâm học hành trong suốt bao năm qua Tôi xin gửi lời cám ơn chân thành tới cha, mẹ, em gái đã luôn động viên và cổ vũ tôi mỗi khi tôi gặp khó khăn và thử thách

Hà Nội, ngày 25 tháng 11 năm 2017

Học viên

Nguyễn Đức Anh

Trang 4

LỜI CAM ĐOAN

Tôi xin cam đoan rằng những nghiên cứu về kiểm thử tự động cho chương trình C/C++ được trình bày trong luận văn này là của tôi và chưa từng được nộp như một báo cáo luận văn tại trường Đại học Công Nghệ - Đại học Quốc Gia Hà Nội hoặc bất

kỳ trường đại học khác Những gì tôi viết ra không sao chép từ các tài liệu, không sử dụng các kết quả của người khác mà không trích dẫn cụ thể

Tôi xin cam đoan công cụ kiểm thử tự động tôi trình bày trong khoá luận là do tôi tự phát triển, không sao chép mã nguồn của người khác Nếu sai tôi hoàn toàn chịu trách nhiệm theo quy định của trường Đại học Công Nghệ - Đại học Quốc Gia

Hà Nội

Hà Nội, ngày 25 tháng 11 năm 2017

Học viên

Nguyễn Đức Anh

Trang 5

MỤC LỤC

Giới thiệu 1

Tổng quan kĩ thuật kiểm thử tự động định hướng 5

2.1 Dữ liệu kiểm thử 5

2.2 Các tiêu chí độ phủ sử dụng trong kĩ thuật kiểm thử tự động định hướng 5 2.3 Đồ thị dòng điều khiển 6

2.4 Cây cú pháp trừu tượng 7

2.5 Quy trình chung kĩ thuật kiểm thử tự động định hướng 7

Phương pháp kiểm thử tự động dự án C/C++ sử dụng kĨ thuật kiểm thử tự động định hướng 9

3.1 Tổng quan phương pháp đề xuất 9

3.2 Pha tiền xử lý mã nguồn 10

3.2.1 Xây dựng cây cấu trúc từ dự án C/C++ 10

3.2.2 Chèn các câu lệnh đánh dấu vào hàm 13

3.3 Pha sinh dữ liệu kiểm thử 14

3.3.1 Xây dựng đồ thị dòng điều khiển từ mã nguồn 16

3.3.2 Xếp hạng đường thi hành 18

3.3.3 Xây dựng hệ ràng buộc từ đường thi hành 19

a Mô hình bộ nhớ sử dụng trong kĩ thuật thực thi tượng trưng 20

b Xây dựng hệ ràng buộc từ đường thi hành sử dụng kĩ thuật thực thi tượng trưng 22

3.3.4 Giải hệ ràng buộc sử dụng bộ giải SMT-Solver 24

3.4 Biên dịch và thực thi dữ liệu kiểm thử trong môi trường chạy 26

3.5 Tối ưu hóa pha sinh dữ liệu kiểm thử 27

3.5.1 Đơn giản hóa hệ ràng buộc 27

Trang 6

3.5.2 Tăng tốc thời gian biên dịch và thực thi dữ liệu kiểm thử 28

3.6 Xuất mã nguồn kiểm thử theo chuẩn Google Test 29

Công cụ và thực nghiệm 30

4.1 Giới thiệu công cụ kiểm thử tự động CFT4Cpp 30

4.2 Thư viện hỗ trợ sử dụng trong công cụ kiểm thử tự động CFT4Cpp 33

4.2.1 Thư viện giải hệ ràng buộc Z3 33

4.2.2 Thư viện phân tích mã nguồn CDT 34

4.2.3 Thư viện tính giá trị biểu thức Jeval 35

4.3 Kết quả thực nghiệm 35

4.3.1 So sánh số lượng bộ dữ liệu kiểm thử và độ phủ với KLEE, CAUT, CREST, PathCrawler 35

4.3.2 Sinh dữ liệu kiểm thử vòng lặp 40

4.3.3 So sánh thời gian biên dịch và thực thi dữ liệu kiểm thử 42

Kết luận 44

Tài liệu tham khảo 48

Trang 7

DANH SÁCH KÝ HIỆU, CHỮ VIẾT TẮT

Dynamic Symbolic Execution DSE Kĩ thuật kiểm thử động

C/C++ Development Tooling CDT

Satisfiability Modulo Theories

Solver

SMT-Solver

Boolean Satisfiability Problem SAT

DSE)

Modified condition/ decision

coverage

MC/DC Độ phủ cấp ba (hoặc phủ điều kiện con)

Trang 8

DANH SÁCH BẢNG BIỂU

Bảng 3.1 Danh sách quan hệ phụ thuộc lô-gic điển hình 10

Bảng 3.2 Luật chèn câu lệnh đánh dấu vào hàm 13

Bảng 4.1 Thông tin các công cụ so sánh trong thực nghiệm 35

Bảng 4.2 Thông tin cấu hình các ví dụ trong thực nghiệm 36

Bảng 4.3 Thông tin các hàm kiểm thử về tiêu chí độ phủ và số bộ dữ liệu kiểm thử 37

Bảng 4.4 Kết quả so sánh công cụ CFT4Cpp với KLEE và PathCrawler 38

Bảng 4.5 Kết quả so sánh công cụ CFT4Cpp với CREST và CAUT 39

Bảng 4.6 Sinh dữ liệu kiểm thử vòng lặp 41

Bảng 4.7 Bảng so sánh thời gian biên dịch và thực thi dữ liệu kiểm thử giữa kĩ thuật cải tiến và kĩ thuật truyền thống 43

DANH SÁCH THUẬT TOÁN Thuật toán 3.1 Thuật toán LDFS sinh dữ liệu kiểm thử 15

Thuật toán 3.2 Thuật toán xây dựng hệ ràng buộc từ đường thi hành 23

DANH SÁCH HÌNH VẼ Hình 2.1 Các cấu trúc điều khiển phổ biến trong C/C++ 7

Hình 3.1 Tổng quan phương pháp đề xuất 9

Hình 3.2 Ví dụ cây cấu trúc của một dự án C/C++ điển hình 12

Hình 3.3 Minh họa đồ thị CFG phủ câu lệnh/nhánh 17

Hình 3.4 Minh họa đồ thị CFG phủ MC/DC 18

Hình 3.5 Mô hình bộ nhớ sử dụng trong kĩ thuật thực thi tượng trưng 21

Hình 3.6 Quy trình xây dựng biểu thức SMT-Lib từ ràng buộc 25

Hình 3.7 Quá trình biến đổi hệ ràng buộc về chuẩn SMTLib 25

Hình 3.8 Kĩ thuật tạo bộ thực thi ca kiểm thử tổng quát 28

Trang 9

Hình 4.1 Kiến trúc công cụ CFT4Cpp 30

Hình 4.2 Giao diện chính của công cụ CFT4Cpp 31

Hình 4.3 Giao diện bước cấu hình công cụ CFT4Cpp 32

Hình 4.4 Giao diện sinh dữ liệu kiểm thử cho hàm Divide 32

Hình 4.5 Biên bản kiểm thử xuất bởi công cụ CFT4Cpp cho hàm Divide 33

Hình 4.6 Minh họa kết quả giải hệ ràng buộc sử dụng Z3 34

DANH SÁCH MÃ NGUỒN Mã nguồn 3.1 Ví dụ một phần mã nguồn trong dự án C/C++ 12

Mã nguồn 3.2 Ví dụ hàm checkFirstSubject sau khi chèn câu lệnh đánh dấu 14

Mã nguồn 3.3 Mã nguồn hàm average 17

Mã nguồn 4.1 Minh họa một hệ ràng buộc theo chuẩn SMT-Lib 34

Trang 10

TÓM TẮT

Để đảm bảo chất lượng phần mềm, nhiều kĩ thuật kiểm thử khác nhau được áp dụng khiến chi phí kiểm thử tăng cao Trong đó, kiểm thử đơn vị là một trong những kĩ thuật được áp dụng rộng rãi trong các công ty phần mềm để kiểm tra chất lượng mã nguồn, đặc biệt đối với các phần mềm nhúng viết bằng ngôn ngữ C/C++ Kĩ thuật này giúp phát hiện sớm nhiều vấn đề tiềm ẩn trong quy trình xây dựng phần mềm Tuy nhiên, nhược điểm của kĩ thuật kiểm thử đơn vị là vấn đề chi phí tăng cao đối với những dự án lớn bởi vì kĩ thuật kiểm thử này xem xét tính đúng đắn của từng thành phần nhỏ nhất trong mã nguồn Để giảm thiểu bài toán chi phí, quy trình kiểm thử đơn vị nên được tự động hóa hoàn toàn

Bởi thế, luận văn hướng đến xây dựng một giải pháp kiểm thử tự động mức đơn

vị cho các dự án C/C++ Tư tưởng chính của phương pháp đề xuất dựa trên kĩ thuật kiểm thử tự động định hướng Hiện nay, kĩ thuật kiểm thử tự động định hướng đã được chứng minh tính hiệu quả trong bài toán kiểm thử tự động Tuy vậy, các vấn đề còn tồn tại cần giải quyết của kĩ thuật kiểm thử tự động định hướng gồm vấn đề sinh

dữ liệu kiểm thử đầu tiên chưa đủ tốt, sinh tập dữ liệu kiểm thử có số lượng nhỏ nhưng đạt độ phủ cao Do đó, luận văn tập trung giải quyết các bài toán này Cụ thể, luận văn đề xuất kĩ thuật sinh dữ liệu kiểm thử đầu tiên dựa trên thông tin phân tích

mã nguồn thay vì áp dụng kĩ thuật sinh ngẫu nhiên truyền thống trong kĩ thuật kiểm thử tự động định hướng Để giảm thiểu số lượng bộ dữ liệu kiểm thử trong khi vẫn đạt độ phủ cao, thuật toán LDFS được đề xuất Để chứng minh tính hiệu quả của phương pháp đề xuất, công cụ CFT4Cpp được xây dựng dựa trên phương pháp đề xuất và tiến hành so sánh với các phương pháp kiểm thử khác gồm KLEE, PathCrawler, CAUT, CREST Kết quả nghiên cứu đã được đăng trong hai hội nghị NICS 2016 và SoICT 2017 với đánh giá khả quan về tính thực tiễn của phương pháp

đề xuất

Trang 11

GIỚI THIỆU

Kiểm thử đơn vị 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, đặc biệt các phần mềm nhúng Hai phương pháp được sử dụng phổ biến gồm kiểm thử hộp đen và kiểm thử hộp trắng 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 Bởi vì 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 vấn đề 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 hàm 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 trắng cần được tự động hóa để giải quyết bài toán về chi phí

Hai hướng chính trong kiểm thử đơn vị theo phương pháp kiểm thử hộp trắng gồm phát hiện lỗi và tối đa hóa độ phủ Cho một hàm cần kiểm thử hộp trắng, hàm này có thể có vấn đề tiềm ẩn rất khó phát hiện Yêu cầu chính là sinh tập dữ liệu kiểm thử để kiểm tra chất lượng hàm này Theo hướng đầu tiên, tập dữ liệu kiểm thử này cần phát hiện được các lỗi tiềm tàng như lỗi chia cho 0, lỗi tràn bộ nhớ [2], [14] Hướng thứ hai yêu cầu sinh tập dữ liệu kiểm thử sao cho số lượng nhánh, câu lệnh, hoặc điều kiện con được thực thi lớn nhất Khái niệm độ phủ liên quan đến chất lượng

dữ liệu kiểm thử theo hướng tối đa hóa độ phủ Độ phủ càng lớn đồng nghĩa với chất lượng bộ dữ liệu kiểm thử càng cao Ví dụ, nếu hàm cần kiểm thử có 10 nhánh mà chỉ có 9 nhánh được đi qua bởi tập 3 dữ liệu kiểm thử thì độ phủ đạt được bằng 90% Điều đó có nghĩa là trong hàm này có một nhánh thừa cần được phát hiện Các công

Trang 12

cụ kiểm thử tiêu biểu theo hướng này có thể kể đến PathCrawler1 [13], CAUT2 [11], CUTE [10], CREST3 [1]

Đối với bài toán sinh tập dữ liệu kiểm thử để đạt độ phủ tối đa, hai phương pháp kiểm thử hộp trắng được sử dụng phổ biến gồm kiểm thử tĩnh và kiểm thử động Tư tưởng chính của phương pháp kiểm thử tĩnh là sinh dữ liệu kiểm thử bằng phân tích

mã nguồn Theo như phương pháp này, tất cả mọi cú pháp trong chương trình cần được hỗ trợ phân tích đầy đủ Tốc độ là một trong những ưu điểm chính của phương pháp kiểm thử tĩnh bởi kĩ thuật này không yêu cầu thực thi chương trình như kĩ thuật động Tuy nhiên, phương pháp này khó áp dụng cho các dự án công nghiệp bởi vì rất khó để hỗ trợ tất cả mọi cú pháp có thể của ngôn ngữ C/C++ Trái ngược với phương pháp kiểm thử tĩnh, phương pháp 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ủ tối đa, phương pháp 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 [2] [13] [5] [1] Bởi thế, phương pháp kiểm thử động dễ dàng đạt được độ phủ cao với nỗ lực phân tích chương trình nhỏ hơn so với phương pháp kiểm thử tĩnh

Phương pháp 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 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 (2008) – 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 biên dịch và chạy chương trình vừa sinh dữ liệu kiểm thử trực tiếp Chẳng hạn, khi trình biên dịch gặp một điều kiện, 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

1 http://pathcrawler-online.com:8080/

2 https://github.com/tingsu/caut-lib

3 https://github.com/jburnim/crest

Trang 13

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 trì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 hay được sử dụng kế tiếp gọi là 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 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, CUTE, CAUT, và CREST Trong phương pháp này, dữ liệu kiểm thử đầu tiên được sinh ngẫu nhiên, sau đó đẩy vào hàm cần kiểm thử để lấy danh sách các câu lệnh đi qua Với một bộ dữ liệu kiểm thử, tập các câu lệnh này gọi là một đường thi hành Dữ liệu kiểm thử kế tiếp được sinh ra dựa trên hai thông tin gồm tiêu chí độ phủ (phủ câu lệnh/nhánh) và trạng thái của chương trình Quá trình sinh dữ liệu kiểm thử kết thúc khi độ phủ đạt được tối đa hoặc chạm đến giới hạn thời gian Hiện tại, nhiều công trình nghiên cứu đưa ra nhiều chiến thuật 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ư [11], [1], [13] Bởi thế, số lượng bộ dữ liệu kiểm thử đạt được độ phủ tối ưu khá ít nên khiến quy trình quản lý dữ liệu kiểm thử dễ dàng hơn

Tuy nhiên, kĩ thuật kiểm thử tự động định hướng còn tồn tại nhiều vấn đề cần giải quyết Vấn đề thứ nhất, dữ liệu kiểm thử đầu tiên thường được sinh ngẫu nhiên dựa trên kiểu biến Chẳng hạn, nếu biến có kiểu con trỏ cấu trúc thì xác suất sinh giá

trị biến đó bằng NULL hoặc khác NULL bằng 50% [5] Kĩ thuật sinh ngẫu nhiên này

không thể hiện tính hiệu quả khi chương trình có biến truy cập vùng nhớ, ví dụ như

hàm sao chép xâu s1 cho s2 Rõ ràng, giá trị xâu s1 luôn khác NULL Nếu giá trị s1 bằng NULL, chương trình sẽ bị lỗi khi thực thi bộ giá trị này Tuy nhiên, kĩ thuật sinh

dữ liệu kiểm thử đầu tiên theo phương pháp ngẫu nhiên không phát hiện được ràng buộc quan trọng này Quá trình sinh dữ liệu kiểm thử đầu tiên lặp đi lặp lại đến khi

dữ liệu kiểm thử này không gây lỗi Điều đó dẫn đến thời gian sinh dữ liệu kiểm thử đầu tiên mà không gây lỗi tăng lên Vấn đề thứ hai liên quan đến bài toán phân tích chương trình C/C++ Các phương pháp kiểm thử đã đề xuất trong [1], [11], [13], và [2] chỉ áp dụng cho ngôn ngữ C mà không hỗ trợ C++

Trang 14

Luận văn hướng đến giải quyết các vấn đề của kiểm thử kĩ thuật kiểm thử tự động định hướng trong bài toán kiểm thử dự án C/C++ Mục tiêu chính của luận văn gồm đề xuất phương pháp sinh dữ liệu kiểm thử giải quyết vấn đề dữ liệu kiểm thử đầu tiên và phương pháp phân tích mã nguồn dự án C/C++ để sinh tập dữ liệu kiểm thử số lượng nhỏ và đạt độ phủ tối đa Tư tưởng chính của phương pháp đề xuất như sau Đầu vào bài toán gồm tiêu chí độ phủ (câu lệnh/nhánh/điều kiện con), số lần lặp tối đa của vòng lặp, cận biến số nguyên/kí tự, số phần tử tối đa của mảng, và hàm cần kiểm thử Đầu tiên, hàm cần kiểm thử được phân tích xây dựng đồ thị dòng điều khiển tương ứng Sau đó, tập các đường thi hành có thể có trên đồ thị này được thu thập bằng cách áp dụng thuật toán duyệt đồ thị theo chiều sâu Tập các dữ liệu kiểm thử này được sắp xếp theo thứ tự ưu tiên nào đó, ví dụ như đường ngắn nhất có độ

ưu tiên cao nhất Với từng đường thi hành được sắp xếp theo độ ưu tiên, kĩ thuật thực thi tượng trưng và SMT-Solver được áp dụng để tìm bộ giá trị thỏa mãn đường thi hành đó Nếu tồn tại bộ giá trị thỏa mãn, ta coi đó là một bộ dữ liệu kiểm thử Đồng thời, trạng thái độ phủ đồ thị được cập nhật và tập đường thi hành nêu trên loại bỏ các đường thi hành thừa (không tăng độ phủ) Quá trình sinh dữ liệu kiểm thử lặp đi lặp lại đến khi đạt được độ phủ tối đa, hoặc đạt đến giới hạn thời gian cho trước Phần còn lại luận văn được trình bày như sau Chương 2 trình bày nền tảng lý thuyết về nghiên cứu sinh dữ liệu kiểm thử tự động cho C/C++ Chương 3 mô tả chi tiết về phương pháp đề xuất Kế tiếp, chương 4 trình bày công cụ và thực nghiệm về tính hiệu quả phương pháp đề xuất Cuối cùng, chương 5 đưa ra kết luận về những

gì đã làm được, những vấn đề còn tồn tại và hướng phát triển

Trang 15

TỔNG QUAN KĨ THUẬT KIỂM THỬ TỰ ĐỘNG ĐỊNH HƯỚNG

Mục tiêu chương này chung cấp nền tảng lý thuyết trong sinh dữ liệu kiểm thử tự động gồm khái niệm dữ liệu kiểm thử và độ phủ, đồ thị dòng điều khiển, cây cú pháp trừu tượng, và tổng quan kĩ thuật kiểm thử tự động định hướng

2.1 Dữ liệu kiểm thử

Dữ liệu kiểm thử là những bộ giá trị được sinh từ chương trình mà không sử dụng

giá trị đầu ra mong muốn (expected output) và được định nghĩa như sau:

Định nghĩa 2.1 (Test data – Dữ liệu kiểm thử) Một bộ dữ liệu kiểm thử của hàm

C/C++ được định nghĩa T = {v 1, v2, …, vk | 1 <= k}, trong đó k là số lượng biến truyền

vào hàm, v i là giá trị của biến thứ i trong danh sách biến Biến truyền vào hàm có thể

là tham số hoặc biến ngoài (biến tĩnh, biến extern, v.v.) Kiểu biến là kiểu cơ bản, kiểu dẫn xuất, kiểu con trỏ, hoặc kiểu mảng

2.2 Các tiêu chí độ phủ sử dụng trong kĩ thuật kiểm thử tự động định hướng

Mục đích của luận văn là sinh dữ liệu kiểm thử đạt độ phủ tối đa với số lượng bộ dữ liệu kiểm thử nhỏ Nói chung, độ phủ là một thước đo quan trọng để đánh giá chất lượng mã nguồn Các loại độ phủ phổ biến trong sinh dữ liệu kiểm thử đơn vị gồm phủ nhánh, phủ câu lệnh, và độ phủ điều kiện con Tùy theo từng tiêu chí độ phủ mà công thức tính giá trị độ phủ khác nhau

- Độ phủ câu lệnh (statement coverage) Sinh tập dữ liệu kiểm thử sao cho mỗi

câu lệnh được thực hiện ít nhất một lần Nếu chương trình gồm 10 câu lệnh,

mà tập dữ liệu kiểm thử chỉ thực thi 8 câu lệnh thì độ phủ đạt được bằng 80%

- Độ phủ câu lệnh (branch coverage) Sinh tập dữ liệu kiểm thử sao cho mỗi

nhánh đúng/sai tại các câu lệnh điều kiện đều được đi qua Nếu chương trình chỉ có 3 câu lệnh điều khiển (6 nhánh), mà tập dữ liệu kiểm thử chỉ thực thi 4 nhánh thì độ phủ đạt được bằng 66.67%

- Độ phủ điều kiện con (MC/DC) Độ phủ này tương tự như độ phủ câu lệnh

Để thỏa mãn độ phủ điều kiện con, tập dữ liệu kiểm thử sinh ca cần đảm bảo

đi qua mọi nhánh đúng/sai của các điều kiện con Điều kiện trong một câu lệnh

Trang 16

điều kiện cần được phân tách thành các điều kiện con để tính toán Chẳng hạn,

điều kiện a>b && c>1 gồm hai điều kiện con a>b và c>1 Tập dữ liệu kiểm

thử cần đi qua hai nhánh đúng/sai của hai điều kiện con này Tổng số nhánh đúng/sai của điều kiện này là 4

Về bản chất, nếu một tập dữ liệu kiểm thử cho độ phủ MC/DC đạt 100% (mọi nhánh đúng/sai của điều kiện con được đi qua) thì độ phủ nhánh và câu lệnh bằng 100% Tương tự, nếu độ phủ nhánh bằng 100% thì độ phủ câu lệnh đạt được tối đa với cùng tập dữ liệu kiểm thử Độ phủ điều kiện con chặt hơn độ phủ nhánh, và độ phủ nhánh chặt hơn độ phủ câu lệnh Với cùng một chương trình, số lượng dữ liệu kiểm thử thỏa mãn độ phủ điều kiện con có thể lớn hơn độ phủ câu lệnh và độ phủ câu lệnh Tuy độ phủ điều kiện con đảm bảo chất lượng phần mềm tốt hơn, chi phí

để sinh bộ dữ liệu kiểm thử thỏa mãn tiêu chí độ phủ này cao hơn nhiều với hai độ phủ còn lại Trong thực tế, độ phủ câu lệnh và nhánh được sử dụng phổ biến do đủ đảm bảo được chất lượng phần mềm

Trường hợp ta sinh tập dữ liệu kiểm thử không thể đạt được độ phủ tối đa, hai trường hợp có thể xảy ra gồm:

- Tập dữ liệu kiểm thử sinh ra chưa đủ tốt Để giải quyết vấn đề này, nhiều

nghiên cứu đề xuất để sinh tập dữ liệu kiểm thử nhỏ mà đạt độ phủ tối đa

- Mã nguồn thừa câu lệnh/nhánh Nếu tập dữ liệu kiểm thử sinh ra không đạt

được độ phủ tối đa, tức là chương trình có khả năng thừa câu lệnh hoặc nhánh không bao giờ đi qua

2.3 Đồ thị dòng điều khiển

Định nghĩa 2.2 (Control flow graph - Đồ thị dòng điều khiển) Đồ thị dòng điều khiển

của một hàm được định nghĩa G = (V, E), trong đó V = {n 0, n1, …, nk-1} chứa các câu lệnh, nhóm câu lệnh trong chương trình 𝑬 = {(𝒏𝒊, 𝒏𝒋)|𝒏𝒊, 𝒏𝒋 ∈ 𝑽, 𝟎 ≤ 𝒊, 𝒋 ≤ 𝒌 −

𝟏} ⊂ 𝑽 × 𝑽 đại diện các cạnh Cạnh (n i ,n j ) thể hiện rằng câu lệnh n i được thực thi

Trang 17

tuần tự if-else do-while while-do for

Hình 2.1 Các cấu trúc điều khiển phổ biến trong C/C++

Đồ thị dòng điều khiển ứng với tiêu chí phủ câu lệnh/phủ nhánh giống nhau, và

có thể khác với tiêu chí MC/DC Sự giống nhau xảy ra khi mã nguồn hàm không có điều kiện kép, tức chỉ có điều kiện đơn

2.4 Cây cú pháp trừu tượng

Cây cú pháp trừu tượng được sử dụng rộng rãi trong các trình biên dịch hoặc IDE Với đầu vào là mã nguồn, các trình biên dịch/IDE này sẽ xây dựng cây AST tương ứng Về bản chất, cây AST là một cách biểu diễn cấu trúc mã nguồn dưới dạng cây Mỗi một thành phần trong cây tương ứng với một thành phần mã nguồn như câu lệnh gán, khối lệnh điều kiện, biến, phép toán, v.v Mỗi thành phần trong cây đều có các kiểu khác nhau được quy định bởi trình biên dịch Ví dụ, trong CDT, kiểu

IASTDeclSpecifier tương ứng với kiểu trả về của hàm hay kiểu biến Kiểu IASTBinaryExpression tương ứng với dấu phép toán Kiểu IASTName đại diện tên

biến, tên hàm IASTReturnStatement chính là câu lệnh return

2.5 Quy trình chung kĩ thuật kiểm thử tự động định hướng

Kĩ thuật kiểm thử tự động định hướng được đề xuất lần đầu tiên bởi nhà khoa học Koushik Sen cùng các cộng sự lần đầu tiên vào 2005 [5] Quy trình thuật kiểm thử

tự động định hướng 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 Chi tiết kĩ thuật chèn các câu lệnh

Trang 18

đánh dấu được trình bày trong phần 3.2.2 Chèn các câu lệnh đánh dấu

vào hàm

Bước 2 Dựa trên tham số truyền vào hàm và các biến ngoài hàm sử dụng, một

bộ giá trị ngẫu nhiên được sinh ra để chạy chương trình Chẳng hạn, nếu biến truyền vào là con trỏ kiểu cấu trúc thì giá trị của biến đó có thể bằng NULL, hoặc được cấp phát một vùng nhớ ngẫu nhiên nào đó Ta coi bộ giá trị ngẫu nhiên này là dữ liệu kiểm thử đầu tiên 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 đó Điều đó có nghĩa là, nếu dữ liệu kiểm thử đầu tiên gây lỗi khi chạy chương trình thì quá trình kiểm thử thất bại Vấn đề này xuất hiện phổ biến khi sinh dữ liệu kiểm thử cho các hàm có tham số truyền vào kiểu con trỏ

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 bộ giá trị ở bước 3 (đường

thi hành)

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, quá trình sinh dữ liệu kiểm thử kết thúc Ngược lại, đường thi hành kế tiếp được sinh ra dựa trên trạng thái hiện tại của đồ thị Nhiều chiến thuật chọn đường thi hành kế tiếp được đề xuất trong các nghiên cứu khác nhau từ 2005 trở lại nay

Bước 6 Nếu ta tìm được thi hành kế tiếp, ta xây dựng hệ ràng buộc ứng với

đường thi hành này bằng cách áp dụng kĩ thuật thực thi tượng trưng

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 5 để tìm một đường thi hành tốt hơn Ngược lại, quay lại bước 3 để chạy dữ liệu kiểm thử kế tiếp này

Trang 19

PHƯƠNG PHÁP KIỂM THỬ TỰ ĐỘNG DỰ ÁN C/C++

SỬ DỤNG KĨ THUẬT KIỂM THỬ TỰ ĐỘNG ĐỊNH HƯỚNG

Chương này trình bày chi tiết phương pháp đề xuất để sinh dữ liệu kiểm thử tự động cho dự án C/C++ Phần đầu tiên trình bày tổng quan của phương pháp gồm hai pha chính Phần kế tiếp trình bày pha tiền xử lý mã nguồn Pha sinh dữ liệu kiểm thử được trình bày ở phần kế tiếp Các kĩ thuật cải tiến thời gian sinh dữ liệu kiểm thử được trình bày ở phần cuối cùng chương này

3.1 Tổng quan phương pháp đề xuất

Hình 3.1 trình bày tổng quan phương pháp sinh dữ liệu kiểm thử đề xuất gồm hai pha chính: pha tiền xử lý mã nguồn và pha sinh dữ liệu kiểm thử Bước đầu tiên trong pha tiền xử lý mã nguồn xây dựng cây cấu trúc tương ứng Các đỉnh trên cây cấu trúc

này đại diện cho các thành phần mã nguồn như các thành phần vật lý (tệp, thư mục), các thành phần lô-gic (class, struct, namespace, khai báo, v.v.) Nói cách khác, thay

vì phân tích trực tiếp trên dự án C/C++, quá trình phân tích được tiến hành trên cây cấu trúc này Sau khi cây cấu trúc đã được xây dựng, bước kế tiếp trong pha đầu tiên chèn thêm các câu lệnh đánh dấu vào các hàm trong dự án C/C++ Nhờ có các câu lệnh đánh dấu này, khi các hàm này được gọi thì tập các câu lệnh được thực thi được lưu lại Tập các câu lệnh này kết hợp lại theo thứ tự thực hiện được gọi là đường thi hành Đầu ra của pha đầu tiên gồm cây cấu trúc tương ứng với dự án C/C++, và tập các hàm được chèn các câu lệnh đánh dấu

Hình 3.1 Tổng quan phương pháp đề xuất

Đầu vào trong pha sinh dữ liệu kiểm thử gồm hàm cần kiểm thử cùng với thông

tin cấu hình (tiêu chí độ phủ, số lần lặp tối đa của vòng lặp, cận biến số nguyên/kí

tự, số phần tử tối đa của mảng) Đầu ra của pha thứ hai là biên bản kiểm thử Trong

Trang 20

pha này, đồ thị dòng điều khiển tương ứng với hàm cần kiểm thử được xây dựng Sau đó, dựa trên đồ thị dòng điều khiển này, tập dữ liệu kiểm thử thỏa mãn độ phủ

và thông tin cấu hình được sinh ra Để sinh ra tập dữ liệu kiểm thử này, cây cấu trúc cùng với tập các hàm đã đánh dấu được sử dụng trong quá trình xử lý Cuối cùng, tập dữ liệu kiểm thử này được chuyển sang các bộ mã nguồn kiểm thử tương ứng tuân theo chuẩn của Google Test

3.2 Pha tiền xử lý mã nguồn

3.2.1 Xây dựng cây cấu trúc từ dự án C/C++

Định nghĩa 3.1 (Component - Thành phần dự án) Thành phần trong dự án C/C++

gồm hai loại: thành phần vật lý và thành phần lô-gic Thành phần vật lý bao gồm tệp (header, mã nguồn) và thư mục Thành phần lô-gic gồm các thành phần trong mã nguồn (hàm, biến, khai báo namespace, khai báo class, khai báo struct, v.v.)

Định nghĩa 3.2 (Logic dependency – phụ thuộc lô-gic) Hai thành phần dự án n1và

n2 có quan hệ phụ thuộc lô-gic khi n2 là tham chiếu của n1, hoặc n1 là tham chiếu của thành phần n2

Bảng 3.1 trình bày một vài danh sách quan hệ phụ thuộc lô-gic điển hình trong các dự án C/C++

Bảng 3.1 Danh sách quan hệ phụ thuộc lô-gic điển hình

Quan hệ phụ thuộc

class Student trong

Trang 21

(#include, header file) Sử dụng từ khóa #include

để sử dụng các hàm trong header

Header school.h trong

ví dụ Mã nguồn 3.1

(Khai báo hàm, định

nghĩa hàm)

Trong một lớp, hàm được khai báo nhưng lại được định nghĩa ở nơi khác

Hàm

checkFirstSubject

trong Mã nguồn 3.1

(hàm, biến static) Hàm sử dụng biến static

được định nghĩa ngoài hàm

Định nghĩa 3.3 (Physical dependency – phụ thuộc vật lý) Hai thành phần dự án n1và

n2 có quan hệ phụ thuộc vật lý khi n2 là một thành phần con của n1

Định nghĩa 3.4 (Structure tree - Cây cấu trúc) Cây cấu trúc của một dự án C/C++

được định nghĩa S = (V, E), trong đó V = {n0, n1, …, nk-1} đại diện danh sách các thành phần dự án E = {(𝑛𝑖, 𝑛𝑗)|𝑛𝑖, 𝑛𝑗 ∈ 𝑉, 0 ≤ 𝑖, 𝑗 ≤ 𝑘 − 1} ⊂ 𝑉 × 𝑉 đại diện danh sách các phụ thuộc vật lý và phụ thuộc lô-gic giữa các thành phần dự án

// school.h

namespace school{

class Student;

typedef Student stu;

class Student{

private: Subject* subjects; char* name; int age;

public:

Subject* getSubjects(){return subjects;}

char* getName(){return name;}

Trang 22

Mã nguồn 3.1 Ví dụ một phần mã nguồn trong dự án C/C++

Hình 3.2 mô tả một phần cây cấu trúc của một dự án C/C++ liên quan đến quản

lý sinh viên được trình bày trong Mã nguồn 3.1 Mã nguồn gồm khai báo namespace

school đặt trong tệp header school.h; và tệp mã nguồn school.cpp Tệp mã nguồn

school.cpp nạp header school.h thông qua từ khóa #include Trong namespace

school, lập trình viên định nghĩa đối tượng Student có kiểu class, và đối tượng Subject

có kiểu struct Mỗi đối tượng sinh viên có các thuộc tính gồm danh sách môn học

sinh viên đang học và đã từng học trong các kì trước đó (subjects), thông tin cá nhân (name, age) Hàm checkFirstSubject kiểm tra thông tin môn học mà sinh viên đã từng học Hàm này được khai báo trong đối tượng Student nhưng được định nghĩa ở tệp school.cpp

Hình 3.2 Ví dụ cây cấu trúc của một dự án C/C++ điển hình

Trang 23

Cây cấu trúc mô tả trong Hình 3.2 biểu diễn được hai loại quan hệ phụ thuộc trong mã nguồn dự án cần phân tích Các đỉnh trong đồ thị ứng với các thành phần

mã nguồn (namespace school, class Student, v.v.), hoặc các thành phần vật lý (tệp

school.h, school.cpp) Các cạnh mũi tên nét liền nối hai thành phần thể hiện quan hệ

phụ thuộc vật lý Ví dụ, quan hệ (school.cpp, namespace school), (school.cpp,

main()) Các cạnh mũi tên nét đứt mô tả quan hệ phụ thuộc về mặt lô-gic giữa hai

thành phần Ví dụ, định nghĩa của hàm checkFirstSubjects có quan hệ phụ thuộc gic với khai báo của nó đặt trong class Student

lô-3.2.2 Chèn các câu lệnh đánh dấu vào hàm

Sau khi cây cấu trúc được xây dựng hoàn chỉnh, bước kế tiếp tiến hành chèn các câu lệnh đánh dấu vào hàm Khi hàm được thực thi, các câu lệnh đánh dấu này xuất danh sách các câu lệnh được thực hiện ra một tệp ngoài Bảng 3.2 trình bày các luật chèn

câu lệnh đánh dấu vào hàm Hàm mark được gọi là câu lệnh đánh dấu và được chèn

ở từng câu lệnh được thực hiện Kí hiệu <A> đại diện nội dung của A Về bản chất, hàm mark(“<A>”) sẽ in nội dung A ra một tệp được định nghĩa từ trước

Bảng 3.2 Luật chèn câu lệnh đánh dấu vào hàm

Gán, khai báo, throw/

For (mark(“<init>”) && init, mark(“<condition>”)

&& condition, mark(“<increment>”) &&

increment){…}

Trang 24

trả về tên môn học đầu tiên mà sinh viên đã học Khối lệnh đánh dấu của câu lệnh

này là mark(“char* firstNameSubject = sv.getSubjects()[0].name”) sẽ in nội dung

câu lệnh này ra một tệp ngoài được định nghĩa từ trước Bởi thế, khi hàm

checkFirstSubject thực thi xong, ta chỉ cần phân tích nội dung tệp ngoài này để lấy

danh sách câu lệnh được thực hiện

int school::Student::checkFirstSubject(school::Student sv) {

mark("{");

1 mark("char* firstNameSubject = sv.getSubjects()[0].name;");

char* firstNameSubject = sv.getSubjects()[0].name;

Mã nguồn 3.2 Ví dụ hàm checkFirstSubject sau khi chèn câu lệnh đánh dấu

3.3 Pha sinh dữ liệu kiểm thử

Sau khi dự án đầu vào được tiền xử lý xong, bước kế tiếp là sinh dữ liệu kiểm thử từ một hàm cho trước Thuật toán 3.1 trình bày các bước trong thuật toán sinh dữ liệu kiểm thử LDFS Cấu hình sinh dữ liệu kiểm thử gồm: số lần lặp tối đa của một vòng

Trang 25

lặp (max_loop), cận biến số/kí tự (bound), kích thước mảng tối đa (max_size), và tiêu chí độ phủ (cov) Đầu ra của Thuật toán 3.1 là danh sách các dữ liệu kiểm thử thỏa

mãn hai mục tiêu với cấu hình cho trước gồm số bộ dữ liệu kiểm thử nhỏ và độ phủ đạt được lớn nhất có thể

Thuật toán 3.1 Thuật toán LDFS sinh dữ liệu kiểm thử

Trong Thuật toán 3.1, để giảm độ phức tạp phân tích chương trình khi sinh dữ liệu kiểm thử, đầu tiên dữ liệu kiểm thử được sinh với số lần lặp tối đa bằng 1, kế tiếp bằng 0, 2, v.v., đến số lần lặp tối đa đã được cấu hình (dòng 2) Số lần lặp tối đa bằng 1 đồng nghĩa nhánh đúng và sai của câu lệnh điều kiện trong khối lệnh điều

Trang 26

khiển được đi qua Số lần lặp tối đa bằng 0 tức là ta sinh dữ liệu kiểm thử chỉ đi qua nhánh sai của câu lệnh điều kiện trong khối lệnh điều khiển Tương tự như vậy với các giá trị số lần lặp tối đa còn lại Bằng cách xuất phát từ giá trị số lần lặp tối đa bằng 1, ta kì vọng có thể sinh sớm được dữ liệu kiểm thử đi qua mọi nhánh đúng và sai của các câu lệnh điều kiện Với từng giá trị số lần lặp tối đa, các bước trong vòng

for (dòng 3) được mô tả như sau Đầu tiên, tập các đường thi hành có thể thực thi

được xây dựng (may-have test paths) với số lần lặp tối đa từng vòng lặp bằng 1 Sau

đó, những đường thi hành không làm tăng độ phủ bị loại bỏ, ta thu được tập đường

thi hành mới (uncovered test paths) (dòng 6) Kế tiếp, những đường thi hành trong tập mới này được xếp hạng theo tiêu chí tăng độ phủ, tiêu chí độ dài, v.v (prioritized

uncovered test paths) (dòng 7) Đường thi hành được xếp hạng cao nhất sẽ được phân

tích để sinh dữ liệu kiểm thử kế tiếp Quá trình này lặp đi lặp lại khi tập xếp hạng hết đường thi hành (dòng 3), hoặc độ phủ đạt được tối đa (dòng 15) Trường hợp tập xếp

hạng hết đường thi hành, số lần lặp kế tiếp được chọn từ biến loops để bắt đầu chu

trình sinh dữ liệu kiểm thử mới Quá trình sinh dữ liệu kiểm thử kết thúc khi một trong những tiêu chí sau thỏa mãn gồm: vượt quá thời gian cấu hình, độ phủ tối đa hoặc đạt số lần lặp tối đa

3.3.1 Xây dựng đồ thị dòng điều khiển từ mã nguồn

Phần này trình bày bước đầu tiên trong thuật toán LDFS (dòng 1) Đồ thị dòng điều khiển được sử dụng để tính độ phủ đạt được từ đường thi hành, và sinh tập đường thi hành có thể thực thi được Tư tưởng chính của thuật toán xây dựng đường thi hành

có thể thực thi được là duyệt đồ thị theo chiều sâu (depth-first search)

double average(int value[], int min, int max){

int tcnt = 0, vcnt = 0;

int sum = 0, i = 0;

while (value[i] != -2 && tcnt < 10){

tcnt++;

if (min <= value[i] && value[i] <= max){

sum = sum + value[i];

vcnt++;

} i++;

}

if (vcnt < 0)

Trang 27

return -9;

return sum/vcnt;

}

Mã nguồn 3.3 Mã nguồn hàm average

Hình 3.3 Minh họa đồ thị CFG phủ câu lệnh/nhánh

Tư tưởng chính để xây dựng đồ thị dòng điều khiển từ mã nguồn là áp dụng thuật toán chia để trị Ví dụ cụ thể, Hình 3.3 trình bày đồ thị CFG phủ câu lệnh/nhánh

tương ứng với hàm average trình bày ở Mã nguồn 3.3 thỏa mãn tiêu chí phủ câu lệnh Khởi đầu, hàm average được chia nhỏ thành bốn khối con gồm khối while , khối if, lệnh return và câu lệnh khai báo dòng 2 Như vậy, đồ thị CFG khởi đầu gồm bốn

đỉnh ứng với bốn khối này Bốn khối này được liên kết với nhau theo dạng danh sách

liên kết trong đồ thị CFG Nhận thấy khối while còn có thể phân tích tiếp được, ta tiếp tục chia nhỏ khối while này thành các khối nhỏ hơn gồm (value[i]!=-

2&&tcnt<2) và khối lệnh khi thỏa mãn điều kiện Khối if phân tích tương tự khối while Lệnh return, câu lệnh khai báo và điều kiện kép không cần phân tích tiếp Sau

Trang 28

đó, khối lệnh trong while được phân tích tiếp và quá trình này cứ lặp lại như thế Quá

trình phân tích kết thúc khi mọi đỉnh trong đồ thị CFG không thể chia nhỏ được nữa

Bên cạnh đồ thị phủ cấp câu lệnh/nhánh, đồ thị CFG MC/DC hàm average thể hiện

Trang 29

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 Hiện tại, nhiều công trình đề xuất các phương pháp chọn đường thi hành kế tiếp khác nhau 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 đó Bởi thế, bộ dữ liệu kiểm thử kế tiếp làm tăng độ phủ Phương pháp đơn giản nhất được đề xuất vào năm 2005 bởi nhà nghiên cứu Patrice Godefroid và được

áp dụng trong công cụ DART Theo như phương pháp này, nhánh cuối cùng của đường thi hành mới nhất được phủ định Tuy nhiên, quá trình phủ định này có thể khiến quá trình sinh dữ liệu kiểm thử rơi vào vòng 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 [10] và [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 Ví dụ, mọi vòng lặp chỉ lặp tối đa 100 lần 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 [11] [12] do

áp dụng đồ thị dòng điều kiển để chọn nhánh phủ định tốt nhất Về bản chất, chiến thuật chọn đường thi hành kế tiếp trong luận văn dựa trên trạng thái của đồ thị CFG tương tự CREST và CAUT

3.3.3 Xây dựng hệ ràng buộc từ đường thi hành

Sau khi tập đường thi hành được xếp hạng, ta cần tìm nghiệm từng đường thi hành xuất phát từ đường thi hành có độ ưu tiên cao nhất Nếu đường thi hành độ ưu tiên cao nhất hiện tại vô nghiệm, đường thi hành có độ ưu tiên nhỏ hơn kế tiếp được phân tích Quá trình này kết thúc khi một hệ ràng buộc được xây dựng Hệ ràng buộc này được giải và lưu thành dữ liệu kiểm thử kế tiếp

Quy trình xây dựng hệ ràng buộc từ đường thi hành được mô tả trong hàm

has_solution() trong dòng 9 tại Thuật toán 3.1 gồm hai bước Bước đầu tiên xây dựng

hệ ràng buộc từ đường thi hành Bước kế tiếp, hệ ràng buộc này được giải để tìm nghiệm thỏa mãn

Về bản chất, hệ ràng buộc là hệ phương trình/bất phương trình thu được sau khi phân tích một đường thi hành sử dụng kĩ thuật thực thi tượng trưng Nghiệm của hệ ràng buộc thỏa mãn đi qua các câu lệnh trong đường thi hành khi thực thi nghiệm đó

Trang 30

trong môi trường chạy Các biến trong hệ ràng buộc gồm tham số truyền vào hàm và biến ngoài (biến static, biến external, v.v.) Về cơ bản, số lượng câu lệnh điều khiển trên đường thi hành bằng số lượng phương trình/bất phương trình trong hệ ràng buộc tương ứng

Để xây dựng được hệ ràng buộc từ đường thi hành, kĩ thuật thực thi tượng trưng được sử dụng Kĩ thuật này được đề xuất lần đầu tiên vào năm 1976 và được ứng dụng rộng rãi trong nhiều bài toán khoa học kĩ thuật Tư tưởng chính của thuật thực thi tượng trưng là biểu diễn đường thi hành dưới dạng hệ ràng buộc bằng cách sử dụng mô hình bộ nhớ

a Mô hình bộ nhớ sử dụng trong kĩ thuật thực thi tượng trưng

Mô hình bộ nhớ này có nhiều điểm tương tự với bộ nhớ máy tính Trong các pha sau, SMT-Solver được sử dụng để giải những hệ ràng buộc này để tìm nghiệm thỏa mãn Trong nghiên cứu hiện tại, mô hình bộ nhớ mô phỏng được biến kiểu cơ bản (số nguyên, số thực, kí tự); con trỏ, mảng; biến kiểu dẫn xuất (biến class, struct) Ba thành phần trong mô hình bộ nhớ gồm bảng biến, bộ nhớ lô-gic và bộ nhớ vật lý

Định nghĩa 3.5 (Bộ nhớ vật lý - Physical memory) Bộ nhớ vật lý của bảng biến được

định nghĩa PM = {cell 0, cell1, …, cellk | k >= 0}, trong đó celli (0 <= i <= k) đại diện một giá trị kiểu biểu thức hoặc giá trị cụ thể (kiểu số, kiểu xâu, kiểu kí tự)

Định nghĩa 3.6 (Bộ nhớ lô-gic – Logical memory) Cho hàm fn và bộ nhớ vật lý

tương ứng PM, bộ nhớ lô-gic được định nghĩa như sau: 𝐿𝑀 = {𝑙0, 𝑙1, … , 𝑙𝑛 |𝑛 ≥ 0,

𝑙𝑖 = {(𝑖𝑛𝑑𝑒𝑥𝑗,, 𝑟𝑒𝑓𝑗)|𝑗 ≥ 0, 𝑟𝑒𝑓𝑗 ∈ {𝐿𝑀 \ {𝑙𝑖}, 𝑃𝑀}}, trong đó 𝑙𝑖 đại diện một khối

bộ nhớ lô-gic của mảng/con trỏ 𝑖𝑛𝑑𝑒𝑥𝑗 đại diện chỉ số một phần tử lô-gic trong 𝑙𝑖 𝑟𝑒𝑓𝑗 là giá trị một phần tử lô-gic trong 𝑙𝑖 𝑟𝑒𝑓𝑗 trỏ đến một phần tử bộ nhớ (cell) trong

bộ nhớ vật lý PM hoặc phần tử lô-gic khác trong bộ nhớ lô-gic LM

Cụ thể, bộ nhớ lô-gic LM tương tự như khối bộ nhớ (block) trong RAM Giả sử

ta có biến con trỏ p2, thì p2 trỏ đến một khối bộ nhớ trong bộ nhớ lô-gic LM Bởi vì

số lượng các phần tử mà p2 trỏ đến không xác định, nên kích thước khối bộ nhớ trỏ

đến này không phải giá trị cụ thể Trường hợp thứ hai, với biến con trỏ kiểu mảng,

kĩ thuật trỏ tương tự như đối với con trỏ Trường hợp thứ ba, nếu biến kiểu cơ bản

thì biến này được liên kết trực tiếp với bộ nhớ vật lý PM

Trang 31

Định nghĩa 3.7 (Bảng biến – Table of variables) Cho hàm fn, bộ nhớ vật lý PM và

bộ nhớ lô-gic tương ứng LM, bảng biến được định nghĩa 𝑇 ={𝑣𝑎𝑟0, 𝑣𝑎𝑟1, … , 𝑣𝑎𝑟𝑖, … , 𝑣𝑎𝑟𝑘 | 𝑘 ≥ 𝑖 ≥ 0, 𝑣𝑎𝑟𝑖 = (𝑛𝑖,, 𝑡𝑖, 𝑠𝑖, 𝑣𝑖 | 𝑠𝑖 ≥ 0, 𝑣𝑖 ∈

{𝐿𝑀, 𝑃𝑀})}, trong đó 𝑣𝑎𝑟𝑖 là tham số truyền vào hàm fn hoặc biến toàn cục 𝑛𝑖 đại diện tên của biến lưu trong 𝑣𝑎𝑟𝑖, 𝑡𝑖 đại diện kiểu biến (kiểu cơ bản, kiểu con trỏ, kiểu mảng, kiểu dẫn xuất) 𝑠𝑖 là phạm vi của biến (biến toàn cục, biến cục bộ) 𝒗𝒊 lưu tham

chiếu của biến trỏ đến bộ nhớ vật lý PM hoặc bộ nhớ lô-gic LM

Nói cách khác, bảng biến lưu tất cả các biến trong quá trình phân tích đường thi hành Hai loại biến trong bảng biến gồm: biến truyền vào hàm (có thể là tham số,

hoặc là biến ngoài như biến định nghĩa với từ khóa extern), và biến cục bộ trong hàm

Để phân biệt các loại biến này, mỗi một biến có một tham số tên scope Giá trị scope càng lớn tức là tính cục bộ của biến đó càng cao Giá trị scope bằng GLO tức là biến

toàn cục

Hình 3.5 Mô hình bộ nhớ sử dụng trong kĩ thuật thực thi tượng trưng

Hình 3.5 trình bày mô hình bộ nhớ sử dụng trong phương pháp đề xuất Bộ nhớ

này mô phỏng trạng thái các biến của hàm test trong hình Các tham số truyền vào hàm gồm biến mảng p1, biến con trỏ p2, biến kiểu cơ bản p3 và biến con trỏ kiểu dẫn xuất p4

Trang 32

b Xây dựng hệ ràng buộc từ đường thi hành sử dụng kĩ thuật thực thi tượng trưng Thuật toán xây dựng hệ ràng buộc từ đường thi hành được trình bày chi tiết trong Thuật toán 3.2 Đầu vào thuật toán là một đường đi từ đỉnh đầu tiên đến đỉnh cuối

cùng của đồ thị CFG, bộ nhớ lô-gic LM và bộ nhớ vật lý PM Đầu ra là hệ ràng buộc tương ứng PC Đầu tiên, danh sách tham số truyền vào hàm và biến ngoài được lưu trong bảng biến tableVar (dòng 2) Kế tiếp, từng câu lệnh trên đường thi hành được phân tích để cập nhật bộ nhớ lô-gic LM, bộ nhớ vật lý PM và hệ ràng buộc PC (dòng 3) Tùy thuộc kiểu câu lệnh stm mà các chiến thuật cập nhật LM, PM cũng như PC

được áp dụng

Cụ thể, nếu câu lệnh là câu lệnh gán con trỏ cho một con trỏ khác thì bộ nhớ

LM được cập nhật (dòng 5-7) Nguyên nhân cập nhật bộ nhớ LM bởi vì bộ nhớ này

có vai trò mô phỏng trạng thái của biến kiểu con trỏ Nếu câu lệnh là phép cập nhật

giá trị biến cơ bản thì bộ nhớ PM được cập nhật Cụ thể hơn, giá trị ô (cell) trong bộ nhớ PM tương ứng với biến được cập nhật giá trị sẽ được thay đổi sang giá trị mới (dòng 8, 9) Trường hợp kế tiếp, hệ ràng buộc PC bổ sung thêm bất phương

trình/phương trình về điều kiện giá trị cấp phát cũng như trạng thái bảng biến

tabelVar được cập nhật nếu câu lệnh đang phân tích là câu lệnh cấp phát (dòng 11,

12) Nói rõ ràng hơn, nếu biến con trỏ được cấp phát một vùng nhớ có kích thước

không xác định (gọi là sizeOfAllocation) thì giá trị vùng nhớ đó phải luôn dương để đảm bảo tính đúng đắn của hệ ràng buộc, tức là sizeOfAllocation>=0 Do đó, bất phương trình sizeOfAllocation >=0 được bổ sung vào hệ ràng buộc PC để đảm bảo

nghiệm (hay dữ liệu kiểm thử) không gây lỗi khi thực thi

Trường hợp kế tiếp, nếu câu lệnh stm là khai báo thì bảng biến tableVar bổ sung

thêm biến đó vào danh sách biến hiện tại Đồng thời cập nhật giá trị biến vừa khai báo nếu biến đó được khởi tạo giá trị (dòng 14, 15)

Xét trường hợp câu lệnh stm là câu lệnh điều kiện, nếu giá trị câu lệnh stm luôn

sai thì hệ ràng buộc luôn vô nghiệm (dòng 16-23) Thuật toán trả về hệ ràng buộc

rỗng và kết thúc Ngược lại, nếu câu lệnh stm luôn có nghiệm, ta bỏ qua câu lệnh đó

và không bổ sung vào PC Trường hợp cuối cùng, câu lệnh stm không thể xác định được tính luận lý, hệ ràng buộc PC lưu thêm câu lệnh điều kiện này

Trang 33

Thuật toán 3.2 Thuật toán xây dựng hệ ràng buộc từ đường thi hành

Nếu câu lệnh stm là câu lệnh đánh dấu phạm vi, ta cập nhật bảng biến như sau (dòng 24-30) Trường hợp stm là dấu mở ngoặc nhọn, tức là bắt đầu thân hàm hoặc

Trang 34

thân khối lệnh điều khiển, ta đánh dấu tất cả các biến được khởi tạo từ câu lệnh này

trở đi ở mức level + 1 (trong đó level là giá trị cục bộ hiện tại) Chú ý rằng biến toàn cục luôn có giá trị level cục bộ bằng 0 (kí hiệu GLO) Ngược lại, nếu stm là dấu kết thúc thân hàm/thân khối lệnh điều khiển, ta xóa toàn bộ biến trong tableVar ở có giá

trị cục bộ cao nhất bởi vì những biến đó đã trở nên vô nghĩa

Sau khi tất cả mọi câu lệnh trong khối lệnh điều khiển được phân tích, hệ ràng

buộc PC tiếp tục bổ sung thêm một vài ràng buộc phụ vào để nghiệm tránh gây lỗi khi thực thi (dòng 32-34) Ví dụ, giả sử hiện tại trong PC có một hệ ràng buộc tồn tại phép chia a/b Để đảm bảo nghiệm không gây lỗi chia cho 0, ta cần bổ sung thêm ràng buộc b!=0 (dòng 33) Ví dụ khác, nếu chỉ số mảng là biểu thức trừu tượng S mà không phải hằng số, ta cần thêm ràng buộc S >= 0 (dòng 32) Thuật toán kết thúc

khi mọi câu lệnh trong đường thi hành được phân tích, và hệ ràng buộc phụ được bổ sung; hoặc trong quá trình phân tích một câu lệnh điều kiện luôn sai được phát hiện Trong trường hợp cuối cùng này, hệ ràng buộc trả về rỗng (vô nghiệm)

Ví dụ, xét đường thi hành của đồ thị CFG phủ câu lệnh/nhánh trình bày trong

Hình 3.3 đi qua nhánh đúng điều kiện (value[i] != -2 && tcnt < 10) và đi qua nhánh sai điều kiện (vcnt<0) như sau:

Begin -> (int tcnt = 0, vcnt = 0, sum = 0, i = 0;) -> (value[i] != -2 && tcnt < 10) [1] -> (vcnt < 0) [2] -> (return sum/vcnt) -> End

Hệ ràng buộc tương ứng với đường thi hành trên được mô tả như sau Trong đó, điều kiện [1] được chuyển thành ràng buộc (1), điều kiện [2] được chuyển thành ràng buộc (2)

{ (𝑣𝑎𝑙𝑢𝑒[0] ! = −2)𝑎𝑛𝑑 (0 < 10) (𝟏)

0 < 0 (𝟐)

3.3.4 Giải hệ ràng buộc sử dụng bộ giải SMT-Solver

Bước giải hệ ràng buộc là bước thứ hai trong hàm has_solution() Phần này giải quyết bài toán: “Cho một hệ ràng buộc sinh từ đường thi hành, ta cần tìm một nghiệm thỏa

mãn hệ ràng buộc này.”

Hệ ràng buộc ta sinh từ đường thi hành được biểu diễn dạng trung tố, tuy nhiên các bộ giải SMT-Solver không hỗ trợ giải biểu thức dạng này Bởi thế, những hệ

Ngày đăng: 11/01/2018, 12:21

TỪ KHÓA LIÊN QUAN

TÀI LIỆU CÙNG NGƯỜI DÙNG

TÀI LIỆU LIÊN QUAN

🧩 Sản phẩm bạn có thể quan tâm

w