ĐẠi hỌc quỐc gia hÀ nỘi tr i h c cÔng ngh nguyễn Đức anh

69
ĐẠI HC QUC GIA HÀ NI TRƯỜNG ĐẠI HC CÔNG NGHNguyễn Đức Anh PHƯƠNG PHÁP PHÂN TÍCH MÃ NGUỒN VÀ SINH DLIU KIM THCHO CÁC DÁN C/C++ LUẬN VĂN THẠC SĨ: KỸ THUT PHN MM HÀ NI 2017

Upload: others

Post on 16-Oct-2021

2 views

Category:

Documents


0 download

TRANSCRIPT

ĐẠI HỌC QUỐC GIA HÀ NỘI

TRƯỜNG ĐẠI HỌC CÔNG NGHỆ

Nguyễn Đức Anh

PHƯƠNG PHÁP PHÂN TÍCH MÃ NGUỒN VÀ

SINH DỮ LIỆU KIỂM THỬ CHO CÁC DỰ ÁN C/C++

LUẬN VĂN THẠC SĨ: KỸ THUẬT PHẦN MỀM

HÀ NỘI – 2017

I

ĐẠI HỌC QUỐC GIA HÀ NỘI

TRƯỜNG ĐẠI HỌC CÔNG NGHỆ

Nguyễn Đức Anh

PHƯƠNG PHÁP PHÂN TÍCH MÃ NGUỒN VÀ

SINH DỮ LIỆU KIỂM THỬ CHO CÁC DỰ ÁN C/C++

Ngành: Công nghệ thông tin

Chuyên ngành: Kỹ thuật phần mềm

Mã số: 60480103

LUẬN VĂN THẠC SĨ: KỸ THUẬT PHẦN MỀM

Cán bộ hướng dẫn: PGS. TS. Phạm Ngọc Hùng

HÀ NỘI - 2017

II

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

III

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

IV

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

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ử ....................... 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

VI

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

Tên đầy đủ Tên viết tắt Mô tả

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

Static Testing Kĩ thuật kiểm thử tĩnh

Concolic Testing Kĩ thuật kiểm thử tự động định hướng

Abstract Syntax Tree AST Cây cú pháp trừu tượng

Control Flow Graph CFG Đồ thị dòng điều khiển

C/C++ Development Tooling CDT

Satisfiability Modulo Theories

Solver

SMT-Solver

Boolean Satisfiability Problem SAT

Symbolic Execution SE Kĩ thuật thực thi tượng trưng

Path selection strategy Chiến thuật chọn đường thi hành (trong

DSE)

Test driver Bộ thực thi dữ liệu kiểm thử

Test data Dữ liệu kiểm thử

Test path Đường thi hành (sinh từ đồ thị CFG)

Modified condition/ decision

coverage

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

VII

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

VIII

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

IX

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.

1

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

2

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

3

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++.

4

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.

5

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 = {v1, v2, …, vk | 1 <= k}, trong đó k là số lượng biến truyền

vào hàm, vi 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

6

đ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 = {n0, 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 (ni,nj) thể hiện rằng câu lệnh ni được thực thi

trước câu lệnh nj.

Các cấu trúc điều khiển phổ biến trong C/C++ gồm tuần tự, if-else, while-do,

do-while, for, try-catch. Hình 2.1 minh họa các cấu trúc điều khiển nêu trên. Đỉnh có

kí hiệu c là đỉnh điều kiện.

7

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

8

đá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.

9

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

10

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 lô-

gic

Mô tả Ví dụ

(Khai báo class, định

nghĩa class)

Lớp được khai báo nhưng

lại được định nghĩa ở nơi

khác

class Student trong

Mã nguồn 3.1

(typedef, class) Lớp được sử dụng với tên

định nghĩa dùng keyword

typedef

class Student;

typedef Student stu;

11

(#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; struct Subject{ char* name; int credit; }; class Student{ private: Subject* subjects; char* name; int age; public: Subject* getSubjects(){return subjects;} char* getName(){return name;} int checkFirstSubject(school::stu sv); // ... }; } // school.cpp #include "school.h" int school::stu::checkFirstSubject(school::stu sv) { 1. char* firstNameSubject = sv.getSubjects()[0].name; 2. char tmp = sv.getName()[3]; 3. if (firstNameSubject[0] == 'M') 4. return 0;

12

else { 5. // Use tmp here... 6. return 1; } }

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.

13

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 lô-

gic với khai báo của nó đặt trong class Student.

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

Kiểu block (A) Chèn câu lệnh đánh dấu

Gán, khai báo, throw/

break/continue/return, {, }.

mark(“<A>”); A;

while (<condition>) {…} while (mark(“<condition>”) && <condition>) {…}

do {…}

while (<condition>)

do {…}

while (mark(“<condition>”) && <condition>)

if (<condition 1>){…}

else if (<condition 2>){…}

else {…}

If (mark(“<condition 1>”) && <condition 1>){…}

else if (mark(“<condition 2>”) && <condition

2>){…} else {…}

for (init, condition,

increment){…}

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

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

increment){…}

14

try {…}

catch (<exception 1>){…}

catch (<exception 2>){…}

mark(“try”); try {…}

catch (<exception 1>){ mark(“<exception 1>”); …}

catch (<exception 2>){ mark(“<exception 2>”);

…}

Mã nguồn 3.2 trình bày hàm checkFirstSubject sau khi chèn các câu lệnh đánh

dấu. Ví dụ, câu lệnh đầu tiên “char* firstNameSubject = sv.getSubjects()[0].name”

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; 2. mark("char tmp = sv.getName()[3];");

char tmp = sv.getName()[3]; 3. if (mark("firstNameSubject[0] == 'M'") &&

(firstNameSubject[0] == 'M')) { 4. mark("{"); mark("return 0;"); mark("}");

return 0; }else{

mark("{"); 5. // Use tmp here 6. mark("return 1;"); mark("}");

return 1; } mark("}");

}

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

15

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

16

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)

17

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

18

đó, 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

ở Hình 3.4.

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

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

Phần này trình bày thuật toán xếp hạng đường thi hành được nêu trong dòng 7 thuật

toán LDFS. Tại bước này, từng đường thi hành được xếp hạng để đánh giá độ ưu

tiên. Đườ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ị 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

19

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 đó

