Chương 1 Giới thiệu Trong quá trình phát triển phần mềm, việc gỡ lỗi phần mềm là một quá trình tốn kém và hầu như thủ công. Chương trình phần mềm sẽ được chạy kiểm thử với bộ kiểm thử nhất định. Nếu có xuất hiện lỗi trong các bài kiểm thử, lập trình viên sẽ phải thực hiện kiểm tra lại chương trình để xác định các câu lệnh gây lỗi. Sau đó, họ sẽ sửa các lỗi này.
Trong các hoạt động của gỡ lỗi phần mềm này, hoạt động định vị lỗi hay xác định vị trí lỗi là hoạt động tốn kém nhất. Quá trình xác định lỗi trong thực tế thường được thực hiện một cách thủ công và tuần tự. Sau khi hoàn thành quá trình phát triển, các lập trình viên thực hiện chạy kiểm thử với chương trình và quan sát kết quả của chương trình. Nếu có một ca kiểm thử có lỗi, các lập trình viên thực hiện đặt các điểm ngắt tại các vị trí mà họ nghi ngờ có khả năng gây lỗi.
Sau đó, họ chạy lại chương trình với ca kiểm thử đã gây lỗi trước đó và quan sát trạng thái của chương trình để xác định lỗi. Lập trình viên sẽ thực hiện lặp đi lặp lại quá trình này cho đến khi ca kiểm thử không cho lỗi nữa. Quá trình trình này gây tốn thời gian và công sức. Do đó, việc áp dụng kỹ thuật và công cụ định vị lỗi có thể giảm đáng kể chi phí gỡ lỗi.
Kỹ thuật định vị lỗi tự động đưa ra các đề xuất, gợi ý về vị trí có thể gây lỗi cho các lập trình viên. Từ đó, họ có thể chỉ cần tập trung vào vị trí được đề xuất để kiểm tra, giúp giảm thời gian và công sức để sửa lỗi. Các nhà nghiên cứu đã đưa ra rất nhiều kỹ thuật để thực hiện định vị lỗi. Các kỹ thuật định vị lỗi truyền thống như: hệ thống nhật ký (logging), lệnh khẳng định (assertions), điểm ngắt (breakpoints) và phân tích hồ sơ (profiling).
Các kỹ thuật định vị lỗi nâng cao như: dựa trên lát cắt (slide-based), dựa trên phổ (spectrum-based), dựa trên xác suất (statistics-based), dựa trên trạng thái (state-based), dựa trên học máy 2 (machine learning-based), dựa trên khai phá dữ liệu (data mining-based), dựa trên mô hình (model-based), … Kỹ thuật định vị lỗi dựa trên phổ (spectrum-based) là kỹ thuật được sử dụng và nghiên cứu phổ biến nhất. Tuy nhiên, các nghiên cứu không cung cấp công khai công cụ kèm theo hoặc các công cụ chưa hoàn chỉnh, khó tiếp cận và cải tiến cho việc định vị lỗi cho các chương trình C/C++. Mục tiêu của luận văn là nghiên cứu các kỹ thuật định vị lỗi hiện nay, từ đó đã lựa chọn ra kỹ thuật định vị lỗi dựa trên phổ phù hợp với các chương trình C/C++. Dựa trên kỹ thuật này, luận văn xây dựng công cụ hoàn chỉnh, có tên HiFa, định vị lỗi tự động cho các ứng dụng C/C++.
HiFa được tích hợp nhiều kỹ thuật SFL để định vị lỗi như: Tarantula, Ochiai, Op2, Dstar, … Bên cạnh phổ theo câu lệnh cơ bản ESHS được sử dụng phổ biến trong các nghiên cứu, luận văn đề xuất thêm các phổ theo cặp def-use DHS cho kỹ thuật định vị lỗi dựa theo phổ. Công cụ HiFa được thử nghiệm với các tập dữ liệu thử nghiệm: dữ liệu lỗi chế tác: bộ Siemens (7 chương trình), chương trình Grep và chương trình Sed; và dữ liệu lỗi thật: chương trình Space. Các phần còn lại của luận văn có nội dung như sau. Chương 2 cung cấp các kiến thức nền tảng về lỗi, kiểm thử phần mềm và các kỹ thuật định vị lỗi.
Chương 3 mô tả phương pháp định vị lỗi đề xuất và kiến trúc của công cụ định vị lỗi. Chương 4 trình bày các thử nghiệm, kết quả và đánh giá về khả năng định vị lỗi của công cụ. Cuối cùng, Chương 5 là kết luận luận văn và các công việc cải tiến công cụ trong tương lai. 3 Chương 2 Kiến thức nền tảng Chương này cung cấp các kiến thức nền tảng cho luận văn.
Khái niệm lỗi, gỡ lỗi và kiểm thử được giới thiệu tổng quan. Sau đó, hiện trạng nghiên cứu các kỹ thuật định vị lỗi hiện này. Luận văn cung cấp các nguyên tắc cơ bản của các kỹ thuật định vị lỗi, hiệu suất và hiệu quả của chúng.1 Khái niệm lỗi: Fault, Error, Failure IEEE Standard [1] định nghĩa fault, error, failure như sau: Fault là một bước, một quá trình hay một định nghĩa dữ liệu sai trong chương trình máy tính. Một fault được tạo ra khi một nhà phát triển viết chương trình.
Error là một trạng thái sai của chương trình khi thực thi có thể gây ra failure. Failure mô tả một chương trình đưa ra kết quả sai lệch so với kết quả mong muốn. Luận văn thực hiện nghiên cứu các phương pháp định vị lỗi khi chương trình có ca kiểm thử thất bại trong quá trình kiểm thử. Nên lỗi được định vị sẽ là loại lỗi Failure.
Khi luận văn sử dụng thuật ngữ "Định vị lỗi" tức là định vị câu lệnh hay thành phần chương trình gây ra failure.2 Kiểm thử phần mềm Kiểm thử phần mềm là một bước cơ bản trong quá trình phát triển phần mềm. Thông qua kiểm thử ta có thể biết được phần mềm có hoạt động đúng với yêu cầu đặc tả. Kiểm thử giúp cải thiện chất lượng của phần mềm. Một ca kiểm thử (testcase) gồm tập các giá trị đầu vào và kết quả đầu ra mong đợi của chương trình.
Một ca kiểm thử thành công khi chương trình hoạt động thỏa mãn hành vi mong đợi, kết quả đầu ra thực tế giống với kết quả đầu ra mong đợi của ca kiểm thử. Ngược lại, một ca kiểm thử thất bại khi chương trình hoạt động không thỏa mãn hành vi mong đợi của ca kiểm thử. Các chiến lược kiểm thử hay việc xây dựng các ca kiểm thử đảm bảo kiểm tra phần mềm dưới các góc độ khác nhau. Xây dựng bộ kiểm thử có thể theo các cách tiếp cận khác nhau như: kiểm thử hộp đen (kiểm thử chức năng), kiểm thử hộp trắng (kiểm thử cấu trúc).
Kiểm thử chức năng tạo ra các ca kiểm thử thông qua đặc tả của phần mềm. Quá trình xây dựng ca kiểm thử chỉ sử dụng các giá trị đầu vào và các giá trị đầu 4 ra mong đợi. Cách tiếp cận này có thể xác định được chương trình hoạt động đúng với hành vi mong muốn trong đặc tả. Kiểm thử chức năng có ba phương pháp phổ biến gồm phương pháp phân tích giá trị biên, phân lớp tương đương và bảng quyết định [2].
Ngược lại, kiểm thử cấu trúc có cách tiếp cận khác để xây dựng ca kiểm thử. Kiểm thử cấu trúc tập trung vào mã nguồn của chương trình làm cơ sở để xác định ca kiểm thử. Cách tiếp cận này đảm bảo cấu trúc mã nguồn được kiểm tra, cho phép phát hiện các lỗi tiềm ẩn của chương trình. Hai phương pháp được sử dụng trong kiểm thử cấu trúc là kiểm thử dòng điều khiển (control flow testing) và kiểm thử dòng dữ liệu (data flow testing).
Độ đo kiểm thử xác định mức độ bao phủ chương trình của một bộ kiểm thử (tập các ca kiểm thử). Mức độ bao phủ của một bộ kiểm thử là tỷ lệ các thành phần được kiểm thử bởi các ca kiểm thử so với tổng thể chương trình. Các thành phần có thể là câu lệnh, các khối lệnh, nhánh điều khiển, điểm quyết định, biến, hàm, … Độ bao phủ càng lớn thì độ tin cậy của bộ kiểm thử càng cao. Mục tiêu của chiến lược kiểm thử là xây dựng bộ kiểm thử với số ca kiểm thử tối thiểu nhưng đạt được độ bao phủ tối đa.
Một CFG là một đồ thị có hướng gốm các đỉnh là các câu lệnh/khối lệnh và các cạnh là các dòng điều khiển giữa các câu lệnh/khối lệnh. Kiểm thử dòng điều 5 khiển gồm độ đo C1, độ đo C2 và độ đo C3. Độ đo C1: mỗi câu lệnh của chương trình được thực thi ít nhất một lần khi chạy bộ kiểm thử. Độ đo C2: mỗi nhánh điều kiện được thực hiện khi thực hiện kiểm thử.
Độ đo 3: các điều kiện con của điều kiện phức tạp được thực hiện ít nhất một lần cả hai nhanh đúng và sai. Hình 2-2 thể hiện đồ thị dòng điều khiển đạt độ đo C2 của hàm mid [3] (Hình 2-1). Đồ thị dòng điều khiển này có 5 điểm quyết định (đỉnh 3, 4, 6, 9, 11) và bốn đường thực thi. 1 2 3 T F 4 T 9 F 5 T 10 F 6 11 T 7 T 12 F F 13 Hình 2-2: Đồ thị dòng điều khiển tương ứng độ đo C2 của hàm mid Kiểm thử dòng dữ liệu dựa trên đồ thị dòng dữ liệu (DFG).
Đồ thị dòng dữ liệu là một CFG được mở rộng để bao gồm thông tin luồng dữ liệu. Kiểm thử dòng dữ liệu kiểm tra các hoạt động liên quan tới gán và sử dụng các biến trong chương trình. Một đồ thị dòng dữ liệu chỉ ra các định nghĩa và cách sử dụng của một biến trong chương trình. Một định nghĩa (definition) là câu lệnh thực hiện 6 gán giá trị cho một biến.
Một sử dụng (use) là một câu lệnh sử dụng một biến. Biến có thể dùng để tính toán tương ứng với c-use hoặc kiểm tra điều kiện tương ứng với p-use. Hình 2-3 thể hiện đồ thị dòng dữ liệu của hàm mid. 1 def={x, y, z} def={m} 2 c-use={z} 3 p-use={y,z} p-use={y,z} 4 p-use={x,y} p-use={x,y} 9 p-use={x,y} def={m} 5 c-use={y} def={m} p-use={x,y} 10 6 c-use={y} p-use={x,z} p-use={x,z} 11 def={m} 7 c-use={z} 12 p-use={x,z} p-use={x,z} 13 c-use={m} Hình 2-3: Đồ thị dòng dữ liệu của chương trình mid 2.3 Gỡ lỗi Lỗi chương trình là một hệ quả tất yếu trong quá trình viết chương trình.
Lỗi có thể xảy ra với nhiều lý do: lỗi đánh máy, hiểu sai yêu cầu phần mềm, gán sai giá trị cho biến, … Việc sử dụng từ lỗi (bug) bắt đầu từ thế kỷ 20, nó được sử dụng để chỉ ra những sai sót trong hệ thống kỹ thuật. Thuật ngữ gỡ lỗi (debug) sau đó được liên kết tới các hoạt động tìm kiếm và sửa các lỗi chương trình. Trong quá trình kiểm thử hay triển khai, một lỗi được phát hiện khi chương trình thể hiện một hành vi không mong muốn. Sau khi xảy ra lỗi, quá trình gỡ lỗi được thực hiện.
Nhà phát triển thực hiện kiểm tra mã nguồn trước tiên để tìm ra 7 nguyên nhân gây lỗi. Sau đó họ sửa lỗi này. Gỡ lỗi là quá trình giải quyết vấn đề yêu cầu hiểu biết và kỹ năng lập luận để hiểu về chương trình và xác định, sửa lỗi.4 Các nghiên cứu về định vị lỗi Từ những năm 1970 đã có rất nhiều nghiên cứu được thực hiện để đưa ra các phương pháp, kỹ thuật định vị lỗi phần mềm.