Tải bản đầy đủ (.pdf) (55 trang)

Mở rộng javapathfinder với z3 để sinh tự động dữ liệu kiếm thử chương trình java

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (1.3 MB, 55 trang )



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


PHÙNG THANH SƠN



MỞ RỘNG JAVAPATHFINDER VỚI Z3 ĐỂ SINH TỰ
ĐỘNG DỮ LIỆU KIỂM THỬ CHƯƠNG TRÌNH JAVA




LUẬN VĂN THẠC SĨ










Hà Nội - 2010




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



PHÙNG THANH SƠN



MỞ RỘNG JAVAPATHFINDER VỚI Z3 ĐỂ SINH TỰ
ĐỘNG DỮ LIỆU KIỂM THỬ CHƯƠNG TRÌNH JAVA

NGÀNH: CÔNG NGHỆ THÔNG TIN
CHUYÊN NGÀNH: CÔNG NGHỆ PHẦN MỀM
MÃ SỐ: 60 48 10


LUẬN VĂN THẠC SĨ


NGƢỜI HƢỚNG DẪN KHOA HỌC: TIẾN SĨ TRƢƠNG ANH HOÀNG





Hà Nội - 2010




MỤC LỤC

MỞ ĐẦU 1
CHƢƠNG 1-
JPF VÀ THỰC THI TƢỢNG TRƢNG 3
1.1 Giới thiệu về JPF 3
1.1.1 JPF có thể kiểm tra những chương trình gì? 4
1.1.2 Kiến trúc mức cao của JPF 5
1.1.3 Khả năng mở rộng của JPF 6
1.1.4 Một số mở rộng của JPF 8
1.2 Thực thi tượng trưng để sinh dữ liệu kiểm thử 9
1.2.1 Thực thi tượng trưng là gì? 9
1.2.2 Thực thi tượng trưng với JPF 10
1.2.3 Hướng dẫn thực thi tượng trưng với JPF 11
1.2.4 Hạn chế 19
CHƢƠNG 2-
MICROSOFT Z3 21
2.1 SMT là gì 21
2.2 Z3 là gì 21
2.3 Tại sao lại là Z3? 22
2.4 Kiến trúc của Z3 22
2.5 Định dạng đầu vào 24
2.6 Định dạng SMT-LIB 24
2.6.1 Các chức năng chính của SMT-LIB. 24
2.7 Các quan hệ, hàm, và hằng số. 25
2.7.1 Tất cả các hàm là tuyệt đối (total) [10] 25
2.7.2 Hàm không dịch (uninterpreted function) và hằng số 26
2.7.3 Hàm đệ quy 27
2.8 Số học 27
2.8.1 Số học tuyến tính thực 27

2.8.2 Số học tuyến tính nguyên 27
2.8.3 Trộn giữa số nguyên và số thực. 28
2.8.4 Số học phi tuyến tính 28
2.9 Kiểu dữ liệu 29
2.9.1 Kiểu bản ghi 29
2.9.2 Kiểu liệt kê (enumeration) 29
2.9.3 Kiểu dữ liệu đệ qui. 29
2.10 Ví dụ về Z3 30
2.11 Một vài ứng dụng của Z3 30
CHƢƠNG 3-
MỞ RỘNG JPF VỚI Z3 32
3.1 Nghiên cứu, đánh giá các giải pháp 32
3.2 Kiến trúc hệ thống 32
3.3 Chuyển đổi dữ liệu 33
3.4 Thiết kế và cài đặt 37
3.5 Kết quả và đánh giá 39
CÁC NGHIÊN CỨU LIÊN QUAN 44






KẾT LUẬN VÀ HƢỚNG PHÁT TRIỂN CỦA ĐỀ TÀI 45
TÀI LIỆU THAM KHẢO 47































DANH MỤC CÁC BẢNG BIỂU VÀ HÌNH VẼ

Hình 1-1: Mô hình hoạt động của JPF [6] 3
Hình 1-2: Sơ đồ trạng thái trong quá trình kiểm thử 5
Hình 1-3: Kiến trúc mức cao [6] 6







Hình 1-4: Mẫu Listener [6] 7
Hình 1-5: Ví dụ về thực thi tượng trưng 10
Hình 1-6: Đầu ra trên Eclipse cho MyClass1 [6] 13
Hình 2-1: Kiến trúc của Z3 [11] 23
Hình 3-1: Kiến trúc hệ thống 33
Hình 3-2: Sơ đồ mức gói 37
Hình 3-3: Biểu đồ trình tự 38
Hình 3-4: Sơ đồ lớp tổng quát 38
Hình 3-5: Kết quả với Choco - số học tuyến tính 40
Hình 3-6: Kết quả với Z3 - số học tuyến tính. 41
Hình 3-7: Kết quả với Choco – số học phi tuyến tính 42
Hình 3-8: Kết quả với Z3 – số học phi tuyến tính 43