20

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 = {cell0, 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.

21

Đị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.

22

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.

23

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

24

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ệ

25

ràng buộc này cần biến đổi về chuẩn đầu vào SMT-Solver – dạng biểu thức biểu thức

chuẩn SMT-Lib. Quy trình xây dựng hệ ràng buộc theo chuẩn SMT-Lib từ hệ ràng

buộc biểu diễn ở dạng biểu thức trung tố (infix expression) sinh từ đường thi hành

được trình bày trong Hình 3.6. Đầu tiên, hệ ràng buộc được biến đổi về dạng biểu

thức hậu tố (postfix expression), sau đó chuyển sang dạng cây (tree expression). Từ

cây, ta xây dựng biểu thức chuẩn SMT-Lib bằng cách duyệt cây đó.

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

Hình 3.7 trình bày quá trình xây dựng hệ ràng buộc chuẩn SMT-Lib từ hệ ràng

buộc sinh từ kĩ thuật thực thi tượng trưng. Đầu tiên, hệ ràng buộc gốc này được

chuyển về dạng biểu thức hậu tố “x 0 > y 1 < || !”. Sau đó, biểu thức hậu tố này được

chuyển thành cây cấu trúc. Các đỉnh trong cây có thể tương ứng với một toán tử

(operator), hoặc toán hạng (operand). Cuối cùng, cây được chuyển về biểu thức

chuẩn SMT-Lib như sau “assert( not (or (> x 0) (< y 1)))”. Từ khóa “not” ứng với

phép phủ định.

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

26

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.

27

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

28

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

biến có tính danh sách liên kết khi biến đó có thuộc tính có kiểu là chính biến đó.

29

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).

4 https://github.com/google/Google Test

30

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.

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

5 http://uet.vnu.edu.vn/~hungpn/CFT4Cpp

31

Về cơ bản, công cụ có kiến trúc hai tầng gồm tầng Presentation và tầng

Business. Tầng Presentation gồm mô-đun chịu trách nhiệm vẽ đồ thị CFG, mô-đun

hiển thị cây cấu trúc của dự án đầu vào, và mô-đun xuất biên bản kiểm thử. Tầng

Business gồm ba mô-đun chính như sau: tiền xử lý mã nguồn, sinh dữ liệu kiểm thử,

và sinh bộ mã nguồn kiểm thử cho từng dữ liệu kiểm thử theo định dạng của Google

Test. Tầng Business tương tác với bộ giải SMT-Solver Z3 và trình biên dịch

MingW64. Trình biên dịch này được tích hợp sẵn trong DevCpp.

Chức năng của công cụ được mô tả qua bài toán sinh dữ liệu kiểm thử cho hàm

Divide. Hàm này nhận hai tham số đầu vào x và y. Nếu giá trị y bằng 0, hàm này trả

về ngoại lệ std::exception. Ngược lại, hàm Divide trả về giá trị x/y.

- Bước 1. Nạp dự án cần phân tích. Đầu tiên, người dùng đẩy dự án C/C++

cần phân tích vào công cụ CFT4Cpp (Hình 4.2). Dự án này có tên

Sample_for_R1_2 do TSDV cung cấp để đánh giá tính hoàn thiện của công cụ

CFT4Cpp. Công cụ tự động dựng cây cấu trúc dự án (phía bên trái). Khung

hiển thị đồ thị dòng điều khiển ở chính giữa. Thông tin mã nguồn hàm đang

kiểm thử, cũng như thông tin cấu hình được thể hiện ở khung bên phải.

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

32

- Bước 2. Cấu hình. Trước khi sinh dữ liệu kiểm thử cho hàm Divide, người

dùng cần cấu hình đường dẫn đến trình biên dịch MingW64

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

- Bước 3. Sinh dữ liệu kiểm thử. Sau khi hoàn thành bước cấu hình trình biên

dịch, người dùng tiến hành sinh dữ liệu kiểm thử cho hàm Divide (Hình 4.3).

Thời gian sinh dữ liệu kiểm thử khoảng 5 giây, và thời gian này có thể nhanh

hơn hoặc chậm hơn tùy theo cấu hình máy tính. Công cụ sinh ra hai dữ liệu

kiểm thử cho hàm Divide gồm (x, y) = (558, 0), (663, 1). Độ phủ nhánh đạt

được bằng 100%.

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

33

- Bước 4. Xuất biên bản kiểm thử. Công cụ hỗ trợ người dùng xuất kết quả

kiểm thử ra biên bản MS Excel, hoặc xuất mã nguồn kiểm thử cho từng giá trị

dữ liệu kiểm thử sinh ở bước 3 (Hình 4.5). Mã nguồn kiểm thử tuân theo chuẩn

của framework Google Test.

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

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

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

Thư viện Z36 là một trong những thư viện giải hệ ràng buộc khá mạnh mẽ. Thư viện

này hỗ trợ hai môi trường gồm Window và Ubuntu. Để giải hệ ràng buộc, ta chuyển

hệ này về biểu thức chuẩn SMT-Lib. Sau đó, biểu thức SMT-Lib này được giải qua

dòng lệnh sử dụng Z3. Giả sử biểu thức SMT-Lib này lưu trong D:/constraints.txt

lưu tại ổ D, cú pháp gọi bộ giải Z3 như sau:

[đường dẫn z3.exe] –smt2 D:/constraints.txt

6 https://github.com/Z3Prover/z3

34

Người dùng có thể giải hệ trực tuyến qua hệ thống Z3 cung cấp sẵn tại link

https://rise4fun.com/Z3. Mã nguồn 4.1 minh họa một hệ ràng buộc theo chuẩn SMT-

Lib. Hình 4.6 trình bày nghiệm hệ ràng buộc này khi chạy Z3 trên hệ thống trực

tuyến nêu trên. Nếu hệ ràng buộc vô nghiệm, hệ thống trả về UNSAT. Ngược lại, hệ

thống thông báo SAT cũng với nghiệm thể hiện ở dưới. Trường hợp hệ ràng buộc có

vố số nghiệm, Z3 trả về một nghiệm bất kỳ. Trong ví dụ này, Z3 trả về một bộ giá trị

ngẫu nhiên (x, y) = (0, -1).

(declare-const x Int) (declare-const y Int) (assert (< x 1)) (assert (> x y)) (check-sat) (get-model)

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

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

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

Cho đến thời điểm hiện tại, nhiều thư viện hỗ trợ khả năng phân tích mã nguồn C/C++

như CDT7, Clang8, v.v. Trong nghiên cứu này, tôi sử dụng thư viện CDT để phân

tích cú pháp mã nguồn.

7 https://www.eclipse.org/cdt

8 https://clang.llvm.org

35

Khi đẩy mã nguồn vào CDT, đầu ra là cây AST ứng với mã nguồn đó. Mã nguồn

đẩy vào có thể là một hàm, một câu lệnh, một tệp header, v.v. Các đỉnh trong cây

AST này chưa có khả năng binding bởi vì thư viện này được sử dụng ngoài Eclipse.

Chẳng hạn, ta đẩy vào mã nguồn có câu lệnh x = y + 1. Đầu ra AST ứng với câu lệnh

này thể hiện rằng biến x có kiểu là IASTIdExpression mà không thể hiện rõ ràng biến

này có khai báo “int x;”. Nếu muốn bắt được thông tin này, ta cần sử dụng thư viện

CDT dưới dạng plug-in Eclipse.

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

