Kiểm thử đột biến đã được nghiên cứu và phát triển từ những thập niên 70 của thế kỷ trước, kiểm thử đột biến được áp dụng trên các chương trình bằng cách áp dụng các toán tử đột biến, và trên các mô hình hệ thống bằng cách thay đổi các tính chất của mô hình. Trong các nghiên cứu của mình, giáo sư Bernhard K. Aichernig và các đồng của mình đã đưa ra quy trình kiểm thử đột biến hướng mô hình.
Quy trình kiểm thử đột biến hướng mô hình được trình bày trong tài liệu [1]. Người dùng tạo một mô hình để kiểm thử từ các yêu cầu của hệ thống; mô hình này được biến đổi thông qua bộ công cụ sẽ tạo thành một mô hình mới đã biến đổi, từ mô hình gốc và mô hình biến đổi sinh ra các ca kiểm thử, các ca kiểm thử này đều có cùng mức độ trừu tượng hoá như là mô hình và mô hình biến đổi, ca kiểm thử này có cả các giá trị đầu vào và giá trị đầu ra mong muốn. Một Trình kiểm thử (Test Driver) sẽ chỉ ra một mối liên quan giữa ca kiểm thử và các trạng thái hoạt động trong chương trình,
thực hiện ca kiểm thử và đưa ra kết quả đánh giá là ca kiểm thử này có thoả mãn chương trình hay không (Pass hay fail).
Hình 5.1. Kiểm thử đột biến hướng mô hình
Nếu chương trình thoả mãn được mô hình thì kết quả đánh giá ca kiểm thử là thoả mãn (Pass). Trong trường hợp chương trình không thoả mãn mô hình đã cho, tức là đã có lỗi xảy ra trong chương trình, thì kết quả trả về là không thoả mãn (Fail). Tuy nhiên, có một số trường hợp chương trình chúng ta bị không bắt được lỗi và kết quả trả về là thoả mãn. Các trường hợp này chúng ta có thể cải tiến mô hình để tăng chất lượng của ca kiểm thử.
Trong kiểm thử đột biến hướng mô hình, chúng ta biến đổi các mô hình một cách tự động và sau đó sinh ra ca kiểm thử trừu tượng để bao phủ được trường hợp đột biến này. Ở đây, giáo sư Bernhard K. Aichernig muốn nhấn mạnh rằng: nếu một lỗi tồn tại và lỗi này được thể hiện bằng một đột biến được sinh ra, thì sau đó ca kiểm thử sẽ phải tìm ra lỗi. Điều này đã được thể hiện rõ ràng trên hình 5.1 thông qua hai mũi tên thoả mãn: nếu chương trình không thoả mãn mô hình gốc, nhưng thoả mãn mô hình biến đổi thì việc thực thi các ca kiểm thử được sinh ra sẽ trả lại kết quả là không thoả mãn (fail). Ở đây, chúng ta giả sử là việc cài đặt này là đơn định, với các cài đặt không đơn định của chương trình thì chúng ta phải lặp lại việc kiểm thử một vài lần.
Kiểm thử đột biến thường được áp dụng trên các chương trình, các mã nguồn với ngôn ngữ Fortran, Ada, C, Java, SQL, C#, AOP và các đột biến trong đặc tả bằng Calculus Specification, Algebraic Specification, Network Protocol, FSM, State Chart, Web Service, Security Policy [12]. Theo [12], kiểm thử đột biến được hỗ trợ bởi các
công cụ tự động tạo ra các đột biến như là công cụ Proteum 1.4, MILU tạo ra các đột biến trong chương trình viết bằng ngôn ngữ C, công cụ MuJava tạo ra các đột biến trong chương trình viết bằng Java, SQLMutation tạo tự động các đột biến trong ngôn ngữ SQL và một số công cụ khác. Kiểm thử đột biến hướng mô hình thường được thực hiện khi hệ thống đã được mô hình hoá, bằng các ngôn ngữ mô hình hoá như UML, và biến đổi mô hình UML để tạo ra các mô hình biến thể. Việc mô hình hoá này được thực hiện trong giai đoạn thiết kế hệ thống. Cả hai cách trên đều áp dụng ở giai đoạn thiết kế hoặc xây dựng mã nguồn của hệ thống.
Luận văn đề nghị một cách kết hợp kiểm chứng mô hình và kỹ thuật kiểm thử đột biến đối với chương trình từ giai đoạn đặc tả hệ thống, thiết kế hệ thống ở mức cao, và mô hình hệ thống ở đây được xây dựng bằng ngôn ngữ Promela là ngôn ngữ mô hình hoá ở mức cao. Mô hình sẽ được kiểm tra trong giai đoạn sớm của quy trình phát triển phần mềm, làm tăng chất lượng của đặc tả chương trình, giảm giá thành và giảm ảnh hưởng của việc sửa lỗi đến các giai đoạn sau trong quy trình phát triển phần mềm. Các phản ví dụ được sinh ra sẽ được xem xét như các ca kiểm thử cho hệ thống sau khi đã có mã nguồn. Mô hình kết hợp được đề xuất trong hình 5.2.
Hình 5.2. Cách tiếp cận kiểm chứng mô hình kết hợp với kiểm thử đột biến
Với cách tiếp cận truyền thống thì ta thấy rằng mô hình có thể được kiểm chứng là thỏa mãn các đặc tả của hệ thống, nếu không thỏa mãn thì sẽ phải sinh ra các phản ví dụ. Số lượng các phản ví dụ là hữu hạn, nên việc kiểm chứng là chưa thật sự hiệu quả.
Với cách tiếp cận mới, giả sử là hệ thống đã thỏa mãn đặc tả của chương trình và không sinh ra các phản ví dụ để kiểm chứng lại. Tác giả dùng phương pháp kiểm thử đột biến, để thay đổi đặc tả hoặc thay đổi mô hình hệ thống theo hai cách sau:
Biến đổi các yêu cầu hệ thống, và giữ nguyên mô hình hệ thống: các yêu cầu được hình thức hóa và đặc tả lại, cùng với mô hình hệ thống đã cho. Ta dùng công cụ kiểm chứng mô hình để kiểm tra lại mô hình xem có thỏa mãn đặc tả đã bị biến đổi này hay không? Nếu kết quả là thỏa mãn, vậy là mô hình hệ thống và đặc tả đều bị sai, nên mới dẫn đến kết quả đúng. Nếu kết quả trả về là vi phạm và sinh ra các phản ví dụ thì các phản ví dụ này chính là các ca kiểm thử của hệ thống.
Giữ nguyên các yêu cầu của hệ thống, và biến đổi mô hình hệ thống: hệ thống sẽ được mô hình hóa lại thành mô hình khác không còn đúng với đặc tả ban đầu của hệ thống nữa. Ta dùng công cụ kiểm chứng mô hình để kiểm tra lại mô hình mới này xem có thỏa mãn các đặc tả ban đầu của hệ thống không? Nếu kết quả là thỏa mãn, vậy là mô hình và đặc tả đều đang bị sai, kết quả kiểm tra là không chính xác, cần kiểm tra và mô hình hóa lại. Nếu kết quả trả về là vi phạm và sinh ra các phản ví dụ thì các phản ví dụ này có thể được xem như các ca kiểm thử của hệ thống.
Việc biến đổi yêu cầu đặc tả của hệ thống và mô hình của hệ thống được thực hiện bằng việc dùng các phép đột biến trên ngôn ngữ Promela hoặc là biến đổi trên mô hình hành vi của hệ thống/ trên máy hữu hạn trạng thái của hệ thống.
Các phép biến đổi trên ngôn ngữ Promela: Sử dụng các đột biến trong biến Sử dụng các đột biến trong toán tử Sử dụng đột biến trong hằng
Sử dụng đột biến trong các câu lệnh
Trong các kiểu đột biến trên, ta đều gặp trường hợp tạo ra đột biến bằng cách thêm, thay thế hoặc xoá bỏ các biến/ toán tử/ hằng/ câu lệnh.
Đột biến trong biến là cách thay đổi các biến này bằng các biến khác để làm thay đổi chương trình. Ví dụ, chương trình gốc:
proctype P() {
do ::sum = a + b od } Chương trình được biến đổi thành:
proctype P() {
do ::sum = a + a od }
Đột biến trong toán tử: Dựa vào bộ các toán tử đột biến được mô tả trên trang [14] và bảng 4.2 mô tả các toán tử của ngôn ngữ Promela, ta đưa ra được bảng các toán tử đột biến trong ngôn ngữ Promela như trong bảng 5.1.
Bảng 5.1. Các toán tử đột biến trong ngôn ngữ Promela
Tên toán tử Toán tử ban đầu (Original operators)
Toán tử đột biến (Mutant operators)
Toán tử phủ định ! (biểu thức) (biểu thức)
(biểu thức) ! (biểu thức)
Các toán tử tăng, giảm ++ --
-- ++ Các toán tử số học - + + - * / / * % * Các toán tử dịch bit << >> >> << Các toán tử quan hệ số học <= > >= < < >= > <= < <= <= < > >= >= > Các toán tử bằng == != != ==
Các toán tử làm việc với từng bit
& |
| &
^ &
Các toán tử logic && ||
|| &&
Ví dụ về việc sử dụng đột biến trong toán tử là: int sum = a + b;
Sau khi dùng đột biến với toán tử số học ta có: int sum = a – b;
Đột biến trong hằng và trong các câu lệnh cũng tương tự như đột biến trong biến, ngĩa là ta cũng thay đổi hằng, giá trị của hằng và các câu lệnh để làm sai khác chương
trình và tạo ra các đột biến. Ví dụ như chèn các câu lệnh assert, thay đổi điều kiện trong điều kiện if, do, … và các phép biến đổi khác để làm chương trình sai khác.
Trường hợp mà chương trình gốc/ mô hình gốc và chương trình đột biến/ mô hình đột biến hầu như không có sự khác nhau, các ca kiểm thử không phát hiện ra các lỗi được gieo vào chương trình thì ta gọi nó là các đột biến tương đương (equivalent mutants). Theo giáo sư Grün và các đồng sự nghiên cứu [5] thì có bốn nguyên nhân sinh ra các đột biến tương đương: (1) các đột biến nằm trong phần code không được gọi đến (dead code); (2) các đột biến ngăn chặn việc tăng tốc độ xử lý của chương trình, nhưng không làm thay đổi cấu trúc của chương trình; (3) các đột biến tương đương chỉ làm thay đổi trạng thái riêng của một lớp hoặc trả về giá trị của các lời gọi hàm riêng mà không ảnh hưởng đến hành vi chung của cả chương trình; (4) đột biến không được thực hiện (trigger) – các đột biến này có thể làm cho chương trình bị lỗi, nhưng điều kiện để xảy ra các đột biến này là hệ thống đã bị lỗi ở chỗ khác, nên vì thế nó không thể xảy ra được nữa. Các đột biến tương đương chiếm một phần lớn trong tổng số các đột biến được sinh ra, các kỹ thuật để phát hiện đột biến tương đương vẫn đang được nghiên cứu với mục tiêu làm giảm số đột biến tương đương và tăng chất lượng cho chương trình.
Số lượng các đột biến của mô hình, và đặc tả hệ thống là rất lớn, việc kiểm chứng với tất cả các đột biến là một việc khó thực hiện. Vì vậy, ta cần áp dụng các kỹ thuật để làm giảm số lượng các đột biến cần kiểm thử, nhưng vẫn đảm bảo được tỷ lệ đột biến (mutation score) ở mức độ chấp nhận được. Một kỹ thuật có thể được xem xét và cân nhắc đến là phương pháp phân hoạch tương đương. Ở đây, ta sẽ đưa các trường hợp đột biến có cùng miền giá trị đầu vào, có cùng miền giá trị đầu ra thành một vùng tương đương, và sau đó ta tiến hành việc kiểm thử trên các vùng tương đương đó. Việc kiểm thử trên các vùng tương đương được chứng minh là mang lại hiệu quả cao, tiết kiệm thời gian, công sức thực hiện.