BẢNG KÝ HIỆU CÁC TỪ VIẾT TẮT


Viết tắt
Thuật Ngữ
Giải thích






JPF
JavaPathFinder
Tên một phần mềm mã nguồn mở được sử
dụng trong luận văn
SMT
Satisfiability Modulo
Theories
Lý thuyết tính thỏa được

PC
Path Condition
Điều kiện đường đi trong 1 chương trình
phần mềm
SMT - LIB
Satisfiability Modulo
Theories Library
Một thư viện chuẩn được sử dụng cho lý
thuyết tính thỏa được
JVM
Java Virtual Machine
Máy ảo Java













1


MỞ ĐẦU


ĐẶT VẤN ĐỀ
Trong những năm gần đây, việc phát triển phần mềm ngày càng được
chuyên nghiệp hóa. Các phần mềm được phát triển ngày càng có quy mô lớn. Yêu
cầu đảm bảo chất lượng phần mềm là một trong những mục tiêu quan trong nhất,
đặc biệt trong một số lĩnh vực như y khoa, ngân hàng, hàng không… Việc kiểm
thử phần mềm một cách thủ công chỉ đảm bảo được phần nào chất lượng của phần
mềm. Vì vậy rất nhiều các tổ chức, công ty đã nghiên cứu và phát triển các lý
thuyết cũng như công cụ để kiểm chứng, kiểm thử phần mềm một cách tự động.
Kiểm thử tự động không chỉ giúp giảm chi phí phát triển phần mềm mà còn làm
tăng tính linh động của các phần mềm hiện đại. Người ta ước lượng rằng chi phí
cho các dự án phần mềm thất bại ở Mỹ là khoảng 60 tỉ USD hàng năm, và việc cải
tiến các phương thức để kiểm thử phần mềm có thể tiết kiệm được 1/3 chi phí trên
[12].
Xuất phát từ nhu cầu thực tế trên, tác giả đã nghiên cứu một số lý thuyết,
công cụ trong việc kiểm thử phần mềm. Một lý thuyết nền tảng rất quan trọng đó
là lý thuyết về tính thỏa được, viết tắt là SMT (Satisfiability Modulo Theories). Lý
thuyết về tính thỏa được đã được ứng dụng để giải quyết nhiều bài toán trong công
nghệ phần mềm như:
 Kiểm chứng chương trình
 Khám phá chương trình
 Mô hình hóa phần mềm
 Sinh các ca kiểm thử
Hiện nay Microsoft Z3 là một công cụ tìm lời giải cho SMT đang được áp
dụng trong nhiều dự án của Microsoft như: Pex, Spec#, SLAM/SDV, Yogi. Z3
được đánh giá là một trong những công cụ tìm lời giải mạnh nhất hiện nay. Tuy
nhiên Z3 chỉ được áp dụng cho các ngôn ngữ của Microsoft. Vì vậy tác giả đặt ra
vấn đề: Liệu có thể sử dụng Z3 để kiểm thử cho các chương trình viết bằng ngôn
ngữ khác như Java?
Trong quá trình nghiên cứu về kiểm chứng, kiểm thử phần mềm tác giả
cũng có tìm hiểu về JavaPathFinder (JPF). JPF là một dự án mã nguồn mở được

phát triển trên ngôn ngữ Java. Hiện nay có một mở rộng của JPF trong việc sinh tự
động dữ liệu đầu vào để kiểm thử chương trình. Tuy nhiên còn rất nhiều hạn chế,
vì vậy tác giả đã nghĩ đến việc làm sao để tích hợp được Z3 với JPF để có thể sinh
tự động dữ liệu kiểm thử chương trình. Nếu việc tích hợp thành công thì sẽ dẫn tới
2







việc giải quyết được lớp bài toán rộng hơn. Điều này là rất có ý nghĩa đối với thực
tế.

MỤC TIÊU CỦA ĐỀ TÀI
Mục tiêu của đề tài là nghiên cứu nắm bắt rõ về Z3 và JPF. Sau đó bước đầu
tích hợp thành công Z3 vào JPF (mở rộng JPF với Z3) để có thể sinh tự động dữ
liệu kiểm thử chương trình Java cho các bài toán mà hiện nay JPF không thể thực
hiện được. (ví dụ: sinh tự động dữ liệu cho số học phi tuyến tính).