Thư viện Jeval9 là một thư viện không thể thiếu khi xây dựng công cụ CFT4Cpp.

Đầu vào thư viện này là một biểu thức. Đầu ra là giá trị biểu thức đó. Chẳng hạn, với

đầu vào là “1+2+3” thì đầu ra Jeval trả về bằng 6.

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

Tác giả đã tiến hành thực nghiệm trên công cụ CFT4Cpp với các bộ dữ liệu thực tế

để thể hiện tính hiệu quả của phương pháp đề xuất. Bốn công cụ được so sánh gồm

KLEE, PathCrawler, CAUT, và CREST. Thực nghiệm được tiến hành trên máy

Intel(R) Core(TM) i5-4200U CPU @1.6GHz - 2.3GHz với 4GB bộ nhớ.

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

PathCrawler

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

Công cụ Phiên bản Ngôn ngữ hỗ

trợ

Môi trường

hỗ trợ

Trình biên

dịch

Phương

pháp sinh

KLEE 2016 C Ubuntu Clang EGT

CAUT 2014 C Ubuntu GCC DSE

CREST 2014 C Ubuntu GCC DSE

PathCrawler 2012 C Web-based GCC DSE

CFT4Cpp 2017 C/C++ Window MingW DSE

9 http://jeval.sourceforge.net/

36

Bảng 4.1 trình bày các công cụ được so sánh với CFT4Cpp gồm KLEE [2],

CAUT [12], PathCrawler [13] và CREST [1]. Công cụ KLEE áp dụng kĩ thuật EGT

để sinh dữ liệu kiểm thử thỏa mãn hai tiêu chí gồm độ phủ tối đa và phát hiện các lỗi

tiềm ẩn. Các công cụ còn lại sử dụng kĩ thuật kiểm thử tự động định hướng.

Để đảm bảo tính khách quan, CFT4Cpp được so sánh với từng công cụ nêu trên

với cùng nhóm cấu hình mà các công cụ này hỗ trợ. Bảng 4.2 trình bày thông tin hai

nhóm thực nghiệm gồm: nhóm A (KLEE, PathCrawler), và nhóm B (CAUT,

CREST). Các thông tin cấu hình gồm: khoảng của biến kí tự, khoảng của biến kiểu

số, số phần tử của mảng, số lần lặp tối đa của vòng, và chiến thuật chọn đường thi

hành. Nếu một cấu hình của công cụ đánh dấu “-“, tức là công cụ không hỗ trợ cài

đặt loại thông tin đó. Chiến thuật chọn đường thi hành được chọn dựa trên bài báo

tương ứng của từng công cụ thực nghiệm. Đó là những chiến thuật đã được chính

minh tính hiệu quả trong các bài toán thực tế. Cụ thể, KLEE đưa ra khá nhiều chiến

thuật chọn đường thi hành khác nhau, nhưng thực nghiệm chỉ ra chiến thuật hiệu quả

nhất có tên nurs:md2u.

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

Nhóm A Nhóm B

KLEE PathCrawler CFT4Cpp CAUT CREST CFT4Cpp

Khoảng biến kí tự [32..126] Phạm vi kiểu biến

Khoảng biến kiểu số [-100..200] Phạm vi kiểu biến

Kích thước mảng [0..10] 10

Số lần lặp tối đa - 11 11 - 11 11

Chiến thuật chọn

đường thi hành

nurs:

md2u k-path LDFS pps cfg LDFS

Bảng 4.2 mô tả thông tin các ví dụ tiến hành thực nghiệm. Tất cả những ví dụ

này được lấy ngẫu nhiên trên github. Kí hiệu CC mô tả độ phức tạp của ví dụ được

tính theo công thức Mc-Cabe. Kí hiệu LOC là số dòng mã nguồn theo tiêu chuẩn của

Eclipse (không tính số dòng thừa, v.v.). Tất cả các hàm kiểm thử đều có vòng lặp,

trừ hàm ArrayCmp. Một vài hàm có đệ quy như reverse, Fibonacci, v.v.

37

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ử

Hàm

Mô tả

Hàm

Mô tả

CC LOC CC LOC

uninit_var(int[3],int[3]) 7 12 concatenate(char[],char[]) 3 11

ArrayCmp(int,char*,char*) 4 7 concatenate_string(char*,char*) 3 8

check_prime(int) 4 7 reverse2(char*,int,int) 2 8

check_armstrong(long long) 4 14 copy_string(char*,char*) 2 6

power(int,int) 2 6 string_length(char*) 2 6

print_floyd(int) 3 10 Fibonacci(int) 3 4

find_minimum(int[],int) 3 9 find_maximum(int[],int) 3 9

linear_search1(long[],long,long) 3 6 add_digits(int) 2 6

linear_search2(long*,long,long) 3 6 reverse(long) 2 8

bubble_sort(long[],long) 4 8

Bảng 4.4 trình bày kết quả thực nghiệm của nhóm A gồm hai công cụ KLEE và

PathCrawler. Kí hiệu num đại diện số lượng bộ dữ liệu kiểm thử sinh ra. Kí hiệu cov

đại diện độ phủ đạt được với số bộ dữ liệu kiểm thử sinh ra đó. Trong tất cả những

ví dụ này, CFT4Cpp đều đạt độ phủ tối đa với số bộ dữ liệu kiểm thử ít hơn hẳn so

với hai công cụ còn lại. Trong ví dụ đầu tiên uninit_var, cả ba công cụ đều đạt độ

phủ tối đa. Tuy nhiên, số bộ dữ liệu kiểm thử do CFT4Cpp sinh ra bằng 5, trong khi

đó KLEE sinh tới 27 bộ dữ liệu kiểm thử và PathCrawler sinh lớn hơn 20 bộ dữ liệu

kiểm thử. Điều này tương tự với các ví dụ power, print_floyd. Trong nhiều ví dụ, số

bộ dữ liệu kiểm thử do KLEE sinh ra khá lớn (kí hiệu n/a). Lý giải cho vấn đề này là

do mục tiêu sinh dữ liệu kiểm thử của KLEE không chỉ đạt độ phủ tối đa mà còn

phát hiện lỗi. Vì thế, KLEE sẽ kiểm tra mọi trường hợp có thể xảy ra trong mã nguồn.

38

Điều đó dẫn đến số lượng bộ dữ liệu kiểm thử sinh bởi KLEE khá lớn gây khó khăn

cho quản lý.

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

No.k Hàm

CFT4Cpp KLEE PathCrawler

Số bộ dữ liệu

kiểm thử

Độ

phủ

Số bộ dữ liệu

kiểm thử

Độ

phủ

Số bộ dữ liệu

kiểm thử Độ phủ

1 uninit_var 5 100 27 100 >20 100

2 ArrayCmp 3 100 82 100 1 16.67

3 check_prime 3 100 n/a 100 >20 66.67

