ĐẠI HỌC QUỐC GIA HÀ NỘI
TRƯỜNG ĐẠI HỌC CÔNG NGHỆ
Trần Bình Dương
SINH CA KIỂM THỬ THAM SỐ HÓA CHO
CHƯƠNG TRÌNH JAVA
KHÓA LUẬN TỐT NGHIỆP ĐẠI HỌC HỆ CHÍNH QUY
Ngành: Công Nghệ Thông Tin
HÀ NỘI - 2009
ĐẠI HỌC QUỐC GIA HÀ NỘI
TRƯỜNG ĐẠI HỌC CÔNG NGHỆ
Trần Bình Dương
SINH CA KIỂM THỬ THAM SỐ HÓA CHO
CHƯƠNG TRÌNH JAVA
KHÓA LUẬN TỐT NGHIỆP ĐẠI HỌC HỆ CHÍNH QUY
Ngành: Công Nghệ Thông Tin
Cán bộ hướng dẫn: TS. Trương Anh Hoàng
LỜI CẢM ƠN
Lời đầu tiên em xin được gửi lời cảm ơn chân thành tới TS. Trương Anh Hoàng,
người thầy đáng kính đã tận tình hướng dẫn em trong suốt thời gian thực hiện khóa
luận này.
Em cũng muốn bày tỏ lòng biết ơn đến các thầy cô giáo trường Đại học Công
Nghệ - Đại học Quốc Gia Hà nội, đặc biệt là các thầy cô trong khoa công nghệ thông
tin đã tận tình dạy dỗ và tạo mọi điều kiện học tập thuận lợi cho em trong suốt bốn
năm học qua.
Cuối cùng, em xin gửi lời cảm ơn tới gia đình đình em. Nếu không có tình yêu,
sự ủng hộ và động viên từ gia đình thì em sẽ không thể hoàn thành khoá luận và có
được những kết quả như ngày hôm nay.
Hà nội, 05/2009
Sinh viên
Trần Bình Dương
i
TÓM TẮT NỘI DUNG
Kiểm thử đơn vị tham số hóa còn đang là một khái niệm mới mẻ đối với nhiều
nhà phát triển phần mềm. Kiểm thử đơn vị tham số hóa đang dần đóng một vài trò hết
sức quan trọng trong phát triển phần mềm. Khóa luận này ra đời chính là để nghiên
cứu về phương pháp kiểm thử mới này và ứng dụng nó cho mục đích kiểm thử các
chương trình Java. Nội dung khóa luận tập trung vào việc áp dụng khả năng của một
nền kiểm chứng Java bytecode mã nguồn mở rất hiệu quả và phổ biến hiện nay là Java
PathFinder để xây dựng một hệ thống hỗ trợ kiểm thử đơn vị tham số hóa cho mục
đích kiểm thử các chương trình Java. Kết quả của khóa luận là đã xây dựng được một
hệ thống để thực thi các ca kiểm thử đơn vị tham số hóa viết cho các chương trình Java
đơn giản. Bên cạnh đó, khóa luận cũng đã trình bày một cách sâu sắc về kiểm thử đơn
vị tham số hóa và những kỹ thuật phức tạp đằng sau phương pháp kiểm thử mới này
cũng như một số nghiên cứu liên quan. Qua đó khóa luận kết thúc bằng việc phác thảo
một số hướng có thể phát triển tiếp để hệ thống này xử lý được các kiểu dữ liệu phức
tạp hơn.
ii
MỤC LỤC
LỜI CẢM ƠN .....................................................................................................................i
TÓM TẮT NỘI DUNG.....................................................................................................ii
MỤC LỤC..........................................................................................................................iii
CÁC KÝ HIỆU VIẾT TẮT .............................................................................................v
DANH MỤC HÌNH VẼ ...................................................................................................vi
Chương 1: Kiểm thử đơn vị tham số hóa ......................................................................3
1.1. Kiểm thử phần mềm.................................................................................................3
1.2. Kiểm thử đơn vị........................................................................................................4
1.3. Kiểm thử đơn vị tham số hóa...................................................................................6
1.3.1. Khái niệm...........................................................................................................6
1.3.2. Mối quan hệ giữa UT và PUT ..........................................................................7
1.3.3. Kiểm thử đơn vị tham số hóa với Pex..............................................................8
1.3.4. Các mẫu kiểm thử tham số hóa ........................................................................9
1.3.5. Lựa chọn đầu vào kiểm thử với Pex...............................................................10
Chương 2: Sinh dữ liệu kiểm thử tự động cho PUT ..................................................12
2.1. Thực thi tượng trưng ..............................................................................................13
2.1.1. Những khái niệm cơ bản.................................................................................13
2.1.2. Thực thi tượng trưng tĩnh................................................................................14
2.1.3. Thực thi tượng trưng động..............................................................................17
2.2. Xây dựng ràng buộc ...............................................................................................23
2.2.1. Lưu trữ giá trị tượng trưng..............................................................................24
2.2.2. SE với các kiểu dữ liệu nguyên thủy..............................................................25
2.2.3. SE với đối tượng..............................................................................................28
2.2.4. SE với các lời gọi phương thức ......................................................................30
2.3. Sinh dữ liệu kiểm thử cho PUT .............................................................................31
Chương 3: Sinh ca kiểm thử tham số hóa với JPF.....................................................36
3.1. Kiến trúc của JPF ...................................................................................................36
3.2. Symbolic JPF..........................................................................................................40
3.2.1. Bộ tạo chỉ thị....................................................................................................40
3.2.2. Các thuộc tính..................................................................................................41
3.2.3. Xử lý các điều kiện rẽ nhánh ..........................................................................42
3.2.4. Ví dụ.................................................................................................................43
3.2.5. Kết hợp thực thi cụ thể và thực thi tượng trưng ............................................47
iii
3.3. Sinh PUT với Symbolic JPF..................................................................................48
3.4. Mở rộng Symbolic JPF ..........................................................................................53
3.4.1. Các phương pháp cũ........................................................................................53
3.4.2. Hướng mở rộng ...............................................................................................54
KẾT LUẬN.......................................................................................................................58
TÀI LIỆU THAM KHẢO ................................................................................................1
iv
CÁC KÝ HIỆU VIẾT TẮT
CFG
DSE
JPF
PC
PUT
SE
SET
TIS
UT
Control Flow Graph
Dynamic Symbolic Execution
Java PathFinder
Path Condition
Parameterized Unit Test
Symbolic Execution
Symbolic Execution Tree
Test Input Selector
Unit Test
v
DANH MỤC HÌNH VẼ
Hình 1: Mối quan hệ giữa UT và PUT...............................................................................8
Hình 2 : Cây thực thi tượng trưng ....................................................................................16
Hình 3: Cây thực thi tượng trưng được quản lý riêng.....................................................22
Hình 4 : Hệ thống kiểm thử tổng quát..............................................................................24
Hình 5: Gán giá trị tượng trưng cho tham số đầu vào.....................................................26
Hình 6: Thực thi tượng trưng với câu lệnh gán ...............................................................27
Hình 7: Thực thi tượng trưng với câu lệnh rẽ nhánh.......................................................28
Hình 8: Khởi tạo đối tượng làm đầu vào cho chương trình............................................29
Hình 9. Sinh ra các ràng buộc liên quan tới đối tượng ...................................................30
Hình 10: Thuật toán sinh dữ liễu kiểm thử ......................................................................32
Hình 11: Các cây thực thi cục bộ tương ứng với hàm abs và TestAbs ..........................34
Hình 12: Kiến trúc JPF......................................................................................................37
Hình 13: Bộ sinh lựa chọn trong JPF ...............................................................................39
Hình 14. Bộ tạo chỉ thị trong JPF .....................................................................................40
Hình 15. Trạng thái chương trình thực thi trong Symbolic JPF .....................................41
Hình 16: Bùng nổ việc thực thi tượng trưng trong Symbolic JPF..................................48
Hình 17: Kiến trúc hệ thống cài đặt .................................................................................49
Hình 18. Một ví dụ với hệ thống cài đặt ..........................................................................52
vi
MỞ ĐẦU
Trong nền kinh tế hiện nay, ngành công nghiệp phần mềm giữ vai trò hết sức
quan trọng. Với một số nước có nền công nghệ thông tin phát triển thì ngành công
nghiệp phần mềm có khả năng chi phối cả nền kinh tế. Tuy nhiên để đảm bảo chất
lượng cho các phần mềm là một thách thức không nhỏ trong ngành công nghiệp phần
mềm. Việc phát hiện và khắc phục các lỗi cho các phần mềm là một công việc đòi hỏi
nhiều nỗ lực và chi phí trong phát triển phần mềm. Với những lĩnh vực ứng dụng ngày
càng mở rộng của phần mềm hiện nay thì chất lượng phần mềm càng được quan tâm
hàng đầu. Trong kỹ nghệ phần mềm thì kiểm thử chính là phương pháp dùng để phát
hiện các lỗi của phần mềm. Trong đó kiểm thử đơn vị là giai đoạn đầu tiên trong quy
trình kiểm thử. Kiểm thử đơn vị là một công việc bắt buộc trong phát triển phần mềm.
Theo nghiên cứu của Micorosoft thì có tới 79% các nhà phát triển phần mềm phải viết
các ca kiểm thử đơn vị để thực hiện việc kiểm thử phần mềm mức đơn vị. Rõ ràng
kiểm thử đơn vị là một công việc nặng nhọc làm mất nhiều thời gian và chi phí trong
phát triển phần mềm. Do đó có một phương pháp kiểm thử đơn vị mới đã ra đời giúp
cải thiện phương pháp kiểm thử đơn vị truyền thống đó là kiểm thử đơn vị tham số
hóa. Với kiểm thử đơn vị tham số hóa công sức giành cho việc kiểm thử phần mềm
mức đơn vị đã được giảm đi đáng kể. Kiểm thử đơn vị tham số hóa giúp việc phát hiện
các lỗi của phần mềm đạt hiệu quả cao hơn do đó nâng cao chất lượng của phần mềm.
Kiểm thử đơn vị tham số hóa còn là một phương pháp kiểm thử đơn vị còn rất mới và
nó mới chỉ được áp dụng trong môi trường .NET. Vì vậy việc nghiên cứu về kiểm thử
đơn vị tham số hóa và ứng dụng nó là một nhu cầu cấp bách. Và khóa luận này ra đời
chính là vì mục đích này.
Nội dụng chính của khóa luận gồm 3 chương:
Chương 1: Trình bày tổng quan về kiểm thử và làm rõ bản chất của kiểm thử đơn
vị tham số hóa thông qua công cụ Pex của Microsoft.
Chương 2: Nghiên cứu về phương pháp sinh dữ liệu làm đầu vào kiểm thử cho
các ca kiểm thử đơn vị tham số hóa. Trong chương này ta cũng sẽ trình bày về một hệ
thống kiểm thử tổng quát nhất dùng để thực thi các ca kiểm thử đơn vị tham số hóa
viết cho ngôn ngữ Java.
1
Chương 3: Trong chương này ta sẽ nghiên cứu về một nền (framework) kiểm
chứng Java bytecode mã nguồn mở rất phổ biến hiện nay đó là Java PathFinder và áp
dụng khả năng của nó để xây dựng một nền thực thi các ca kiểm thử tham số hóa viết
cho những chương trình Java đơn giản. Đồng thời ta cũng đề xuất giải pháp để có thể
mở rộng Java PathFinder cho mục đích hoàn thiện nền kiểm thử mà ta đã xây dựng.
2
Chương 1: Kiểm thử đơn vị tham số hóa
1.1. Kiểm thử phần mềm
Để đảm bảo một hệ thống phần mềm hoặc các thành phần của phần mềm làm
việc như mong muốn là một thách thức lớn trong ngành công nghiệp phần mềm. Các
phần mềm lỗi gây ra những tổn thất về kinh tế cũng như những hậu quả nghiêm trọng
khác tùy thuộc vào lĩnh vực mà phần mềm được sử dụng. Do đó cần phải phát hiện và
khắc phục các lỗi của phần mềm trước khi đem chúng vào sử dụng. Có các phương
pháp khác nhau để phát hiện lỗi của phần mềm bao gồm kiểm tra mô hình (model
checking)[4], các kỹ thuật phân tích tĩnh (static analysis)[24] và kiểm thử (software
testing)[1].
Các kỹ thuật phân tích chương trình tĩnh thường đưa ra nhiều cảnh báo
(warnings) không tương ứng với các lỗi thực sự. Các kỹ thuật kiểm thử phát hiện ra
các lỗi thực sự nhưng thường không phát hiện ra được tất cả các lỗi do chỉ phân tích
một số sự thực thi trong chương trình. Trong kiểm tra mô hình, một mô hình của hệ
thống được tạo ra để hỗ trợ phân tích mọi sự thực thi có thể trong mô hình. Kiểm tra
mô hình có thể kiểm chứng được rằng mô hình của hệ thống hoạt động chính xác trong
tất cả trường hợp có thể. Kiểm tra mô hình phân tích hết mọi khía cạnh thực thi của
chương trình và chỉ ra những sự vi phạm nhưng không chứng minh được chương trình
sẽ được thực thi chính xác mà không có lỗi. Hạn chế của kiểm tra mô hình đó là không
gian trạng thái của mô hình thường quá lớn do đó việc thám hiểm tất cả các trạng thái
không phải lúc nào cũng thực hiện được.
Kiểm thử chính là kỹ thuật được sử dụng phổ biến nhất để phát hiện và khắc
phục các lỗi của phần mềm nhằm đảm bảo chất lượng của phần mềm. Chi phí giành
cho việc kiểm thử chiếm khoảng 50% tổng chi phí trong phát triển phần mềm. Kiểm
thử là một tiến trình quan trọng trong kỹ nghệ phần mềm. Kiểm thử đơn vị chính là
bước đầu tiên trong quy trình kiểm thử đó. Có các kỹ thuật kiểm thử khác nhau được
sử dụng như kiểm thử hộp trắng (white-box testing), kiểm thử hộp đen (black-box
testing), kiểm thử hộp xám (gray-box testing). Các kỹ thuật kiểm thử đó được dựa trên
2 loại kiểm thử đó là kiểm thử chức năng (funcional testing) và kiểm thử cấu trúc
(structured testing). Kiểm thử chức năng là loại kiểm thử dựa trên đặc tả chức năng
của hệ thống, nó phát hiện các sai sót về chức năng mà không quan tâm tới cài đặt.
3
Kiểm thử cấu trúc là loại kiểm thử có nghiên cứu mã nguồn bằng việc phân tích thứ tự
thực hiện các lệnh.
1.2. Kiểm thử đơn vị
Kiểm thử đơn vị là một cộng việc quan trọng trong kỹ nghệ phần mềm. Kiểm thử
đơn vị thường được áp dụng để kiểm tra việc cài đặt của các lớp hoặc phương thức. Để
thực hiện việc kiểm thử đơn vị, các lớp kiểm thử được tạo ra. Các lớp kiểm thử này
gồm các phương thức kiểm thử. Các phương thức kiểm thử là các phương thức không
tham số có kiểu trả về là void chứa trong các lớp kiểm thử để kiểm tra các khía cạnh
cài đặt khác nhau của chương trình. Mỗi phương thức kiểm thử trong các lớp kiểm thử
biểu thị cho một ca kiểm thử đơn vị (UT).
Có thể chia một phương thức kiểm thử ra làm 3 phần: Các giá trị đầu vào, dãy
các lời gọi phương thức, và sự xác nhận (assertions). Kiểm thử thất bại nếu bất cứ sự
xác nhận nào bị vị phạm hoặc có một ngoại lệ (exception) xảy ra.
Ví dụ 1.1: Ta xét một phương thức kiểm thử được viết trong nền kiểm thử
VSUnit.
public void TestArrayList() {
// exemplary data
int capacity = 1;
object element = null;
// method sequence
ArrayList list = new ArrayList(capacity);
list.Add(element);
// assertions
Assert.IsTrue(list[0] == element);
}
Phương thức kiểm thử TestArrayList bắt đầu bằng việc gán giá trị 1 cho biến
capacity và giá trị null cho biến element như là các giá trị đầu vào kiểm thử. Sau đó nó
thực hiện một dãy các lời gọi phương thức, trước tiên là khởi tạo một đối tượng
ArrayList với kích cỡ là capacity không chứa phần tử nào. ArrayList là một mảng
động với kích cỡ có thể thay đổi. Tiếp theo nó chèn một đối tượng là element vào
mảng. Và cuối cùng là xác nhận xem phần tử đầu tiên của mảng có bằng đối tượng
vừa được chèn vào hay không.
Việc cài đặt nhiều phương thức kiểm thử không đảm bảo rằng sẽ kiểm tra được
hết mọi khía cạnh thực thi của chương trình. Với các chương trình có nhiều đường đi
thực thi khác nhau thì việc thiếu xót các UT để kiểm tra một vài đường đi thực thi
trong chương trình là điều thường xuyên xảy ra. Khi người lập trình thay đổi mã cài
đặt của chương trình được kiểm thử thì nếu như các phương thức kiểm thử không được
4
cập nhật theo thì sẽ dẫn đến việc nhiều đường đi thực thi của chương trình sẽ không
được kiểm thử.
Các nền kiểm thử hỗ trợ viết các UT theo các cách khác nhau. Tuy nhiên, đa
phần các nền kiểm thử đều cung cấp những dịch vụ (service) như sau:
+ Cung cấp thuộc tính để chỉ định các phương thức như là các UT.
+ Phát hiện và thực thi tự động các UT
+ Một runner với khả năng báo cáo (reporting). Một runner có thể là ứng dụng
dòng lệnh (console) hoặc là giao diện tích hợp.
Như trong nền kiểm VSUnit[29] cho môi trường .NET. Ta sử dụng thuộc tính
[TestClass] để chỉ định một lớp là lớp kiểm thử, và [TestMethod] để chỉ định một
phương thức như là một phương thức kiểm thử. Ngoài ra còn có các thuộc tính khác
như [ExpectedException] để chỉ định phương thức kiểm thử ném ra
ngoại lệ của một kiểu ngoại lệ cụ thể nào đó.
Ví dụ 1.2: Giả sử có một lớp LuhnAlgorithm được cài đặt như sau:
public static class LuhnAlgorithm {
public static bool Validate(string number){
if (number == null)
throw new ArgumentNullException("");
foreach (var c in number)
if (!Char.IsDigit(c))
throw new ArgumentException("");
return false;
}
}
Ta có thể viết một lớp kiểm thử chứa các UT để thực hiện việc kiểm thử lớp
LuhnAlgorithm:
[TestClass]// lớp chứa các unit test
public class LuhnAlgorithmTest {
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void Test1() {
LuhnAlgorithm.Validate(null);
}
[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void Test2() {
LuhnAlgorithm.Validate("K50");
}
5
[TestMethod]
public void Test3() {
LuhnAlgorithm.Validate(“123”);
}
}
Khi thực thi phương thức kiểm thử Test1 thì ngoại lệ ArgumentNullException
được ném ra. Khi thực thi phương thức kiểm thử Test2 thì ArgumentException được
ném ra. Rõ ràng là mỗi phương thức kiểm thử ở trên chỉ có thể kiểm tra việc thực thi
của lớp LuhnAlgorithm theo một nhánh đi cụ thể. Thực thi cả 3 phương thức kiểm thử
ở trên ta sẽ kiểm tra được tất cả các trường hợp thực thi của lớp LuhnAlgorithm. Với
một chương trình có nhiều đường đi thì ta cần viết các UT khác nhau để kiểm tra sự
thực thi của chương trình theo các đường đi đó. Tuy nhiên, với những chương trình có
nhiều đường đi thực thi khác nhau thì việc viết các UT như thế đòi hỏi nhiều thời gian
và công sức để tính các giá trị đầu vào thích hợp và khó có thể kiểm tra hết được sự
thực thi của chương trình theo tất cả các đường đi.
1.3. Kiểm thử đơn vị tham số hóa
Có rất nhiều các nền kiểm thử khác nhau như JUnit[33] cho Java, NUnit[34],
VSUnit[29] cho .NET để thực thi các ca kiểm thử đơn vị. Tuy nhiên các nền kiểm thử
này không hỗ trợ việc sinh tự động các ca kiểm thử đơn vị. Việc viết các ca kiểm thử
đơn vị để thực thi tất cả các đường đi của một chương trình là một công việc nặng
nhọc. Giải pháp để giảm công sức cho việc này đó là sử dụng ca kiểm thử đơn vị tham
số hóa.
Kiểm thử đơn vị tham số hóa[7, 11, 12] là phương pháp mới trong kiểm thử phần
mềm. Kiểm thử đơn vị tham số hóa giúp cải thiện nỗ lực trong việc phát triển phần
mềm. Về bản chất nó chính là sự mở rộng của phương pháp kiểm thử đơn vị truyền
thống.
1.3.1. Khái niệm
Các UT truyền thống là các phương thức kiểm thử không tham số. Ta có thể mở
rộng các UT đó bằng cách cho phép truyền vào tham số cho các phương thức kiểm
thử. Các ca kiểm thử tham số hóa (PUT) là sự mở rộng của các UT truyền thống. Các
PUT là các phương thức kiểm thử cho phép nhận các giá trị đầu vào kiểm thử khác
nhau thông qua tham số đầu vào.
6
PUT được hiểu ở 2 khía cạnh:
+ PUT là sự đặc tả về hành vi bên ngoài của chương trình. Nếu như mỗi UT kiểm
tra sự thực thi của chương trình với những giá trị đầu vào được chọn trước và xác nhận
kết quả của lần thực thi đó có như mong đợi. Nói cách khác, mỗi UT biểu thị cho một
hành vi thực thi cụ thể của chương trình. UT chỉ cung cấp các giá trị đầu vào cụ thể
cho chương trình và xác nhận kết quả thực thi của chương trình với những đầu vào cụ
thể đó mà không quan tâm tới quá trình thực thi của chương trình với những giá trị đó
diễn ra như thế nào. Vì thế, UT biểu thị cho hành vi bên ngoài của chương trình. PUT
cũng xác nhận về hành vị thực thi của chương trình nhưng được mở rộng cho tất cả
các giá trị đầu vào có thể. Sự xác nhận (assertion) của PUT biểu thị cho các hành vi
bên ngoài của chương trình. Ở khía cạnh này, PUT giống như sự đặc tả hộp đen
(black-box) cho lớp được PUT kiểm thử.
+ Sự lựa chọn các đầu vào kiểm thử cho PUT dẫn đến việc phân tích chương
trình được kiểm thử. Việc phân tích này cần tìm ra các giá trị sao cho khi chương trình
được thực thi với các giá trị đó thì có nhiều dòng lệnh được chạy (code coverage)[5].
Nói cách khác, các giá trị cần được lựa chọn sao cho PUT có thể kiểm tra được các
đường đi thực thi khác nhau của chương trình. Và mỗi UT có thể được sinh ra từ PUT
bằng cách gọi PUT với mỗi đầu vào cụ thể được chọn. Ở khía cạnh này, PUT là kiểm
thử hộp trắng (white-box).
Từ đặc tả về PUT, ta có thể phát biểu vấn đề kiểm thử như sau:
Cho một chương trình P gồm các câu lệnh S, cần tính toán một tập hợp các đầu
vào (inputs) I sao cho với tất cả các câu lệnh cần thực thi s trong S, tồn tại đầu vào i
trong I để P(i) thực thi s.
1.3.2. Mối quan hệ giữa UT và PUT
Với mỗi chương trình có mã nguồn ta có thể viết các PUT để mô tả các hành vi
thực thi của nó. Tuy nhiên, nếu đã có các UT được viết thì ta có thể tái cấu trúc mã cài
đặt các UT đó để biến chúng thành các PUT. Ngược lại, từ các PUT ta có thể sinh lại
các UT bằng cách truyền các giá trị cụ thể làm đầu vào cho PUT. Các nền kiểm thử
đơn vị khác nhau hỗ trợ viết các UT theo các cách khác nhau. Do đó các PUT cũng
cần được viết sao cho có thể sinh ra các UT tương ứng với các nền kiểm thử đó.
7
Hình 1: Mối quan hệ giữa UT và PUT
1.3.3. Kiểm thử đơn vị tham số hóa với Pex
Pex[30] là công cụ mạnh mẽ hỗ trợ việc viết và thực thi các ca kiểm thử tham số
hóa cho môi trường .NET.
Ví dụ 1.3: Ca kiểm thử tham số hóa sử dụng Pex :
Cũng như với UT, ta có thể viết các lớp kiểm thử chứa các ca kiểm thử tham số
hóa. Với sự hỗ trợ của Pex ta có thể thực thi các ca kiểm thử tham số hóa đó. Tuy
nhiên không giống việc thực thi các lớp kiểm thử chứa các UT, Pex chỉ thực thi được
một ca kiểm thử tham số hóa trong mỗi lần chạy.
[PexMethod]
// exemplary data
public void PutArrayList(ArrayList list, object element) {
// assumptions
PexAssume.IsTrue(list != null);
// method sequence
int len = list.Count;
list.Add(element);
// assertions
PexAssert.IsTrue(list[len] == element);
}
Ca kiểm thử tham số hóa ở trên là sự mở rộng của UT trong ví dụ 1.1. Rõ ràng
các giá trị đầu vào của UT trong ví dụ 1.1 là các giá trị cụ thể đã được chuyển thành
các tham số đầu vào của phương thức. Nếu như UT trong ví dụ 1.1 kiểm thử phương
thức Add của lớp ArrayList ở khía cạnh là khi chèn một đối tượng có giá trị null vào
một ArrayList rỗng thì đối tượng được chèn vào trở thành phần tử đầu tiên của
ArrayList. Thì với ca kiểm thử tham số hóa ở trên phương thức Add được kiểm thử
với những đối tượng và ArrayList khác. Các ArrayList làm đầu vào kiểm thử có thể là
ArrayList không chứa phần tử nào hoặc ArrayList đã chứa một số phần tử, hoặc
ArrayList đã chứa đủ số phần tử so với kích cỡ được cấp phát thì khi chèn một đối
tượng object có giá trị là null hay các giá trị khác thì đối tượng được chèn vào trở
thành phần tử cuối cùng của ArrayList.
8
PUT có thể sử dụng các giả thuyết (assumptions) để giảm kích cỡ miền giá trị
của tham số đầu vào. Các tham số đầu vào có giá trị thỏa mãn giả thuyết này mới được
xem xét làm đầu vào kiểm thử. Như với PUT ở trên thì chỉ các ArrayList khác null
mới được lựa chọn làm đầu vào kiểm thử.
Khi Pex thực thi ca kiểm thử tham số hóa ở trên sẽ sinh ra Test Suite gồm 2
VSUnit UT. Đồng thời Pex cũng báo cáo về kết quả xác nhận của ca kiểm thử tham số
hóa ở trên.
[TestMethod]
public void TestAddNoOverflow() {
PutArrayList(new ArrayList(1), new object());
}
[TestMethod]
public void TestAddWithOverflow() {
PutArrayList(new ArrayList(0), new object());
}
Với ca kiểm thử tham số hóa này phương thức Add của lớp ArrayList được kiểm
thử với tất cả các trường hợp thực thi có thể xảy ra.
Thuộc tính [PexClass] của Pex để xác định một lớp kiểm thử là lớp chứa các ca
kiểm thử tham số hóa. Thuộc tính [PexMethod] để chỉ định một phương thức kiểm thử
là ca kiểm thử tham số hóa. Ta muốn viết một lớp kiểm thử chứa các ca kiểm thử tham
số hóa cần sử dụng thuộc tính [PexClass] và [PexMethod] để viết các ca kiểm thử
tham số hóa.
1.3.4. Các mẫu kiểm thử tham số hóa
Viết các ca kiểm thử tham số hóa là một nghệ thuật. Để viết các ca kiểm thử tham số
hóa hiệu quả, ta cần thực sự hiểu về mã cài đặt của chương trình mà ta muốn kiểm thử.
Pex hỗ trợ nhiều mẫu kiểm thử tham số hóa khác nhau[15]. Các mẫu được sử dụng
nhiều nhất đó là mẫu AAA (Triple-A) và AAAA:
+ Với mẫu AAA (Arrange, Act, Assert) PUT được tổ chức thành 3 phần:
Arrange: khởi tạo giá trị các biến sẽ sử dụng
Act: dãy các lời gọi phương thức
Assert: sự xác nhận
+ Với mẫu AAAA, một giả thuyết (Assume) được thêm vào để giới hạn miền
giá trị của các tham số đầu vào.
9
Ví dụ 1.4: Mẫu kiểm thử tham số hóa AAAA
[PexMethod]
void AssumeActAssert(ArrayList list, object item) {
// assume
PexAssume.IsNotNull(list);
// arrange
var count = list.Count;
// act
list.Add(item);
// assert
Assert.IsTrue(list.Count == count + 1);
}
1.3.5. Lựa chọn đầu vào kiểm thử với Pex
Thêm tham số vào UT cải thiện đặc tả về hành vi mong muốn nhưng lại mất đi
các ca kiểm thử cụ thể. Ta cần những giá trị thực sự cho các tham số đầu vào để sinh
lại các ca kiểm thử cụ thể. PUT sẽ không thể thực thi nếu không có các giá trị cụ thể
được truyền vào cho các tham số đầu vào của PUT.
Để có thể sinh các đầu vào cụ thể cho PUT. Pex cần phải phân tích chương trình
mà PUT kiểm thử. Có 2 kỹ thuật phân tích chương trình đó là phân tích tĩnh và phân
tích động:
+ Phân tích tĩnh (static analysis): Kiểm chứng một tính chất nào đó của chương
trình bằng việc phân tích tất cả các đường đi thực thi. Kỹ thuật này coi các cảnh bảo
(violations) là các lỗi (error).
+ Phân tích động (dynamic analysis): Kiểm chứng một tính chất bằng việc phân
tích một số đường đi thực thi. Đây là một kỹ thuật phân tích động hỗ trợ việc phát hiện
ra các lỗi (bugs) nhưng không khẳng định được rằng có còn những lỗi khác hay không.
Các kỹ thuật này thường không tìm ra được tất cả các lỗi.
Pex cài đặt một kỹ thuật phân tích chương trình bằng cách kết hợp cả hai kỹ thuật
phân tích chương trình ở trên gọi là thực thi tượng trưng động[14, 25]. Về bản chất
Pex là một công cụ hỗ trợ kỹ thuật kiểm thử hộp trắng (white-box testing). Tương tự
như kỹ thuật phân tích chương trình tĩnh, Pex chứng minh được rằng một tính chất
được kiểm chứng trong tất cả các đường đi khả thi. Pex chỉ báo cáo (reporting) về các
lỗi thực sự như với kỹ thuật phân tích chương trình động.
10
Pex sử dụng khả năng của bộ xử lý ràng buộc Z3[31] kết hợp với các lý thuyết
toán học khác như hàm chưa định nghĩa, lý thuyết mảng, bit-vetor[2] để giải quyết
ràng buộc sinh ra trong quá trình thực thi tượng trưng động và sinh ra các đầu vào
kiểm thử cụ thể cho PUT.
11
Chương 2: Sinh dữ liệu kiểm thử tự động cho PUT
Trong kiểm thử phần mềm, các ca kiểm thử thường được tạo ra thủ công do đó
gây tốn kém trong chi phí phát triển phần mềm và làm kéo dài thời gian để hoàn thành
một phần mềm.
Có các phương pháp khác nhau hỗ trợ việc sinh tự động các ca kiểm thử một
cách có hệ thống giúp giảm chi phí cho việc kiểm thử phần mềm. Một trong những
phương pháp đơn giản nhất để sinh tự động các ca kiểm thử đó là kiểm thử ngẫu nhiên
(random testing)[5]. Với kiểm thử ngẫu nhiên các đầu vào cho hệ thống được sinh
ngẫu nhiên. Để thực hiện, một luồng các bits được sinh ngẫu nhiên để thể hiện cho các
giá trị của tham số đầu vào. Giả sử với một hàm nhận tham số đầu vào có kiểu string
thì chỉ cần sinh ngẫu nhiên một luồng các bits để thể hiện giá trị cho một chuỗi. Kiểm
thử ngẫu nhiên có ưu điểm là dễ dàng sinh các đầu vào ngẫu nhiên và hệ thống kiểm
thử ngẫu nhiên không yêu cầu nhiều tài nguyên bộ nhớ lúc thực thi. Nhưng hạn chế
của nó là kiểm tra cùng một hành vị thực thi của chương trình nhiều lần với những đầu
vào khác nhau và chỉ có thể kiểm tra được một số trường hợp thực thi của chương
trình. Thêm vào đó, kiểm thử ngẫu nhiên khó xác định được khi nào việc kiểm thử nên
được dừng lại và nó không biết tại điểm nào không gian trạng thái đã được thám hiểm
hết. Để xác định khi nào việc kiểm thử dừng lại thì hệ thống kiểm thử ngẫu nhiên được
kết hợp với các adequacy criterion [5]. Giả sử adequacy criterion là bao phủ lệnh
(statement coverage)[5] thì việc kiểm thử chỉ dừng lại khi tất cả các câu lệnh của
chương trình được thực thi ít nhất một lần.
Một phương pháp khác đang rất phổ biến giúp khắc phục được hạn chế của kiểm
thử ngẫu nhiên đó là thực thi tượng trưng[6]. Thực thi tượng trưng chính là việc xây
dựng các ràng buộc dựa vào các giá trị tượng trưng và giải quyết các ràng buộc đó để
sinh ra các giá trị làm đầu vào chương trình mà có thể thực thi chương trình theo các
đường đi thực thi khác nhau. Ý tưởng chính của thực thi tượng trưng đó là thực thi một
chương trình với những giá trị tượng trưng. Có hai cách tiếp cận đối với thực thi tượng
trưng đó là cách tiếp cận dựa trên phân tích tĩnh và phân tích động chương trình.
Việc lựa chọn các đầu vào kiểm thử cho PUT liên quan tới vấn đề sinh dữ liệu
kiểm thử tự động. Sinh dữ liệu kiểm thử tự động là một lĩnh vực nghiên cứu rất rộng
trong kiểm thử phần mềm. Có rất nhiều các kỹ thuật khác nhau[8, 10, 18] được sử
dụng để sinh dữ liệu kiểm thử tự động. Tuy nhiên với PUT thì các dữ liệu làm đầu vào
kiểm thử cần được chọn lựa làm sao để PUT có thể kiểm tra được sự thực thi của
12
- Xem thêm -