CẤU TRÚC CỦA LUẬN VĂN
Luận văn bao gồm các phần sau:
Mở đầu: Giới thiệu về đề tài, tính cấp thiết cũng như mục tiêu của đề tài
Chƣơng 1: JPF và Thực thi tượng trưng
Nội dung: Trong chương này tác giả sẽ trình bày những hiểu biết về JPF và thực
thi tượng trưng, JPF sẽ thực thi tượng trưng để sinh dữ liệu kiểm thử ra sao. Ngoài
ra tác giả cũng giới thiệu một số mở rộng của JPF đang được phát triển hiện nay.
Chƣơng 2: Microsoft Z3
Nội dung: Giới thiệu về dự án của Microsoft đang được quan tâm hiện nay đó là

Z3. Trong phần này tác giả sẽ trình bày các chức năng chính và một số ứng dụng
của Z3, cũng như các API mà Z3 hỗ trợ để từ đó có thể tích hợp với JPF.
Chƣơng 3: Mở rộng JPF với Z3
Nội dung: Dựa trên kết quả của chương 1 và 2, trong phần này tác giả sẽ đánh giá
và đưa ra các giải pháp để mở rộng JPF với Z3. Sau khi đã có giải pháp sẽ tiến
hành thiết kế kiến trúc hệ thống, sau đó chi tiết hóa sang mức gói, mức lớp cuối
cùng là cài đặt và đánh giá kết quả.
Các nghiên cứu liên quan
Phần này tác giả sẽ trình bày một số nghiên cứu khác liên quan đến luận văn, đó là
các công cụ Pex, AgitarOne, Cute trong việc tự động kiểm thử chương trình.
Kết luận và hƣớng phát triển của luận văn
Trình bày kết quả sau khi nghiên cứu, những hạn chế và hướng phát triển tiếp
theo.





3







CHƢƠNG 1-
JPF VÀ THỰC THI TƢỢNG TRƢNG

Trong chương này sẽ bao gồm 2 phần chính. Phần 1 giới thiệu về JPF, một

dự án mã nguồn mở được viết bằng ngôn ngữ Java để kiểm chứng mô hình. Phần
2 giới thiệu một mở rộng của JPF đó là thực thi tượng trưng trong việc sinh tự
động dữ liệu để kiểm thử chương trình Java.
1.1 Giới thiệu về JPF
JPF là một bộ kiểm tra mô hình phần mềm trạng thái tường minh cho Java
[6]. Hiểu một cách cơ bản JPF là một máy ảo thực thi chương trình Java không
chỉ một lần (giống như các máy ảo thông thường), mà thực thi trong tất cả các
nhánh, các đường đi có thể. JPF sẽ kiểm tra các vi phạm thuộc tính như khóa chết
hoặc các ngoại lệ không thể bắt được xuyên xuốt các đường thực thi tiềm năng.
Hình 1-1 mô tả mô hình hoạt động của JPF.






Hình 1-1: Mô hình hoạt động của JPF [6]

Về lý thuyết điều này là rất khả thi, tuy nhiên với việc tăng kích cỡ của ứng
dụng, phần mềm kiểm chứng mô hình phải đối mặt với nhiều thách thức. JPF cũng
không là ngoại lệ. Câu trả lời của chúng ta đó là tăng sự linh hoạt của JPF để thích
4







nghi với một ứng dụng cụ thể. Chúng ta có thể coi JPF như là một Framework và

từ đó phát triển mở rộng để có thể giải quyết được bài toán cụ thể mà chúng ta
muốn.
1.1.1 JPF có thể kiểm tra những chƣơng trình gì?
JPF có thể kiểm tra tất cả các chương trình Java. JPF có thể tìm ra các khóa
chết hoặc ngoại lệ. Ngoài ra chúng ta có thể tự phát triển mở rộng để kiểm tra các
thuộc tính khác. Để hiểu rõ hơn về JPF chúng ta có thể xét ví dụ sau:
Tạo một lớp là Rand như bên dưới, sau đó chúng ta sẽ dùng JPF để kiểm tra
xem có lỗi không.

import java.util.Random;
public class Rand {
public static void main (String[] args) {
Random random = new Random(42); // (1)

int a = random.nextInt(2); // (2)
System.out.println("a=" + a);

int b = random.nextInt(3); // (3)
System.out.println(" b=" + b);

int c = a/(b+a -2); // (4)
System.out.println(" c=" + c);
}
}

Hoạt động của lớp trên đó là khởi tạo 2 biến a và b một cách ngẫu nhiên
trong các khoảng tương ứng là [0,2] và [0,3]. Sau đó có một biến c có giá trị được
xác định bằng công thức c = a/(b+a-2).
Nếu ta chạy chương trình Java này thông thường thì có thể thấy kết quả là: a
= 1, b =0, và c = -1. Như vậy chương trình là không có lỗi. Tuy nhiên nếu ta sử