4 check_armstrong 2 100 n/a 100 5 100

5 power 2 100 > 20 100 >20 100

6 print_floyd 2 100 n/a 100 >20 100

7 find_minimum 2 100 n/a 100 - -

8 linear_search1 2 100 n/a 100 1 25

9 linear_search2 2 100 n/a 100 2 25

10 bubble_sort 2 100 n/a 100 1 16.67

11 concatenate 1 100 n/a 100 - -

12 concatenate_string 1 100 n/a 100 - -

13 reverse2 2 100 8 100 1 50

14 copy_string 2 100 10 100 - -

15 string_length 2 100 10 100 - -

18 Fibonacci 2 100 n/a 100 - -

19 find_maximum 2 100 n/a 100 - -

20 add_digits 2 100 4 100 2 100

21 reverse 2 100 4 100 2 100

39

Điểm thứ hai từ kết quả thực nghiệm trong Bảng 4.4 là công cụ PathCrawler đạt

kết quả không tốt trong nhiều ví dụ. Tiêu biểu như ví dụ ArrayCmp, linear_search1,

v.v. công cụ đạt được độ phủ khá nhỏ. Thậm chí, trong ví dụ find_minimum,

concatenate, v.v. công cụ PathCrawler không thể sinh được dữ liệu kiểm thử (kí hiệu

bởi dấu “-“). Về vấn đề đầu tiên, nguyên nhân chính do công cụ PathCrawler chưa

xử lý được nhiều trường hợp mã nguồn khi áp dụng kĩ thuật thực thi tượng trưng.

Nguyên nhân của vấn đề thứ hai liên quan đến phương pháp kĩ thuật kiểm thử tự

động định hướng. Trong phương pháp kĩ thuật kiểm thử tự động định hướng, dữ liệu

kiểm thử đầu tiên được sinh ngẫu nhiên, và sau đó được đẩy vào chương trình để

chạy. Nếu dữ liệu kiểm thử đầu tiên không chạy được, tức là chương trình có lỗi, quá

trình sinh dữ liệu kiểm thử thất bại.

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

No.k Hàm

CFT4Cpp CREST CAUT

Số bộ dữ

liệu kiểm

thử

Độ

phủ

Số bộ dữ

liệu kiểm

thử

Độ

phủ

Số bộ dữ

liệu kiểm

thử

Độ phủ

1 uninit_var 5 100 24 100 11 91.69

2 ArrayCmp 3 100 6 100 4 100

3 check_prime 3 100 5 100 3 83.331

4 check_armstrong 2 100 4 100 3 87.5

5 power 2 100 - - 2 100

6 print_floyd 2 100 - - - -

7 find_minimum 2 100 - - 4 100

8 linear_search1 2 100 3 100 2 100

9 linear_search2 2 100 3 100 2 100

10 bubble_sort 2 100 - - 3 100

11 concatenate 1 100 2 100 - -

40

12 concatenate_string 1 100 2 100 - -

13 reverse2 2 100 3 100 2 100

14 copy_string 2 100 2 100 1 100

15 string_length 2 100 2 100 1 100

18 Fibonacci 2 100 - - - -

19 find_maximum 2 100 - - 4 100

20 add_digits 2 100 2 100 1 100

21 reverse 2 100 2 100 1 100

Bảng 4.5 trình bày kết quả thực nghiệm khi so sánh công cụ CFT4Cpp với hai

công cụ CREST và CAUT. Hai công cụ này đều áp dụng kĩ thuật kiểm thử tự động

định hướng nên có những điểm yếu như công cụ PathCrawler nêu trên. Hai công cụ

CREST và CAUT đều cho kết quả kiểm thử tốt hơn hẳn so với PathCrawler. Thứ

nhất, trong ví dụ copy_string, string_length, add_digits, và reverse, công cụ CREST

cho độ phủ 100% trong khi số lượng bộ dữ liệu kiểm thử bằng CFT4Cpp. Nguyên

nhân chính bởi vì chiến thuật chọn đường thi hành của CREST tốt hơn PathCrawler

nên số bộ dữ liệu kiểm thử nhỏ hơn hẳn. Thứ hai, công cụ CAUT thể hiện tính ưu

điểm hơn trong các ví dụ copy_string, string_length, add_digits và reverse so với hai

công cụ CFTCpp và CREST. Những hàm này đều chứa vòng lặp. CAUT chỉ cần sinh

ra đúng một bộ dữ liệu kiểm thử mà vẫn đạt độ phủ tối đa. Lý giải cho vấn đề này là

do tính may mắn khi CAUT sinh dữ liệu kiểm thử đầu tiên theo phương pháp ngẫu

nhiên. Với dữ liệu kiểm thử đầu tiên “may mắn” này, độ phủ đạt được là 100%. Tuy

nhiên, CAUT thất bại khi xử lý các hàm print_floyd, concatenate, v.v.

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

Phương pháp đề xuất LDFS ưu tiên sinh dữ liệu kiểm thử lặp khối lệnh điều khiển

lặp (while...do, do..while, for) càng nhỏ càng tốt. Cụ thể, tập dữ liệu kiểm thử lặp

vòng lặp 1 lần, 0 lần, 2 lần, v.v., đến lặp tối đa n lần được sinh ra theo thứ tự (n là

giá trị cấu hình số lần lặp tối đa do người dùng định nghĩa). Một khi tập dữ liệu kiểm

thử đạt độ phủ tối đa, quá trình sinh dữ liệu kiểm thử kết thúc mà không quan tâm

41

các lần lặp kế tiếp. Điều đó có nghĩa, tập dữ liệu kiểm thử sinh ra đạt tiêu chí độ phủ

cao và số lượng ít. Thực tế, lỗi có thể phát sinh ở vòng lặp khi số lần lặp đủ lớn. Để

đảm bảo chất lượng phần mềm, tập dữ liệu kiểm thử thỏa mãn một trong hai điều

kiện sau cần được sinh ra gồm (i) độ phủ lớn và số lượng bộ dữ liệu kiểm thử nhỏ,

(ii) kiểm thử vòng lặp. Tập dữ liệu kiểm thử thỏa mãn điều kiện đầu tiên được sinh

bằng cách áp dụng LDFS. Phần này trình bày thực nghiệm sinh dữ liệu kiểm thử

đánh giá chất lượng vòng lặp theo tiêu chí số lần lặp.

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

Ví dụ

Mô tả Vòng lặp

CC LOC Loại

vòng lặp

Vòng lặp

kiểm thử

Kiểu

biên

Số lần

lặp tối đa Số lần lặp

Số bộ dữ

liệu kiểm

thử

Merge1 6 17 Vòng lặp

nối tiếp

i <3 && j

< 3 Biên 6 0,1,2,5,6,7 1

i<3 Biên 3 0,1,2,3,4 1

j<3 Biên 3 0,1,2,3,4 1

