Академический Документы
Профессиональный Документы
Культура Документы
SUMMARY
In software development, maintaining and testing are considered as boring tasks when
comparing to programming. Software people are more excited to new and innovative
things than to those that are already well known to them. However, the more software
are produced, the more bugs we observe. Testing is an indispensable activity in
software development for bug detection and quality assurance. This writing
introduces the use an open source tool known as JUnit to facilitate the unit testing
activity. Recognizing the importance of this tool, many integrated development
environments (IDEs), such as JBuilder, have integrated JUnit to their architecture.
ĐẶT VẤN ĐỀ
Trong chuyến công tác tại Đại học Calgary – Canada vào cuối năm 2003, chúng tôi
nhận thấy nhiều môn học liên quan đến công nghệ phần mềm, ở bậc đại học lẫn cao
học, đã nhấn mạnh đến công đoạn kiểm thử chương trình. Mọi chương trình đều phải
đi kèm với bộ số liệu kiểm thử. Tuy nhiên, hoạt động này chưa thực sự phổ biến trong
chương trình giảng dạy lập trình và công nghệ phần mềm tại một số trường đại học ở
nước ta. Người lập trình thường xem nhẹ việc kiểm thử, đơn giản vì đó là một công
việc nhàm chán, ít gây hứng thú. Nhưng kiểm thử là một hoạt động quan trọng và
không thể thiếu được nhằm phát hiện lỗi trong chương trình, từ đó nâng cao năng suất
và đảm bảo chất lượng sản phẩm phần mềm. Beck và Gamma là những người đầu tiên
phát triển công cụ mã nguồn mở JUnit để hỗ trợ việc kiểm thử. Bài viết này sẽ trình
bày lại một ví dụ minh họa việc áp dụng JUnit bằng việc đưa ra một thiết kế đơn giản
và hợp lý để giải quyết bài toán đặt ra.
JUnit tránh cho người lập trình phải làm đi làm lại những việc kiểm thử nhàm chán
bằng cách tách biệt mã kiểm thử ra khỏi mã chương trình, đồng thời tự động hóa việc
tổ chức và thi hành các bộ số liệu kiểm thử.
Thoạt tiên, khi sử dụng JUnit, ta có thể có cảm giác là JUnit chỉ làm mất thêm thời
gian cho việc kiểm thử: Thay vì phải viết thêm các lớp và phương thức mới phục vụ
cho công tác kiểm thử, ta có thể soạn nhanh một bộ số liệu rồi viết ngay vào trong
phương thức main() và quan sát ngay kết quả kiểm thử. Vì quá trình soạn số liệu và
quá trình kiểm thử diễn ra đồng thời, nên ta sẽ dễ dàng nhận biết được ngay chương
trình đã chạy đúng trên bộ số liệu kiểm thử hay không, mà không cần nhìn vào tín
hiệu “xanh” mà JUnit có thể hỗ trợ.
Nhưng khi tổ chức lại chương trình cho hợp lý hơn (refactoring) hoặc khi phải thay
đổi chương trình để phục vụ cho nhu cầu mới, các bộ số liệu kiểm thử trước đây sẽ
cần được sử dụng lại để chắc chắn rằng những thay đổi trong chương trình không làm
phương hại đến những thành quả trước đó, lúc này ta sẽ phải mất thời gian để tìm hiểu
lại xem bộ số liệu trước đây sẽ tương ứng với kết xuất gì vì ta không thể nhớ hết mọi
hoạt động kiểm thử đã diễn ra. Việc nhớ lại những kiểm thử đã qua sẽ chẳng thú vị vì
không đem đến cho ta điều gì mới. Nếu phải kiểm thử trên những bộ số liệu lớn thì
gánh nặng của việc tổ chức kiểm thử sẽ chồng chất thêm. JUnit giúp người lập trình
tự động hóa các công việc nhàm chán, và chỉ cần nhìn thấy tín hiệu “xanh” là ta có thể
yên tâm. Phụ lục A sẽ hướng dẫn việc tích hợp JUnit vào môi trường Eclipse.
VÍ DỤ MINH HỌA
Sau đây là một ví dụ minh họa với những yêu cầu mới dần dần được thêm vào: Hãy
thiết kế lớp tiền tệ. Tiền tệ được đặc trưng bằng số tiền và đơn vị tiền (chẳng hạn
VND hoặc USD). Trước yêu cầu này, ta dễ dàng viết ra lớp Money ở Hình 1.
Bây giờ giả sử rằng ta chỉ cần xử lý một loại tiền tệ duy nhất, chẳng hạn tiền Việt
Nam. Hãy hiện thực phương thức dưới đây để cộng hai số tiền cùng loại với nhau, và
dùng JUnit để kiểm thử chương trình.
Money add(Money money)
Hình 2 sẽ trình bày phần hiệu chỉnh lớp Money nhằm giải quyết yêu cầu mới vừa đặt
ra.
import junit.framework.Assert;
import junit.framework.TestCase;
Ở đây ta đã dùng lệnh assertTrue() và phương thức equals() vì kết quả và dự kiến là
hai đối tượng. Nếu kết quả và dự kiến thuộc kiểu dữ liệu nguyên thủy, chẳng hạn int,
thì ta có thể dùng một trong hai lệnh sau:
Assert.assertTrue(result == expected);
Assert.assertEquals(result, expected);
Lệnh kiểm chứng thứ hai sẽ cung cấp nhiều thông tin hơn nếu kết quả tính toán và dự
kiến không trùng khớp nhau, từ đó giúp người lập trình nhanh chóng phát hiện lý do
gây ra lỗi bên trong chương trình. (Thi hành việc kiểm thử JUnit trong môi trường
Eclipse: Chọn lớp kiểm thử → Kích vào menu Run → Run As → JUnit Test. Có thể
cần click vào thẻ JUnit ở phía dưới bên trái màn hình để thấy được tín hiệu “xanh”
hoặc “đỏ”)
Sau khi thi hành JUnit Test, ta sẽ gặp tín hiệu “đỏ” (Hình 3), nghĩa là chương trình đã
có lỗi. Để có được tín hiệu “xanh”, ta cần định nghĩa lại (override) phương thức
equals() bên trong lớp Money để so sánh bằng giữa hai đối tượng (Hình 4).
Hình 3. Kết quả kiểm thử khi thi hành lớp MoneyTest ở Hình 2
Ta cũng cần viết mã để kiểm thử tính đúng đắn của phương thức equals() (Hình 5).
Sau khi thi hành JUnit Test, ta sẽ được tín hiệu “xanh” (Hình 6). Điều cần lưu ý là
phương thức equals() ở Hình 4 không thật sự an toàn nếu số tiền có phần thập phân,
nhưng chi tiết tế nhị này được cố ý bỏ qua để đơn giản hóa vấn đề đang đề cập.
Hình 6. Kết quả kiểm thử sau khi định nghĩa lại phương thức equals()
Quan sát mã kiểm thử ở Hình 5, ta thấy cần phải tổ chức lại vì đã có sự trùng lặp mã.
Để loại bỏ sự trùng lặp mã này, ta có thể chuyển hai đối tượng cục bộ m1 và m2 thành
hai thuộc tính riêng tư. (Chuyển biến cục bộ thành thuộc tính lớp trong môi trường
Eclipse: Kích vào biến cục bộ cần chuyển → menu Refactor → Convert Local
Variable to Field… → Initialize in: Field declaration)
Bây giờ giả sử rằng ta lại có thêm một yêu cầu mới: Các đơn vị kinh doanh có thể giữ
nhiều hơn một loại tiền, chẳng hạn vừa có VND vừa có USD. Khi thêm một loại tiền
khác với loại tiền hiện có thì chương trình không được cộng vào số tiền hiện có mà
phải lưu trữ riêng loại tiền mới này. Ví dụ, nếu hiện thời ta có 1200 VND, thì sau khi
thêm 10 USD, ta sẽ có 1200 VND và 10 USD. Hãy thiết kế chương trình để thực hiện
yêu cầu trên.
Để hoàn thành yêu cầu, ta sẽ thiết kế thêm một lớp mới có tên là MoneyBag để lưu trữ
một danh sách các loại tiền khác nhau, và viết thêm một lớp kiểm thử mới (Hình 7).
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import junit.framework.Assert;
import junit.framework.TestCase;
Sau đây là câu hỏi dành cho bạn đọc trước khi chúng tôi trình bày tiếp: Tại sao không
viết phương thức equals() trong lớp MoneyBag và sử dụng nó trong JUnit Test?
Ngoài việc dùng phương thức toString() để kiểm thử, ta còn có thể viết phương thức
equals() và một phương thức bổ trợ, được đặt tên là contains(), để kiểm tra xem đối
tượng MoneyBag có chứa một đối tượng Money cho trước không (Hình 8).
Vì MoneyBag chỉ chứa các đối tượng Money phân biệt, nên khi xét tính bằng nhau
giữa hai đối tượng MoneyBag trong phương thức equals(), ta chỉ cần kiểm chứng đối
tượng này có phải là tập con của đối tượng kia và số phần tử của hai đối tượng cần
bằng nhau là đủ. Việc kiểm thử tính đúng đắn của các phương thức equals() được thể
hiện trong phương thức testEquals() (Hình 9). Ngoài ra, vì thuộc tính của phương
thức bổ trợ contains() là private nên ta không thể kiểm thử riêng phương thức này
trong lớp MoneyBagTest, mà đã chuyển vào phương thức main() của lớp MoneyBag.
System.out.println(!bag.contains(m1));
bag.add(m1);
System.out.println(bag.contains(m1));
System.out.println(!bag.contains(m2));
bag.add(m2);
System.out.println(bag.contains(m2));
}
}
Hình 8. Bổ sung vào MoneyBag hai phương thức contains() và equals()
Để kết hợp đồng thời nhiều lớp lớp kiểm thử khác nhau, ta có thể viết ra một bộ kiểm
thử (TestSuit) như được minh họa ở Hình 10. (Trong môi trường Eclipse: Kích vào
menu File → New → Other… → Java → JUnit → TestSuit → Next → Finish)
import junit.framework.Test;
import junit.framework.TestSuite;
• Hãy bổ sung phương thức subtract() vào lớp MoneyBag để trừ bớt một số tiền cho
trước từ số tiền hiện có. Đừng quên kiểm thử với JUnit.
• Như đã được đề cập, bài viết này chủ yếu dựa trên bài viết của Beck và Gamma.
Hãy đọc bài đó và đối chiếu, rồi cho nhận xét về phương án thiết kế lời giải của
hai bài viết.
KẾT LUẬN
Các thao tác sửa mã chương trình trong quá trình bảo trì phần mềm là nỗi lo thường
trực đối với người phát triển chương trình vì đôi khi chỉ một thay đổi nhỏ cũng có thể
làm toàn bộ chương trình ngưng hoạt động. Công cụ JUnit giúp giảm bớt gánh nặng
này bằng cách đưa ra một khuôn khổ hợp lý nhằm tổ chức các bộ số liệu kiểm thử và
tự động hóa một số công việc nhàm chán, từ đó khuyến khích người lập trình thực
hiện việc kiểm thử thường xuyên hơn. Kiểm thử càng nhiều thì càng giúp nhanh
chóng phát hiện lỗi, dẫn đến việc giảm bớt lỗi trong chương trình, từ đó nâng cao
năng suất lập trình và chất lượng phần mềm. JUnit có thể kết hợp với những hoạt
động khác để tăng tính hiệu quả trong việc bắt lỗi. Một trong những hoạt động được
trường phái Lập trình Cực đoan (Extreme Programming) khuyến khích đó là lập trình
bắt cặp (pair programming) và chúng tôi đang thực hiện một vài thực nghiệm về hoạt
động lý thú này.
CẢM TẠ
Chúng tôi chân thành cảm ơn Viện Công nghệ thông tin và Công nghệ phần mềm –
Đại học Liên hiệp quốc (UNU/IIST), Macao và Khoa Kỹ nghệ Điện và Máy tính –
Đại học Calgary, Canada đã tạo điều kiện cho chúng tôi tiếp cận với phương pháp
kiểm thử dùng JUnit.
BECK, K., GAMMA. E, 1998. Test Infected: Programmers Love Writing Tests. Java
Report, Vol. 3, No. 7, p.51 – 56.
http://JUnit.sourceforge.net/doc/testinfected/testing.htm
2. Trong môi trường Eclipse tạo một project mới có tên junit
3. Chuyển file src.jar vào project junit: Kích vào menu File → Import… → Zip file →
Next (xem Hình A1) → Browse… để chuyển đến vị trí file src.jar rồi chọn lựa các
mục như được minh họa ở Hình A2 → Finish
Hình A1
Hình A2
4. Có thể thêm phần hỗ trợ Javadoc: Kích phải vào project junit → Properties →
Javadoc Location → chỉ đường dẫn về folder junit (xem ví dụ minh họa ở Hình
A3)
Hình A3
Các bước từ 1 đến 4 chỉ cần được thực hiện một lần duy nhất. Sau này mỗi khi tạo ra
một project chương trình mới và muốn kiểm thử dùng JUnit, ta cần thông báo cho
project này đường dẫn đến junit như sau:
• Click phải vào project cần áp dụng JUnit → Properties → Java Build Path →
click thẻ Project rồi chọn junit (xem minh họa ở Hình A4) → OK
Hình A4