dụng JPF để kiểm tra chương trình trên thì sẽ thấy như hình 1-2:

5








Hình 1-2: Sơ đồ trạng thái trong quá trình kiểm thử

Nhìn hình vẽ trên ta có thể thấy nếu chạy chương trình java bình thường thì
ta chỉ có thể nhận được 1 trong 6 kết quả trên, do vậy khả năng lớn là không phát
hiện được ra lỗi (đường bôi đậm là ví dụ). Tuy nhiên JPF sẽ tìm ra tất cả các
đường đi của chương trình sau đó kiểm tra chúng. Ta sẽ thấy có 2 trường hợp lỗi
gây ra bởi phép chia cho 0.
1.1.2 Kiến trúc mức cao của JPF
Hình 1-3 biểu diễn sơ đồ kiến trúc mức cao của JPF. JPF được thiết kế
thành 2 thành phần chính đó là: JVM, và Search.
JVM là một bộ sinh trạng thái cụ thể Java. Bằng việc thực hiện các chỉ thị
Java bytecode.
6











Hình 1-3: Kiến trúc mức cao [6]

Search chịu trách nhiệm lựa chọn trạng thái mà JVM nên xử lý, hoặc hướng
JVM sinh trạng thái tiếp theo, hoặc yêu cầu JVM quay trở lại một trạng thái trước
đó. Nói một các khác Search có thể coi như các driver cho các đối tượng JVM.
Search cũng cấu hình và đánh giá các đối tượng thuộc tính. Các cài đặt chính của
Search bao gồm tìm kiếm theo độ sâu (DFSearch) và HeuristicSearch. Một cài đặt
Search sẽ cung cấp một phương thức Search đơn giản bao gồm một vòng lặp chính
sẽ duyệt qua tất cả các không gian trạng thái liên quan cho đến khi nó duyệt xong
tất cả hoặc tìm ra một vi phạm thuộc tính (property violation).
1.1.3 Khả năng mở rộng của JPF
JPF có thể được coi như là một Framework mà tại đó bất kỳ nhà phát triển
nào đều có thể mở rộng để phục vụ cho một mục đích cụ thể. JPF cung cấp một cơ
7







chế mở rộng để cho phép thêm vào các chức năng mới mà không phải thay đổi
trực tiếp cài đặt của Search hoặc VM.


Hình 1-4: Mẫu Listener [6]


Yêu cầu về khả năng mở rộng có thể đạt được bằng cách sử dụng mẫu
Listerner trên hình 1-4. Các thể hiện sẽ tự đăng ký hoặc đăng ký với đối tượng
Search/VM, nhận thông báo khi một đối tượng (Subject) tương ứng thực thi một
hoạt động nhất định, và sau đó có thể tương tác với đối tượng để truy vấn các
thông tin bổ sung hoặc điều khiển hành vi của đối tượng.
Việc thay đổi các khía cạnh của đối tượng được ánh xạ vào các phương thức
Observer riêng biệt, các thể hiện của đối tượng sẽ được truyền đi như tham số. Đối
tượng Subject sẽ theo dõi các listener đã đăng ký theo Multicaster.
Có 3 mức khác nhau để có thể lấy được thông tin của đối tượng Subject
bằng cách cài đặt listener.
 Generic – listener cư trú bên ngoài các gói JPF và chỉ sử dụng các thông tin
đã được công khai (public) theo gov.nasa.jpf.Search / VM.
 Search-specific – listener cư trú bên ngoài gói JPF nhưng sẽ đưa các tham
số thông báo của đối tượng Subject vào các cài đặt cụ thể (ví dụ:
gov.nasa.jpf.search.heuristic.BFSHeuristic), và sử dụng các API của nó để
lấy các thông tin cài đặt cụ thể.
8







 Internal - listener cư trú trong các gói cài đặt Subject riêng biêt và truy cập
các thông tin riêng của gói (private) .
1.1.4 Một số mở rộng của JPF
Với kiến trúc mở rộng linh hoạt, hiện nay đã có một số mở rộng được phát
triển cho JPF.