check_

subsequence 6 13

Vòng lặp

lồng nhau

a[c]!= ‘\0’

Không

xác

định

- 0,1,2,30 4

a[c]!=b[d]

&&

b[d]!=

‘\0’

Không

xác

định

- 0,1,2,26 4

find_

frequency 4 6

Vòng lặp

đơn s[c]!=’0’

Không

xác

định

- 0,1,2,50 4

Bảng 4.6 trình bày tập dữ liệu kiểm thử để kiểm tra tính đúng đắn vòng lặp. Ba

loại vòng lặp sinh dữ liệu kiểm thử gồm vòng lặp đơn, vòng lặp lồng nhau (nested

loop) và vòng lặp liên tiếp (concatenated loop). Trong bảng này, CC và LOC tương

42

ứng là độ phức tạp của hàm và số dòng mã nguồn được tính bằng cách sử dụng plugin

metrics2∗∗ của Eclipse. Cột Vòng lặp kiểm thử mô tả khối lặp đang cần sinh dữ liệu

kiểm thử để kiểm tra tính đúng đắn. Cột Kiểu biên mô tả loại khối lặp đang kiểm tra

ở cột Vòng lặp kiểm thử. Nếu vòng lặp đang kiểm thử xác định được biên, số lần lặp

tối đa thể hiện ở cột Số lần lặp tối đa, ngược lại đánh dấu “-“. Cột Số lần lặp mô tả

số lượng lặp của vòng lặp đang kiểm thử để sinh dữ liệu kiểm thử. Cột cuối cùng Số

bộ dữ liệu kiểm thử trình bày số lượng bộ dữ liệu kiểm thử sinh được thành công từ

những lần lặp này.

Ba ví dụ tiêu biểu ứng với ba loại vòng lặp được trình bày trong bảng. Hàm

Merge1 ghép hai mảng thành một mảng tăng dần gồm 3 khối lệnh điều khiển while

nối tiếp nhau (xem mã nguồn tại phụ lục). Do đó, tập dữ liệu kiểm thử cho từng khối

lệnh điều khiển while được sinh ra để kiểm tra tính đúng đắn của vòng lặp. Xét khối

while đầu tiên, điều kiện khối lệnh này xác định được số lần lặp tối đa bằng 6 (i<3

&& j < 3, trong đó i, j khởi tạo bằng 0). Do đó, tập dữ liệu kiểm thử thỏa mãn lặp

khối while đầu tiên này 0 lần (không lặp lần nào), 1 lần, 2 lần, 5 lần, 6 lần (số lần

lặp tối đa), 7 lần (số lần lặp tối đa + 1) được sinh. Trong trường hợp này, chỉ có dữ

liệu kiểm thử lặp 5 lần được sinh thành công. Những lần lặp còn lại không thể sinh

dữ liệu kiểm thử thỏa mãn.

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

Bảng 4.7 so sánh thời gian biên dịch và thực thi dữ liệu kiểm thử giữa phương pháp

cải tiến và phương pháp truyền thống. Tất cả mọi hàm đầu vào đều chứa biến kiểu

dẫn xuất, và thuộc tính của những biến này có kiểu con trỏ. Tiêu chí độ phủ sử dụng

trong bảng thực nghiệm này là độ phủ MC/DC.

Xét ví dụ tiêu biểu calculateZodiac (mã nguồn ở phụ lục), đầu vào của hàm này

là một đối tượng kiểu ngày tháng. Số nhánh thỏa mãn độ phủ điều kiện con trong ví

dụ này lên tới 120 nhánh. Áp dụng phương pháp truyền thống, tổng số lần thực thi

lên tới 74 lần, tổng thời gian biên dịch và chạy vượt quá 10 phút, độ phủ MC/DC đạt

được hơn 90%. Trong đó, hơn nửa thời gian chạy là bước biên dịch 74 bộ thực thi

dữ liệu kiểm thử này. Ngược lại, phương pháp cải tiến chỉ tiến hành biên dịch 74 dữ

43

liệu kiểm thử này duy nhất một lần. Kết quả, tổng thời gian biên dịch và chạy rút

ngắn xuống 3 lần (còn hơn 267 giây).

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

Tên hàm Độ

phủ

Số

lần

thực

thi

Kĩ thuật truyền thống Kĩ thuật cải tiến

Tổng thời

gian (s)

Biên dịch

(s)

Tổng thời

gian (s)

Biên

dịch (s)

calculateZodiac(Date) 90.8 74 656.82 60.15 267.19 2

struct_test1(SinhVien1) 100 4 12.5 76.22 5.62 44.48

class_test1(SinhVien) 75 4 11.92 88.22 4.05 65.19

StackLinkedList::push(Node*) 100 2 6.35 39.53 5.48 24.64

StackLinkedList::pop() 100 3 4.56 81.88 2.13 60.09

StackLinkedList::destroyList() 100 2 3.42 71.36 2.56 55.47

disp(Node*) 100 2 3.45 75.32 2.4 60.42

testDate0(Date) 100 2 23.4 97.39 12.01 94.92

44

KẾT LUẬN

Luận văn đã trình bày một phương pháp phân tích mã nguồn và sinh dữ liệu kiểm

thử tự động cho các dự án C/C++. Kết quả nghiên cứu của luận văn được hiện thực

hóa trong công cụ CFT4Cpp. Tính đúng đắn và hiệu quả của phương pháp đề xuất

đã được chứng minh trong thực nghiệm qua các so sánh với KLEE, PathCrawler,

CUTE, CREST, CAUT. Hiện tại, kết quả nghiên cứu đã được trình bày trong hai hội

nghị gồm NICS 2016 và SoICT 2017.

Mục tiêu chính của luận văn hướng đến giải quyết vấn đề sinh dữ liệu kiểm thử

đầu tiên trong kiểm thử kĩ thuật kiểm thử tự động định hướng, đề xuất phương pháp

sinh tập dữ liệu kiểm thử số lượng nhỏ và đạt độ phủ tối đa. Thứ nhất, để giải quyết

vấn đề sinh dữ liệu kiểm thử đầu tiên, phương pháp đề xuất tiến hành phân tích

chương trình để sinh bộ dữ liệu kiểm thử đầu tiên thay vì sinh ngẫu nhiên. Bởi thế,

quá trình thực thi dữ liệu kiểm thử đầu tiên sẽ gây ít lỗi hơn so với phương pháp sinh

ngẫu nhiên, đặc biệt với chương trình thao tác với vùng nhớ như con trỏ, xâu, v.v.

Thứ hai, để sinh số lượng bộ dữ liệu kiểm thử nhỏ mà đạt độ phủ tối đa, luận văn đề

xuất thuật toán LDFS. Tư tưởng chính của thuật toán LDFS là sinh dữ liệu kiểm thử

cho trường hợp càng đơn giản hết mức có thể để giảm thiểu chi phí phân tích mã

nguồn mà vẫn đạt độ phủ tối đa. Cụ thể, tập dữ liệu kiểm thử được sinh thỏa mãn lặp

vòng lặp tối đa một lần, tức là cả nhánh đúng và sai của câu lệnh điều kiện đều được

đi qua. Sau bước này, nếu độ phủ đạt được chưa tối đa, LDFS tiến hành sinh dữ liệu

kiểm thử thỏa mãn lặp vòng lặp tối đa 0 lần (tức chỉ đi theo nhánh sai của câu lệnh

điều kiện), sau đó lặp 2 lần, 3 lần, v.v. đến số lần lặp tối đa được cấu hình từ trước.

Quá trình sinh dữ liệu kiểm thử dừng lại khi độ phủ đạt được là tối đa, hoặc hết thời

gian chạy, hoặc LDFS đạt đến số lần lặp tối đa của vòng lặp.

Công cụ CFT4Cpp cần được tiếp tục phát triển để ngày càng hoàn thiện hơn.

Thứ nhất, CFT4Cpp sẽ được nâng cấp để hỗ trợ đầy đủ cú pháp của các chuẩn C++98,

C++03, C++11, và C++14. Để làm được điều này, kĩ thuật phân tích mã nguồn cần

được cải tiến. Thứ hai, nhiều trường hợp trong dự án C/C++ cần được xử lý thêm

như template, dynamic invocation, overloading, v.v. Thứ ba, thuật toán thực thi

tượng trưng chưa giải quyết được vấn đề sử dụng bộ nhớ không tường minh khi sử

45

dụng hàm liên quan bộ nhớ như memmove, strncat. Kĩ thuật thực thi tượng trưng

hiện tại chưa mô phỏng đầy đủ và chính xác được thay đổi của các biến khi những

câu lệnh kiểu này được gọi. Thứ tư, công cụ cần được cải tiến để sinh dữ liệu kiểm

thử cho các độ phủ khác bên cạnh ba độ phủ đã hỗ trợ sẵn gồm độ phủ nhánh, độ phủ

câu lệnh và độ phủ MC/DC. Cuối cùng, công cụ hướng tới hỗ trợ nhiều trình biên

dịch khác nhau trên các nền tảng khác nhau.

46

PHỤ LỤC // Hàm calculateAge struct Date { int date; int month; int year;}; int calculateZodiac(Date born) { int date1 = born.date; int month1 = born.month; int year1 = born.year; int t; if (((month1 == 3) && (date1 >= 21) && (date1 <= 31)) || ((month1 == 4) && (date1 <= 19))){ t = 1; } else if (((month1 == 4) && (date1 >= 20) && (date1 <= 30)) || ((month1 == 5) && (date1 <= 20))) { t = 2; } else if (((month1 == 5) && (date1 >= 21) && (date1 <= 31)) || ((month1 == 6) && (date1 <= 20))) { t = 3; } else if (((month1 == 6) && (date1 >= 21) && (date1 <= 30)) || ((month1 == 7) && (date1 <= 22))) { t = 4; } else if (((month1 == 7) && (date1 >= 23) && (date1 <= 31)) || ((month1 == 8) && (date1 <= 22))) { t = 5; } else if (((month1 == 8) && (date1 >= 23) && (date1 <= 31)) || ((month1 == 9) && (date1 <= 22))) { t = 6; } else if (((month1 == 9) && (date1 >= 23) && (date1 <= 30)) || ((month1 == 10) && (date1 <= 22))) { t = 7; } else if (((month1 == 10) && (date1 >= 23) && (date1 <= 31)) || ((month1 == 11) && (date1 <= 21))) { t = 8; } else if (((month1 == 11) && (date1 >= 22) && (date1 <= 31)) || ((month1 == 12) && (date1 <= 21))) { t = 9; } else if (((month1 == 12) && (date1 >= 22) && (date1 <= 30)) || ((month1 == 1) && (date1 <= 19))) { t = 10; } else if (((month1 == 1) && (date1 >= 20) && (date1 <= 31)) || ((month1 == 2) && (date1 <= 18))) { t = 11; } else if (((month1 == 2) && (date1 >= 19) && (date1 <= 29)) || ((month1 == 3) && (date1 <= 20))) { t = 12; } else t = -1; return t; }

47

// Hàm Merge1 // Should copy all the elements of ordered arrays t1 and t2 into the ordered array t3 // Link: pathcrawler-online.com void Merge1(int t1[3], int t2[3], int t3[6]) { int i = 0, j = 0, k = 0; while (i < 3 && j < 3) { if (t1[i] < t2[j]) { t3[k] = t1[i]; i++; } else { t3[k] = t2[j]; j++; } k++; } while (i < 3) { t3[k] = t1[i]; i++; k++; } while (j < 3) { t3[k] = t2[j]; j++; k++; } } // Hàm find_frequency // Find frequency of characters in a string // Link: http://www.programmingsimplified.com/c-program-find-characters-frequency void find_frequency(char s[], int count[]) { int c = 0; while (s[c] != '\0') { if (s[c] >= 'a' && s[c] <= 'z') count[s[c] - 'a']++; c++; } }

48

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 Association,

Berkeley, CA, USA, 209–224.

[3]. Cristian Cadar and Koushik Sen. 2013. Symbolic Execution for Software

Testing: Three Decades Later. Commun. ACM 56, 2 (Feb. 2013), 82–90.

[4]. Leonardo De Moura and Nikolaj Bjørner. 2008. Z3: An Efficient SMT Solver.

In Proceedings of the Theory and Practice of Software, 14th International

Conference on Tools and Algorithms for the Construction and Analysis of

Systems (TACAS’08/ETAPS’08). Springer-Verlag, Berlin, Heidelberg, 337–

340.

[5]. 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.

[6]. James C. King. 1976. Symbolic Execution and Program Testing. Commun.

ACM 19, 7 (July 1976), 385–394.

[7]. Guodong Li, Indradeep Ghosh, and Sreeranga P. Rajan. 2011. KLOVER: A

Symbolic Execution and Automatic Test Generation Tool for C++ Programs.

In Proceedings of the 23rd International Conference on Computer Aided

Verification (CAV’11). Springer-Verlag, Berlin, Heidelberg, 609–615.

[8]. George C. Necula, Scott McPeak, Shree Prakash Rahul, and Westley Weimer.

2002. CIL: Intermediate Language and Tools for Analysis and Transformation

of C Programs. In Proceedings of the 11th International Conference on

Compiler Construction (CC ’02). Springer-Verlag, London, UK, UK, 213–

228.

49

[9]. Dirk Riehle. 1997. Composite Design Patterns. In Proceedings of the 12th

ACM SIGPLAN Conference on Object-oriented Programming, Systems,

Languages, and Applications (OOPSLA ’97). ACM, New York, NY, USA,