ui - User Interface Model Checking
Đây là mở rộng cho việc kiểm tra mô hình một lớp đặc biệt của các ứng
dụng Java đó là các chương trình Swing và AWT. Mở rộng này được cài đăt như
một thư viện chuẩn được mô hình hóa MJI (MJI Là viết tắt của: Model Java
Interface) nhằm thay thế các chức năng của Swing và AWT để mà các ứng dụng
giao diện sử dụng chuẩn của Java có thể được kiểm thử với các đầu vào khác
nhau.
symbc - Symbolic Test Data Generation
Mở rộng này sử dụng BytecodeFactory để ghi đè lõi (core) JPF bytecodes
nhằm sinh ra các ca kiểm thử riêng biệt. Nói tóm lại nó hoạt động bằng cách sử
dụng các thuộc tính/ trường của JPF để thu thập các điều kiện đường đi PC, sau đó
được đưa các PC vào một hệ thống tìm lời giải theo định dạng của hệ thống đó để
đưa ra dữ liệu kiểm thử. Mở rộng này sẽ được trình bày chi tiết hơn ở phần 1.2.
cv - Compositional Verification Framework
Mở rộng này là một thuật toán học máy được sử dụng cho các lập luận thừa
nhận/ đảm bảo, nhằm mục đích phân chia hệ thống thành các thành phần con và
sau đó kiểm chứng từng thành phần đó một cách riêng rẽ. Mục đích chính của mở
rộng này là cải tiến khả năng của JPF, nó có thể được sử dụng để sinh ra môi
trường giả định cho kiểm chứng mô hình UML, để xác định các trình tự sự kiện
đúng.
numeric - Numeric Property Verification
Mở rộng này được sử dụng để kiểm chứng các thuộc tính của số học. Ban
đầu mở rộng được sử dụng như như một tập các lớp chỉ thị số học để phát hiện
tràn bộ nhớ, sau đó được mở rộng để kiểm chứng việc truyền giá trị không chính
xác, so sánh dấu phẩy động chính xác (floating point comparison).
statechart - UML State Chart Model Checking
Mục đích của mở rộng này là kiểm tra lược đồ chuyển trạng thái UML.
Trong mở rộng này mỗi một biểu đồ chuyển trạng thái sẽ được biểu diễn tương
ứng với một lớp Java (hoặc nhiều lớp). Sau đó quá trình kiểm tra sẽ là kiểm tra các
lớp Java đó.


9







1.2 Thực thi tượng trưng để sinh dữ liệu kiểm thử
1.2.1 Thực thi tƣợng trƣng là gì?
Kỹ thuật thực thi tượng trưng là kỹ thuật thực thi chương trình bằng cách sử
dụng các giá trị tượng trưng, không phải sử dụng các giá trị cụ thể [8, 16]. Để hiểu
rõ thực thi tượng trưng là gì, xét ví dụ chuyển đổi giữa 2 biến x và y:

Đổi giá trị giữa 2 biến Đường đi cụ thể


Việc thực thi cụ thể (sử dụng các giá trị cụ thể của 2 biến x, y) được mô tả
như trên, khi đó với mỗi giá trị cụ thể của x và y ta sẽ có đường đi cụ thể. Hình 1-
5 sẽ biểu diễn quá trình thực thi tượng trưng

10










Hình 1-5: Ví dụ về thực thi tƣợng trƣng

Ở ví dụ trên, nếu trong trường hợp thực thi tượng trưng, giá trị của x và y là
các giá trị tượng trưng X, Y chứ không phải là các giá trị cụ thể. Kết quả của quá
trình thực thi tượng trưng sẽ duyệt hết các đường đi có thể có của chương trình, và
cho ra điều kiện đường đi (PC). PC là một biểu thức boolean với các biến là các
giá trị tượng trưng đầu vào, đây chính là các ràng buộc để đầu vào phải thỏa mãn
theo một nhánh cụ thể [15, 16]. Ở ví dụ trên ta sẽ có 2 PC đó là: X > Y  Y ≤ X
và X > Y  Y > X.
Ưu điểm của phương pháp này là ta có thể thực thi tại bất kỳ điểm nào trong
chương trình và có thể trộn giữa đầu vào tượng trưng với đầu vào cụ thể. Phương
pháp này sẽ cho ta các điều kiện đường đi của chương trình, và với việc sử dụng
các công cụ tìm lời giải cho các điều kiện đường đi (coi mỗi điều kiện đường đi là
một biểu thức) sẽ sinh ra dữ liệu kiểm thử cho chương trình.
Tuy nhiên phương pháp này cũng có giới hạn đó là có thể bùng nổ các
đường đi trong việc thực thi tượng trưng.
1.2.2 Thực thi tƣợng trƣng với JPF
Thực thi tượng trưng là một mở rộng của JPF. Mở rộng này của JPF sẽ thực
thi tượng trưng các chương trình java. Một trong những ứng dụng chính của mở
rộng này, đó là tự động sinh dữ liệu kiểm thử bao phủ toàn bộ chương trình của mã
nguồn [6].
11