218–228.

[10]. 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.

[11]. T. Su, G. Pu, B. Fang, J. He, J. Yan, S. Jiang, and J. Zhao. 2014. Automated

CoverageDriven Test Data Generation Using Dynamic Symbolic Execution.

In 2014 Eighth International Conference on Software Security and Reliability

(SERE). 98–107.

[12]. 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.

[13]. 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

[14]. 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.

D~I HOC QUOC GIA HA NQI TRU"ONG D~I HQC CONG NGHE:

------w ------C<)NG HOA XA H<)I CHU NGHiA VIE:T NAM

D(}c l~p- Tl! do- H~nh phuc ***********

Ha N{Ji, ngay .. Z-thang 12 niim 2017

QUYETNGHJ CUA HQI DONG CHAM LU~N VAN TH~C Si

Can Clr Quy€t djnh s6 1138/QD-DT, ngay 23 thang 11 nam 2017 cua Hi~u tru6ng truemg D~i hQC Cong ngh~ v€ vi~c thanh l~p H(>i dbng ch~m lu~n van th~c si cua h9c vien Nguy~n Due Anh, H(>i dbng ch~m lu~n van Th~c si da hQp vao 14h, thu 7, ngay 02 thang 12 nam 2017, Phong 309, Nha E3, Truo·ng D~i hQc Cong ngh~- DHQGHN.

Ten d€ tai lu~n van: Phuong phap phan tich rna ngufin va sinh dfr li~u ki~m thlr cho cac dl}' an C/C++

Nganh: Cong ngh~ Thong tin

Chuyen nganh: Ky thu~t phftn m~m Mas6:

Sau khi nghe h9c vien trinh bay tom tAt lu~n van Th~c si, cac phim bi~n d9c nh~n xet, h9c vien tra lai cac cau hoi, H(>i dbng da hQp, trao d6i y ki€n va thfJng nh~t k€t lu~n:

1. V~ tinh cftp thi~t,Jinh th(ri S\f, y nghia ly lu~n va th\fC ti~n CUa d~ tai lu~n van:

...... {)i~ ... k. .... ~ .... v.tl:;, ....... U: ..... ti. .. ~~-~ ...... ~ ....... ll.~ ..... ~ ..... t.:~~ .. ~ ~ {/ '·7~ , ..

2. Vi bB Ct}.C, phuO"ng phap nghien CUU, tai li~U tham khao, ..... CUa lu~n van:

.................. ·; ............................................... ) .. ...... ·o ..... ~ ............ ~- ................. ·;~ .................... , ........ ·p .... ~- ........ t~· .. .

.. -=: .. /f,£. ... ~·······4····~····~--~·····~······,?16r; ...... ~f;;-]······

::c:::::flt::::j:::;x.;;::::::::;;p;:::::~::::r::·~~:::::::;x:::;&~~:::::~:::::

~:::::::~-:::::zE/::·:~:::~:::.::J??::::::~:::::::i!1':::::·~:1:::--:::::::::::::::::::::·:::-:::

3. v~ k~t qua nghien cuu:

=---ii:~--~----~-~---4···4'. ..... ~~---··;4 ... r-~-f···;£;·~-~·-····~---·· -~······~·-f:··-'·: .. ~ .......................................................................................................................................... . ·············~············;·)·················~··························;·············.:.:.·························/··;1·····················:;·································

=--~ .... ,KJ.J..itl ..... h······-N·····w. ...... Oa_ ....... ~ ....... L ................................... .

:;:~:::::::ZiJ:::::::~::::::~::::::::i :::z:;i::~:;;:_::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

4. H~n ch~ ciia lu~n van (n~u co):

::::::::;za.:::;;z::::::~:::::::~::::~:::::::L::.t.::::::~:~:::::::::::::::::::::::::::::::::::::::::::: -.......... , ... ~······························f'····························································································r································r·······

~ ...... kk. ...... d.:. ..... ;;~ ...... ~ ....... ~ ...... ~~-······Ld: ...... ~~ . .A ..... ~ ...... ~d?. .... . I , -

;::::c;i,::::;;R;i::·::;.:::;t;i:::::;;.J.:::::~::::::~:::::::;z.::::;;.::::~:::::~&J.:'.::::::

5. Danh gia chung va k~t lu~n: /) r

... ~ .... Mt!it. .... ~ ... u, ..... ~ ... cX.. .... uJ. ..... ~Y..::_~······~·-··-~····-~~­

.{;/,1.J .... fH:4. .... ~ ..... y~---·~·-······~····· ~-····~································································

:::::-::::::::::::::::::::::::::::::::::.::::::::::::-:::::::::::::::::::::::::::::::::::::::::::1::::::::::::::::::::::::::::::::::::::::::::::::::.:::::::::::::::::::::::::::: Lu~n van d~t f...~ . ./ 10 di€m. Quy€t ngh! nay OUQ"C ... :s:. I .s:: ... thanh vien cua H(>i a6ng nhftt tri thong qua.

THU' KY H<)I DONG CHU TJCH H<)I DONG

11 T(Lu i!j /Jr( /-{ ~ ,__

XACNH~~CUACOS6DAOT~O

:·r r !~ \ \Tl!'tT '\..T • '·r , , · Ur il ,l r"t.. · ~ ...... l "i "~ "·~ .t

- H ~· n h ph U. c

BAN NHAN XET PHAN BIEN LUAN VAN THAC Si . . . . HQ va ten can b<) phan bi~n: Le Quang Minh

HQC haiTI, hQC vi:

Chuyen nganh: May tinh va H~ th6ng tinh toan

Co quan cong tac: Vi~n Cong ngh~ thong tin - DHQGHN

HQ va ten hQc vien cao hQc: Nguy~n Drrc Anh

Ti~n sy

Ten d~ tai lu~n van: PhrrO'ng phap phan tich rna nguBn va sinh dfr li~u ki~m thrr cho cac dlf an C/C++

Chuyen nganh: Ky thu~t ph~n rn~rn Ma s6: 60.48.01.03

Y KIEN NH~N XET

1. Tinh cdp thiit, thai Sl:f, y ngh'ia khoa hQC va thvc tiln cita aJ tai lu(jn van.

Ngon ngfr l~p trinh C/C++ vfrn la ngon ngfr trong han 70% cac dv an phat tri6n

h~ thf>ng nhung, vi v~y vi~c nghien Clru cac ky thu~t ki6rn thtr cho cac dV an tren ngon

ngfr C/C++ luon la rn<)t doi hoi d~t ra. Lu~n van nay t~p trung nghien c(ru v~ vfrn d~

phan tich rna ngu6n va sinh ca ki6rn thu tv d<)ng cho cac dv an C/C++, d~ tai nay c6 y nghia thai sv, khoa hQc, phil hqp v6i lu~n van th~c sy chuyen nganh ky thu~t ph~n

rnern.

2. Sv khong trung l¢p cua a~ tai nghien ciru so vai cac cong trinh khoa h9c, lucjn

win aa cong b6 a trong va ngoai nuac; tinh trung thvc, ro rang va ady au trong

trich ddn tai li¢u tham khao.

Lu~n van c6 sv t6ng hqp va phat tri6n tir nhi~u ngu6n tai li~u, c6 sv trich dfrn r5

rang. Cach thuc trich dfrn tai li~u nghiern tuc. Khong phat hi~n ra sv trilng l~p v6i cac

lu~n van tru6c day.

3. Sv phil hQ'p gifra ten a~ tai vai n9i dung nghien CUu, cung nhu vai chuyen

nganh va ma s6 aao tqo.

Ten d~ tai va n<)i dung d~ tai phil hqp v6i nhau, phil hqp v6i chuyen nganh va

rna s6 dao t~o.

4. D9 tin ccjy va tinh hi?n aqi cita phuang phap nghien CUu aa sir dZJng a~ hoan

thanh lucjn van.

D~ tai c6 sv trich dfrn tir nhi~u ngu6n tai li~u, cac tai li~u th6 hi~n sv phong phu,

c~p nh~t phil hqp v6i llnh vvc cua d~ tai.

Pi;. __

c(ru.

5. J(dt qzf(z nglzien cz!·u ;n(li CltLt /(lC giu: dc)ng gop mO'i cho s~r pluit trien chuyen

ngc7nh: t/ong gop 1no·i ph~tc v~1 san xuci1. kinh rd. xa h(Ji, an ninh, qubc phong va t/o·i sbng. Y nghza khoa h9c, gia trf vet (19 1 in c4_v czla nhfing kit qua nghien cz'ru.

Lu<;ln van trinh bay t6ng quat chung v~ l) thuy~t sinh ca ki~1n thu tg d<)ng d6i

v&i dv an C/C++, d€ xuclt cac cai ti~n ti~p c<;ln theo huang sinh ca ki~m thu dftu tien d~

tir d6 se c6 s6 luqng ca ki~1n thu nho nhclt v&i d(> phu t6i da. Cac k~t qua nay da duqc

thu nghi~1n va cong b6 trong ky y~u khoa hQc cua 2 H<)i nghi NICS 2016 va SoiCT

2017.

6. Uu aiim va nhu(JC aiim v~ n(Ji dung, bb Cl;lC va hinh thuc cua luqn van.

Lu<;ln van nhin chung dap ung cac yeu cftu ca ban cua lu?n van th~c sy nganh

Cong ngh~ thong tin, chuyen nganh Ky thu?t phftn m€m, tuy nhien v~n con m<)t s6

di~m nen luu y d~ chinh sua cho hoan thi~n han:

- Hinh thuc: Dftu dong cftn thvt vao, khoang each trang phai theo dung quy

dinh. Nhi€u cau van chua duqc chau chu6t, cvt lun ho~c khong r5 nghia;

- Ten gQi cac chuang nen duqc d~t l~i cho r5 y nghia cua cac chuang, hi~n nay

n~u chi nhin vao mvc lvc khong r5 lu?n van lam gi va cai ti~n gi;

- Trong lu?n van xuclt hi~n cac tir lu?n an, kh6a lu?n t6t nghi~p, khong r5 la tac

gia c6 sv nhftm l~n hay c6 sv copy 6 dau trong qua trinh vi~t lu?n van.

7. Kit luqn chung

Lu?n van dap ung duqc yeu cftu cua m<)t lu?n van t6t nghi~p th~c sy, toi de nghi

H<)i dbng cho phep hQc vien duqc phep bao v~ tru&c H<)i dbng.

Ha N <)i, ngayl. thang 1(tnam lJ)) }­

CAN BQ PHAN BI~N

TS. Le Quang Minh

C<)NG HOA xA H<)I CHU NGHiA VI~T NAM D(}c I~p - Tv do - H~nh phuc =====================

BAN NH~N xET PHAN BI~N LU~N VAN TH~C Si

HQ va ten can b{) phan bi~n: Truang Ninh Thu~n

HQc ham, hQc vi: PGS.TS

Chuyen nganh: CNTT

Ca quan c6ng tac: Truemg DH C6ng ngh~ - DHQGHN

HQ va ten hQc vien cao hQc: Nguy~n Due Anh

Tend~ tai lu~n van: Phuang phap phan tich rna ngu6n va sinh dfr li~u ki~m thu cho cac dv an C/C++

Y KIEN NH~N XET

Ki~m thu d6ng vai tro quan trQng trong quy trinh phat tri~n phAn m~m, cho phep ki~m tra cac I6i c6 th~ xay ra trong chuang trinh phAn m~m. M{)t trong cac thach thuc cua

vi~c ki~m thu la sinh dfr li~u dAu vao d~ vi~c ki~m thu chuang trinh hi~u qua.

Lu~n van nghien Clru v~ phuang phap phan tich rna ngu6n va sinh dfr li~u ki~m thir cho cac d\f an C/C++ la m{)t d~ tai c6 y nghia khoa hQc va thvc ti~n cao.

1. N{)i dung lu~n van bao g6m 5 chuang:

Chuang 1 neu phAn d~t vfin d~.

Chuang nay nen chuy~n thanh Gi6i thi~u trong d6 c6 phAn d~t vfin d~, gi6i thi~u n{)i dung va cfiu true kh6a lu~n.

Chuang 2 gi6i thi~u v~ ca sa ly thuy~t, tac gia trinh bay cac ki~n thuc vS ki~m thu.

Chuang 3 trinh bay phuang phap sinh ca ki~m thir tir rna ngu6n chuang trinh C/C++

Chuang 4 gi6i thi~u c6ng C\} h6 trq va thvc nghi~m.

Chuang 5 la phAn k~t lu~n cua lu~n van.

2. D6ng g6p cua lu~n van:

- Tim hi~u v~ ki~m thu va phan tich rna ngu6n

- Xay d\fllg phuang phap sinh ca ki~m thu tir rna chuang trinh C/C++

- Xay dvng c6ng cv sinh ca ki~m thu tv d{)ng

3. H~n ch~ cua lu~n van:

- H~n ch~ v~ cfiu true, trinh bay, I6i chinh ta

- Chua lam r5 vi~c sinh ca ki~m thu thS nao, hi~u qua cua cac ca ki~m thu sinh ra

trong vi~c tim 16i chuang trinh ph~n mSm.

4. KSt lu~n chung: Lu~n van dap irng cac yeu c~u cua m<)t lu~n van th~c sy nganh CNTT, chuyen nganh KTPM.

Ha N<)i, ngay 01 thang 12 nam 2017

:xAc NH~N CUA CO QUAN CONG TAC CAN B<) PHAN BI~N

_/LI~17~ r'l~ •.

/ .--" jV'" '/r)