Mở rộng này phối hợp thực thi tượng trưng với kiểm chứng mô hình và các
ràng buộc giải quyết để sinh dữ liệu kiểm thử. Trong công cụ này, các chương
trình được thực thi trên đầu vào tượng trưng. Các giá trị của các biến được biểu
diễn như và các biểu thức số và ràng buộc, chúng được sinh từ việc phân tích cấu
trúc mã nguồn. Những ràng buộc sau đó được giải quyết để sinh ra các dữ liệu
kiểm thử để đảm bảo đạt được phần mã nguồn đó.
Tại thời điểm hiện tại JPF hỗ trợ các tham số nguyên và thực. Tuy nhiên vẫn
còn một số trường hợp cần giải quyết cho số thực.
Hiện tại mở rộng này chỉ hỗ trợ các ràng buộc tuyến tính (số học tuyến
tính), số học phi tuyến là chưa được hỗ trợ. Thông tin tượng trưng được truyền
theo các thuộc tính kết hợp với các biến và các toán tử. Thực thi tượng trưng có
thể bắt đầu từ bất kỳ điểm nào trong chương trình và nó có thể thực thi tượng
trưng riêng biệt với nhau.
1.2.3 Hƣớng dẫn thực thi tƣợng trƣng với JPF
Để thực hiện một phương thức (hàm) một cách tượng trưng, người sử dụng
cần đặc tả tham số phương thức nào là tượng trưng/cụ thể (concrete). Các tham
biến toàn cục cũng có thể được đặc tả để thực thi tượng trưng, theo các sự chú
thích đặc biệt. Đây là một ví dụ để chạy một thực thi tượng trưng. Ví dụ này cho
phép thực thi tượng trưng của phương thức test trong lớp chính (ExSymExe).
+vm.insn_factory.class=gov.nasa.jpf.symbc.SymbolicInstructionFactory
+jpf.listener=gov.nasa.jpf.symbc.SymbolicListener
+symbolic.method=test(sym#con)
+search.multiple_errors=true
+jpf.report.console.finished=
ExSymExe
1.2.3.1 Một ví dụ đơn giản
Sau đây là một ví dụ rất đơn giản của việc thực thi tượng trưng với JPF.
Chúng ta có thể sử dụng Eclipse hoặc thông qua giao diện dòng lệnh.
Giả sử ta có phương thức sau trong lớp bạn muốn sinh kiểm thử:


public class MyClass1 {
public int myMethod(int x, int y) {
int z = x + y;
if (z > 0) {
z = 1;
} else {
z = z - x;
12







}
z = 2 * z;
return z;
}
}

Chúng ta sẽ cần tạo một driver để gọi myMetho(int,int). Driver có thể là
một lớp khác hoặc phương thức main() của chính lớp này. Trong trường hợp này
ta sẽ viết driver trong phương thức main() của lớp MyClass1.
Trong ví dụ đơn giản này, driver chỉ cần gọi myMethod() với số và kiểu
tham số đúng sau đó in ra điều kiện đường đi (Path condition – PC). Điều lưu ý là
tham số chính xác không phải là vấn đề, vì chúng ta sẽ thực thi myMethod() một
cách tượng trưng, tất cả các giá trị cụ thể sẽ được thay thế bằng giá trị tượng trưng.
Chúng ta có thể xem các ca kiểm thử (test case) bằng cách in ra điều kiện

đường đi. Việc này thực hiện được bằng cách gọi phương thức:
gov.nasa.jpf.symbc.Debug.printPC(). Sau đây là mã nguồn đầy đủ:

public class MyClass1 {
public int myMethod(int x, int y) {
int z = x + y;
if (z > 0) {
z = 1;
} else {
z = z - x;
}
z = 2 * z;
return z;
}

// driver để kiểm thử
public static void main(String[] args) {
MyClass1 mc = new MyClass1();
int x = mc.myMethod(1, 2);
Debug.printPC("MyClass1.myMethod Path Condition: ");
}
}

Khi đó nếu chạy bằng Eclipse sẽ cho kết quả sau:

13










Hình 1-6: Đầu ra trên Eclipse cho MyClass1 [6]

Nhìn vào kết quả ở trên các PC sẽ chỉ ra các ca kiểm thử là
Ca kiểm thử 1: y = -9999999, x = 10000000
Ca kiểm thử 2: y = -10000000, x = 10000000
Ca kiểm thử 1 tương ứng với z > 0 của câu lệnh if của phương thức
myMethod. Ca kiểm thử 2 tương ứng với nhánh z ≤ 0.
1.2.3.2 Lọc các trƣờng hợp kiểm thử
Chúng ta thay đổi MyClass1 thành MyClass 2 như sau.

public class MyClass2 {
private int myMethod2(int x, int y) {
int z = x + y;
if (z > 0) {
z = 1;
}
if (x < 5) {
z = -z;
}
return z;
}
14








// The test driver
public static void main(String[] args) {
MyClass2 mc = new MyClass2();
int x = mc.myMethod2(1, 2);
Debug.printPC("MyClass2.myMethod2 Path Condition: ");
}
}
Chúng ta có thể chạy chương trình với các tham số cấu hình như sau:
+vm.insn_factory.class=gov.nasa.jpf.symbc.SymbolicInstructionFactory
+vm.classpath=.
+vm.storage.class=
+symbolic.method=myMethod2(sym#sym)
+search.multiple_errors=true
+jpf.report.console.finished=
MyClass2
Khi đó chúng ta sẽ nhận được 4 ca kiểm thử như sau:
 Ca kiểm thử 1: y = 10000000, x = -9999999
 Ca kiểm thử 2: y = -4, x = 5
 Ca kiểm thử 3: y = -10000000, x = -10000000
 Ca kiểm thử 4: y = -10000000, x = 5
Tuy nhiên giả sử chúng ta chỉ cần quan tâm trong các ca kiểm thử mà lệnh if
được thực hiện, khi đó chúng ta chỉ cần quan tâm đến ca kiểm thử 2 và 3. Chúng ta
có thể chỉ chạy JPF như ở trên và lọc chúng một cách thủ công. Tuy nhiên có một
cách khác tốt hơn đó là ta sử dụng Verify.ignoreIf() để bắt JPF quay trở lại khi một
câu lênh if được tìm ra hơn một lần, ví dụ ta có thể thông báo myMethod2() như
sau:

import gov.nasa.jpf.jvm.Verify;
import gov.nasa.jpf.symbc.Debug;

public class MyClass2 {
private int myMethod2(int x, int y) {
int jpfIfCounter = 0;
int z = x + y;
if (z > 0) {
jpfIfCounter++;
z = 1;
}
if (x < 5) {
jpfIfCounter++;
Verify.ignoreIf(jpfIfCounter > 1);
15







z = -z;
}
Verify.ignoreIf(jpfIfCounter == 0);
return z;
}

// The test driver
public static void main(String[] args) {

MyClass2 mc = new MyClass2();
int x = mc.myMethod2(1, 2);
Debug.printPC("\nMyClass2.myMethod2 Path Condition: ");
}
}

Các chú thích (annotations) được bôi đậm. Và bây giờ ta có thể chạy chương trình
và kết quả là sẽ chỉ nhận được 2 ca kiểm thử cần thiết:
 Test Case 1: y = -4, x = 5
 Test Case 2: y = -10000000, x = -10000000
1.2.3.3 Bổ sung tiền điều kiện
Giả sử rằng ta muốn giới hạn các ca kiểm thử được sinh ra, nhưng bây giờ
vấn đề là bạn muốn rằng phương thức của bạn sẽ chỉ được gọi với các tham số
trong một khoảng nào. Ví dụ trong MyClass1.myMethod() bạn tin tưởng rằng x và
y được giới hạn trong khoảng -100 <= x <= 100 và 1 <= y <= 3.
Để thực hiện điều này trong JPF là rất dễ ràng. Thậm chí ta không cần sửa
đổi phương thức myMethod(). Thay vào đó ta có thể sử dụng tiền điều kiện trong
khi cài đặt driver. Chúng ta sẽ cài đặt driver (gọi là MyDriver1) như sau

import gov.nasa.jpf.symbc.Debug;
public class MyDriver1 {

private static void imposePreconditions(int x, int y) {
MyClass1 mc = new MyClass1();
if (-100 <= x && x <= 100 && 1 <= y && y <= 3){
mc.myMethod(x, y);
Debug.printPC("MyClass1.myMethod Path Condition: ");
}
}


// The test driver
public static void main(String[] args) {
//Actual arguments are ignored when doing .
16







imposePreconditions(1,2);
}
}
Chúng ta cần các tiền điều kiện và bởi vì chúng ta không muốn chỉnh sửa
lớp, do đó ta sẽ tạo ra một phương thức gọi tượng trưng như sau:
MyDriver1.imposePreconditions(), không phải MyClass1.myMethod(). Chú ý
rằng tham số của phương thức imposePreconditions() là x và y, đây chính là tham
số của myMethod() cần phải được thực thi tượng trưng.
Kết quả sẽ cho ra các ca kiểm thử với các tham số nằm trong khoảng giới
hạn.
 Ca kiểm thử 1: y = 1, x = 0
 Ca kiểm thử 2: y = 1, x = -100

1.2.3.4 Các tham số thực
Các phương thức với tham số thực được xử lý một cách chính xác như các
tham số nguyên, mặc dù đầu ra là khác nhau. Đây là một ví dụ:

import gov.nasa.jpf.symbc.Debug;


public class MyClassFP {
public double myMethodFP(double x, double y) {
double z = x + y;
if (z > 0.0) {
z = 1.0;
} else {
z = z - x;
}
z = 2.0 * z;
return z;
}

// The test driver
public static void main(String[] args) {
MyClassFP mc = new MyClassFP();
double x = mc.myMethodFP(1.0, 22.0);
Debug.printPC("MyClassFP.myMethodFP Path Condition: ");
}
}

17







MyClassFP.myMethodFP() là giống phương thức MyClass1.myMethod()
ngoại trừ tham số có kiểu double.

Chạy Eclipse với các tham số cấu hình như sau:
+vm.insn_factory.class=gov.nasa.jpf.symbc.SymbolicInstructionFactory
+vm.classpath=.
+vm.storage.class=
+symbolic.method=myMethodFP(sym#sym)
+search.multiple_errors=true
+jpf.report.console.finished=
MyClassFP
Khi đó kêt quả ta sẽ được các ca kiểm thử sau:
 Ca kiểm thử 1: y = -4.99999991632194E-7, x = -50.00000025000001
 Ca kiểm thử 2: y = 0.0, x = 0.0
 Ca kiểm thử 3: y = -4.99999991632194E-7, x = 50.00000025000001
Ở đây ta thấy nhận được 3 ca kiểm thử tuy nhiên trong trường hợp kiểu là số
nguyên ta chỉ nhận được 2 ca kiểm thử. Đó là bởi vì JPF thực thi trên JVM
bytecode, không phải là Java source code. Khi z là biến double, đoạn mã
if (z > 0.0) {
z = 1.0;
} else {
z = z - x;
}
Sẽ được dịch như sau:
if (z > 0.0) {
z = 1.0;
} else if (z == 0.0 || z < 0.0) {
z = z - x;
}

Vì vậy ta sẽ nhận các ca kiểm thư tương ứng với các điều kiện của câu lệnh
if: z > 0.0, z == 0.0, và z < 0.0.
1.2.3.5 Các thuộc tính của đối tƣợng

Thay thế việc thực thi tượng trưng của các tham số phương thức, có thể
chúng ta cần thực hiện với các thuộc tính của đối tượng. Một cách để thực hiện
như vậy là tạo driver gán các tham số phương thức vào thuộc tính của đối tượng.

public class MyClassWithFields {
public int field1;
public int field2;
18








public void myMethod1() {
etc
}
}

public class MyDriverForFields {
private static void makeFieldsSymbolic(int x, int y) {
MyClassWithFields mc = new MyClassWithFields();

mc.field1 = x;
mc.field2 = y;
mc.myMethod1();
Debug.printPC("\nMyClassWithFields.myMethod1 Path
Condition: ");

}

// The test driver
public static void main(String[] args) {
makeFieldsSymbolic(1,2);
}
}

Chúng ta có thể chạy trong eclipse bằng cấu hình sau:
+vm.insn_factory.class=gov.nasa.jpf.symbc.SymbolicInstructionFactory
+vm.classpath=.
+vm.storage.class=
+symbolic.method=makeFieldsSymbolic(sym#sym)
+search.multiple_errors=true
+jpf.report.console.finished=
MyDriverForFields
Hoặc có một cách khác để thực hiện với thông báo @Symbolic():

public class MyClassWithFields {
@Symbolic("true")
public int field1;

@Symbolic("true")
public int field2;

public void myMethod1() {
int z = field1 + field2;
19








if (z > 0) {
z = 1;
} else {
z = z - field1;
}
z = field1 * z;
return z;
}
}

public class MyDriverForFields {
// The test driver
public static void main(String[] args) {
MyClassWithFields mc = new MyClassWithFields();

mc.myMethod1();
Debug.printPC("MyClassWithFields.myMethod1 PC:");
}
}
Sau khi chạy ta sẽ có kết quả sau:
 Ca kiểm thử 1: field2 = -9999999, field2 = 10000000
 Ca kiểm thử 2: field2 = -10000000, field2 = -10000000
1.2.4 Hạn chế
Hiện nay JPF đang sử dụng một số công cụ tìm lời giải của Java : Choco,
IAsolver, CVC3. Những công cụ này được sử dụng như các thư viện. JPF có một

số hạn chế sau: Không giải được đại số phi tuyến, các hàm không dịch
(uninterpreted function), phép chia cho 0. Ví dụ đơn giản là thực thi tượng trưng
cho lớp Java sau:

public class MulClass {
// The method you need tests for
public int myMethod(int x, int y) {
int z = x * y;
if (z > 10) {
z = 1;
} else {
z = z - x;
}
z = x * z;
return z;
}

×