SlideShare ist ein Scribd-Unternehmen logo
1 von 13
Downloaden Sie, um offline zu lesen
I. Bai tap Testing Junit…..
16/10/2009 · Tập tin được lưu ở: Uncategorized
Đề Tài:
Những người thực hiện:
+ Trần Đình Lương. MSSV
+ Nguyễn Ngọc Tuyền.
+ Hồ Thanh Bình.
—————******—————-
I. Giới thiệu JUnit
JUnit là một framework đơn giản dùng cho việc tạo các unit testing tự động, và chạy các test có
thể lặp đi lặp lại. Nó chỉ là một phần của họ kiến trúc xUnit cho việc tạo các unit testing. JUnit là
một chuẩn trên thực tế cho unit testing trong Java. JUnit về nguồn gốc được viết bởi 2 tác giả
Erich Gamma và Kent Beck 1
Giới thiệu:
JUnit có những đặc điểm đáng lưu tâm như sau:
• Xác nhận (assert) việc kiểm tra kết quả được mong đợi
• Các Test Suite cho phép chúng ta dễ dàng tổ chức và chạy các test
• Hỗ trợ giao diện đồ họa và giao diện dòng lệnh
Các test case của JUnit là các lớp của Java, các lớp này bao gồm một hay nhiều các phương thức
unit testing, và những test này lại được nhóm thành các Test Suite.
Mỗi phương thức test trong JUnit phải được thực thi nhanh chóng. Tốc độ là điều tối quan trọng
vì càng nhiều test được viết và tích hợp vào bên trong quá trình xây dựng phần mềm, cần phải
tốn nhiều thời gian hơn cho việc chạy toàn bộ Test Suite. Các lập trình viên không muốn bị ngắt
quãng trong một khoãng thời gian dài trong khi các test chạy, vì thế các test mà chạy càng lâu thì
sẽ có nhiều khả năng là các lập trình viên sẽ bỏ qua bước cũng không kém phần quan trọng này.
Các test trong JUnit có thể là các test được chấp nhận hay thất bại, các test này được thiết kế để
khi chạy mà không cần có sự can thiệp của con người. Từ những thiết kế như thế, bạn có thể
thêm các bộ test vào quá trình tích hợp và xây dựng phần mềm một cách liên tục và để cho các
test chạy một cách tự động
1. Đặt vấn đề
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.
2. Lợi ích của JUnit
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ợ.
3. 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:
public class Money {
private double amount;
private String currency;
public Money(double amount, String currency) {
this.amount = amount;
this.currency = currency;
}
}
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)
Do đó sẽ phải hiệu chỉnh class Money để giải quyết vấn đề đặt ra:
public class Money {
// Phần tương tự như trong Hình 1 đã được lược bớt
public Money add(Money money) {
return new Money(this.amount + money.amount, this.currency);
}
}
import junit.framework.Assert;
import junit.framework.TestCase;
public class MoneyTest extends TestCase {
public void testAdd() {
Money m1 = new Money(200, “VND”);
Money m2 = new Money(1000, “VND”);
Money result = m1.add(m2); // đối tượng lưu kết quả tính toán
Money expected = new Money(1200, “VND”); // kết quả dự kiến
Assert.assertTrue(result.equals(expected)); // lệnh kiểm thử
}
}
Phần kiểm thử có bốn điểm đáng chú ý sau:
1. Phương thức kiểm thử cần được đặt tên bắt đầu bằng từ test
2. Đối tượng lưu kết quả tính toán,
3. Đối tượng lưu kết quả dự kiến, và
4. Kiểm chứng sự trùng khớp giữa kết quả tính toán và kết quả dự kiến.
Ở đâ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 (primitive), 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 (đoạn code dưới).
public class Money {
// Phần tương tự như trong Hình 2 đã được lược bớt
public boolean equals(Object object) {
if (object instanceof Money) {
Money money = (Money)object;
return this.amount == money.amount &&
this.currency == money.currency;
}
return false;
}
}
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() Ở đoạn code trên 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.
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
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class MoneyBag {
private List monies = new ArrayList();
public String toString() {
String s = “{ “;
Iterator iter = monies.iterator();
while (iter.hasNext())
s += iter.next() + ” “;
return s + “}”;
}
public void add(Money money) {
for (int i = 0; i < monies.size(); i++) {
Money m = (Money)monies.get(i);
if (money.getCurrency().equals(m.getCurrency())) {
monies.set(i, m.add(money));
return;
}
}
// money is a new currency
monies.add(money);
}
}
import junit.framework.Assert;
import junit.framework.TestCase;
public class MoneyBagTest extends TestCase {
public void testAddWithToString() {
MoneyBag bag = new MoneyBag();
Assert.assertEquals(bag.toString(), "{ }");
bag.add(new Money(1200, "VND"));
Assert.assertEquals(bag.toString(), "{ [1200 VND] }");
bag.add(new Money(10, "USD"));
Assert.assertEquals(bag.toString(), "{ [1200 VND] [10 USD] }");
}
}
Khi thi hành JUnit Test, ta sẽ gặp tín hiệu “đỏ”. Bằng việc chèn lệnh
System.out.println() vào phương thức testAddWithToString(), ta sẽ phát hiện ra kết
quả phải là { [1200.0 VND] }, thay vì { [1200 VND] }. Từ đó ta biết rằng cần phải hiệu
chỉnh kết quả để được tín hiệu “xanh”. Điều này một lần nữa cho thấy điểm tế nhị khi
phải thực hiện việc so sánh trên các số thực.
Sau đây là câu hỏi dành cho bạn đọc trước khi 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.
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(). 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.
public class MoneyBag {
// Phần tương tự như trong Hình 7 đã được lược bớt
private boolean contains(Money money) {
Iterator iter = monies.iterator();
while (iter.hasNext()) {
if (money.equals((Money)iter.next()))
return true;
}
return false;
}
public boolean equals(Object object) {
if (object instanceof MoneyBag) {
MoneyBag bag = (MoneyBag)object;
if (monies.size() != bag.monies.size())
return false;
Iterator iter = bag.monies.iterator();
while (iter.hasNext()) {
Money m = (Money)iter.next();
if (!monies.contains(m))
return false;
}
return true;
}
return false;
}
public static void main(String[] args) {
MoneyBag bag = new MoneyBag();
Money m1 = new Money(1200, "VND"); Money m2 = new Money(10, "USD");
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));
}
}
public class MoneyBagTest extends TestCase {
public void testAddWithToString() {
……………….
}
public void testAddWithEquals() {
MoneyBag bag1 = new MoneyBag();
MoneyBag bag2 = new MoneyBag();
Assert.assertTrue(bag1.equals(bag2));
bag1.add(new Money(1200, "VND"));
Assert.assertFalse(bag1.equals(bag2));
bag1.add(new Money(10, "USD"));
bag2.add(new Money(10, "USD"));
bag2.add(new Money(1200, "VND"));
Assert.assertTrue(bag1.equals(bag2));
}
}
Để 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). (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;
public class AllTests {
public static Test suite() {
TestSuite suite = new TestSuite(“Test for Money”);
suite.addTest(new TestSuite(MoneyTest.class));
suite.addTest(new TestSuite(MoneyBagTest.class));
return suite;
}
}
II. Giới thiệu Unit Testing
- Xin mở đầu bằng một tình huống thế này: Anh Chuột được assign một task là viết một hàm
kiểm tra tính hợp lệ cho dữ liệu nhập vào một text box. Dữ liệu này là một chuỗi các chữ số và
dấu chấm của một số kiểu double có giá trị lớn hơn hoặc bằng 0. Giá trị chuỗi chỉ được chứa tối
đa 1 chữ số sau dấu chấm. Chuỗi số này có thể là giá trị của số nguyên tức là không có dấu chấm
nào cả, và cuối cùng giá trị của số nhập vào phải nằm trong khoảng 0 đến 100. Với nhiều điều
kiện ràng buộc như vậy anh Chuối quyết định áp dụng Test Driven Development kết hợp với
Unit Test để thực hiện và Anh Chuột đã viết một hàm test thể hiện mọi yêu cầu như sau:
Code 1: Ví dụ về một hàm test
- Tất nhiên sau khi viết hàm Test, Anh Chuột sẽ bắt tay vào implement class NumberChecker để
cái test này pass. Anh Chuột cho rằng nên kết hợp Regular Expression và các hàm Parse của kiểu
double là nhanh nhất , do đó Anh Chuột đã làm như sau:
Code 2: Implement lớp checknumber để Unit Test pass
- Lúc đó, trong team của Anh Chuột có chị Bưởi là một QC khét tiếng khó chịu, chị Bưởi này có
tật là test và soi mói chương trình rất kĩ và đã phát hiện ra rất nhiều bug hiểm hóc mà một
developer chân chính như Anh Chuột không ngờ tới. Sau khi test, chị Bưởi phát hiện ra rằng nếu
người ta nhập vào 000 thì chương trinh vẫn cho nhập, ngược lại khi nhập +100 thì chương trình
báo lỗi không hợp lệ. Phát hiện được bug này chị Bưởi rất vui và assign liền 1 bug cho anh
Chuối với status là critical.
- Rõ ràng là Anh Chuột đã tính toán thiếu một số trường hợp. Hàm test sẽ được thêm vào một số
dòng code để check các trường hợp chị Bưởi liệt kê, đồng thời regular expression của Anh Chuột
phải được sửa lại cho đúng.
- Trong ngành phần mềm, thuật ngữ Unit Testing là một phương pháp dùng để kiểm tra tính
đúng đắn của một đơn vị source code. Một Unit (đơn vị) source code là phần nhỏ nhất có thể test
được của chương trình. Trong lập trình thủ tục, một unit có thể là cả chương trình, một function
hay một procedure. Còn trong lập trình hướng đối tượng, đơn vị nhỏ nhất có lẽ là một method
của một class nào đó.
- Điều kiện lý tưởng nhất là mỗt test case phải độc lập với những test case khác. Người ta có thể
dùng nhiều kĩ thuật như stubs, mock hoặc fake objects, … để phục vụ việc test các module trong
chương trình. Viết Unit test là trách nhiệm, nghĩa vụ và quyền lợi của các lập trình viên, lập trình
viên chúng ta nên sử dụng Unit Test để bảo đảm những gì mình viết chạy đúng như yêu cầu phần
mềm, và nhất là đúng với cách mình hiểu.
II.1 Lợi ích của Unit Test
- Nhiều . Mục đích của Unit Test là cô lập từng phần của chương trình và đảm bảo những phần
đó chạy đúng như yêu cầu. Unit test giúp bảo đảm tính chính xác của chương trình, nó giúp thiết
lập những ràng buộc và những phần code của chúng ta phải thực hiện chính xác những ràng buộc
đó. Kết quả là Unit Test đem lại rất nhiều lợi ích, nhưng rõ ràng nhất là nó giúp phát hiện lỗi và
những vấn đề liên quan ngay từ những phase đầu tiên của quá trình phát triển phần mềm.
II.2 Unit Test giúp cho việc sửa đổi dễ dàng hơn
- Trên lý thuyết, Unit Test cho phép lập trình viên refactor code và bảo đảm những gì anh ta viết
vẫn chạy đúng sau khi code bị thay đổi. Để làm được điều này, người ta buộc phải viết các test
case cho tất cả các function và methods và do đó bất cứ một thay đổi nào làm chương trình chạy
sai sẽ bị phát hiện kịp thời và buộc người gây lỗi phải fix ngay. Còn trong thực tế để những test
case của bạn cover hết toàn bộ những trường hợp trong chương trình lại là một vấn đề khác.
- Nếu như team của bạn có sử dụng những hệ thống build tự động như CruiseControl.NET có sử
dụng Nunit test thì mỗi lần commit code gây lỗi sẽ dễ dàng phát hiện thủ phạm như hình dưới
đây:
Hình 1: Giao diện Report của CruiseControl.NET
II.3 Unit test giúp tính hợp code dễ hơn
- Bạn làm việc trong một team, mỗi người làm một phần của chương trình và mỗi phần bạn viết
đều đã apply unit test kĩ càng. Đến khi kết hợp những thành phần của team với nhau, quá trình
đó nói chung sẽ rất xuông sẽ và ít lỗi hơn nhiều so với việc mạnh ai nấy code rồi cuối cùng
merge lại với nhau.
II.4 Document và Design
- Mỗi một test case bạn viết có thể được xem như API document cho chính method được test.
Một team member vào sau bạn có thể dự vào test case đó để hiểu hàm này công dụng là gì, input
thế nào và output ra sao.
- Trong quá trình phát triển phần mềm, document của chương trình bao gồm các design,
requirement có thể bị bỏ quên và trở nên “out of date” nhưng những Unit Test Cases sẽ luôn
chính xác những gì chương trình thực hiện và vì vậy ở một khía cạnh nào đó, Unit Test có thể
được xem như một dạng document của chương trình.
II.5 Những hạn chế của Unit Test
- Người ta khó có thể viết Unit Test để bắt tất cả các lỗi của 1 chương trình. Thêm vào đó, những
test case ta viết chỉ kiểm lỗi những unit nhỏ nhất của chương trình do đó không thể nào lường
trước những vấn đề có thể xảy ra khi kết hợp các module với nhau. Unit testing sẽ thể hiện được
hiệu quả rõ nhất khi kết hợp nó với những kĩ thuật test khác và tất nhiên sẽ cần tới sức người.
Unit Testing không thể nào thay thế được QC – Tester và cũng như nhiều kiểu test khác, nó chỉ
có thể kiểm tra được những lỗi đã biết chứ không thể sử dụng nó để tìm ra các lỗi tiềm ẩn của
chương trình.
- Software testing là một tổ hợp của nhiều trường hợp. Ví dụ như để kiểm tra một hàm trả về
kiểu boolean, tức là có hai trường hợp trả về chúng ta thường phải viết ít nhất hai dòng code để
test lần gọi hàm đó. Anh Nguyễn Văn Chuối rất thường viết những hàm dài cả trăm dòng code
với nhiều if / else, làm sao bảo đảm rằng anh Chuối có thể viết một test case có thể cover hết
những trường hợp có thể xảy ra. Trong trường hợp đó anh Chuối có thể refactor code để chia nhỏ
thân hàm thành nhiều hàm nhỏ hơn rồi từ đó test các hàm nhỏ đó. Nếu team của bạn có sử dụng
một Continuous Enviroment với NCoverExplorer thì sẽ dễ dàng phát hiện test case của bạn cover
bao nhiêu % chương trình:
Hình 2: Giao diện Report của NCover trong dashboard của CruiseControl.NET
- Có nhiều trường hợp khác chúng ta không thể nào sử dụng Unit Test, chẳng hạn như không thế
test private class, private method, … nên nói chung Unit Test là một công cụ hỗ trợ chứ không
thể thay thế các kĩ thuật test đang được nhiều người sử dụng.
III. Một số tool và framework hỗ trợ Unit Testing
III.1 NUnit:
Là một unit-testing framework cho ngôn ngữ lập trình .NET được port từ Junit. NUnit có hai
dạng là console và GUI. Thực sự thì NUnit thường được sử dụng kết hợp với CruiseControl.NET
và dùng để test tự động trên build server, và lập trình viên bình thường cũng không cần
download về máy làm gì.
III.2 TestDriven.NET:
Là một trong những tool không thể thiếu đối với dân .NET. Khi install vào máy, nó sẽ tích hợp
một menu vào Visual Studio.NET và cho phép chúng test, debug các class/method rất tiện lợi,
ngoài ra ta có thể sử dụng assembly nunit.framework trong thư mục cài đặt của TestDriven.NET
để sử dụng cho project test.
Hình 3: Menu run test khi cài TestDriven.NET
III.3 NCover, NCoverExplorer:
Như giới thiệu ở trên, các tool này giúp chúng ta kiểm soát mức độ cover của các test case đối
với source code, và cũng giống như NUnit, chúng thường được kết hợp với CruiseControl.NET
để report sau khi source code được build tự động.
III.4 NMock, NMock2, Rhino Mock và TypeMock
- Các tool trên giúp chúng ta giả lập một object để test một component của chương trình khi mà
component này có reference đến một component khác. Chúng ta sẽ sử dụng các kĩ thuật mock
này để test project EnterpriseSample.Core bằng cách giả lập các object kiểu IxxxDao mà không
cần đến EnterpriseSample.Data. Cách sử dụng các tool trên tương đối giống nhau và sẽ được ví
dụ bằng Rhino Mock trong phần dưới đây, thông dụng nhất có lẽ là Rhino Mock và Type Mock
IV. Tạo project test sử dụng NUnit và Rhino Mock
Bài viết này được dịch,- Người ta thường tạo một project dạng class library dành cho các test
class. Project này theo đúng tên gọi của nó chỉ có ý nghĩa để test và không có vai trò gì trong sản
phầm phần mềm cuối cùng. Thực ra NUnit có thể test bất kí test class nào bên trong một
assembly bất kì nên project test có thể là Console application, window application, v.v nhưng
thông thường người ta sẽ chọn project loại class library. Có một lưu ý là test class của bạn phải
được khai báo public, test method cũng thế. Khi sử dụng NUnit.Framework, các bạn sẽ phải làm
quen với những Attribute như [TestFixture], [Test], [Setup], [TearDown], … xin được giải thích
ngắn gọn những Attribute thường được sử dụng nhất như sau:
[TestFixture]: Dùng để đánh đấu 1 class là test class, những class khác không có Attribute này sẽ
mặc định bị ignore khi NUnit test assembly của bạn.
[Test]: Dùng để đánh dấu 1 method là test method, ý nghĩa của nó tương tự như TestFixture
nhưng scope ở cấp method.
[Setup]: Dùng để đánh dấu 1 method sẽ được gọi trước khi 1 test case được gọi. Nếu trong 1 test
class có 10 method test, thì mỗi lần một method test được chạy thì NUnit sẽ chạy method được
đánh dấu với Setup trước tiên.
[TearDown]: Ngược với Setup, chạy sau mỗi test method.
[TestFixtureSetup]: Tương tự như Setup nhưng ở cấp của class, khi 1 test class được test thì
method nào được đánh dấu với attribute này sẽ được chạy trước tiên.
[TestFixtureTearDown]: Ngược với TestFixtureSetup.
- Vậy để apply NUnit Test thì công việc vô cùng đơn giản: tạo một project class library, thêm
reference đến dll nunit.framework, thêm 1 class mới, khai báo nó thành public, thêm using
nunit.framework, thêm attribute [TestFixture] vào đầu của class, viết một method test và khai
báo với attribute [Test]. Cơ bản như vậy là đủ để test, bạn có thể kết hợp nhiều attribute khác
cũng như nguyên tắc Inheritance của lập trình hướng đối tượng để có một project test uyển
chuyển. Người ta thường sử dụng [Setup] để mở một transaction scope, sau đó dùng [TearDown]
để roll back transaction khi test các Dao, như vậy sẽ không có dữ liệu bị thêm xóa vào database
và bảo đảm dữ liệu test sẽ như nhau trước khi test các method. tóm tắt và bổ sung dựa vào bài
viết NHibernate Best Practices with ASP.NET, 1.2nd Ed trên code Project. Các code sample
trong bài dựa vào database Northwind của Microsoft và tham khảo 99,99% từ code mẫu của tác
giả Billy McCafferty.
IV.1 Tạo dữ liệu test với NUnit
- Trên nguyên tắc, trước khi test bất kì một method test nào thì dữ liệu test phải như nhau. Ví dụ
như bạn muốn test xem một Customer có thể thêm và xóa Order hay không thì trước khi test hàm
AddOrder và DeleteOrder thông tin về Customer cũng như số lượng Order mà Customer đó đang
giữ phải như nhau. Vì vậy người ta thường tạo những lớp Factory chỉ dành riêng để tạo ra dữ liệu
Test nhất quán.
- Dữ liệu test của chúng ta trong trường hợp này là các object Customer, Order và
HistoricalOrderSummary. Thế nên ta sẽ tạo ra các lớp Factory để tạo các List những object này,
các lớp Factory này được đặt trong folder TestFactories bên trong project Test. Ví dụ nội dung
lớp TestCustomerFactory như sau:
Code 3: Lớp Factory để tạo các object làm dữ liệu test
IV.2 Tạo các Mock Factory và Stub objects
- Nếu các bạn còn nhớ thì trong project EnterpriseSample.Core, ta đã khai báo các Interface
DAO, các lớp Domain như Customer, Order sẽ reference đến những Interface này. Còn
implementation thực sự của các interface Dao để truy xuất database được đặt ở project
EnterpriseSample.Data. Như vậy khi test project EnterpriseSample.Core, người ta thường sử
dụng các kĩ thuật Mock hoặc tạo một class implement các Interface này để test. Các Mock hay
Stub này sẽ là cascadeur cho các lớp Dao khi ta test EnterpriseSample.Core. Đoạn code dưới đây
sử dụng RhinoMock để tạo ra một mock object kiểu ICustomerDao, đóng thể cho CustomerDao:
Code 4: Sử dụng Rhino Mock để tạo một Mocked Dao object
- Anh Nguyễn Văn Chuối thuyết minh đoạn code trên như thế này: tui dùng MockRepository tạo
ra một mock object thuộc kiểu ICustomerDao, đặt tên nó là mockedCustomerDao rồi nói với nó
là: "lỡ ai có biểu mày lại hỏi mày có biết GetAll hay không thì mày trả lời là biết và đưa cho
người ta danh sách Customer của thằng TestCustomersFactory. Còn ai hỏi mày biết GetById
không thì cũng trả lời như vậy nghe chưa!". Cuối cùng tui dùng MockRepository để ghi nhớ
thằng mock Object vừa được dặn dò kĩ lưỡng, bất cứ ai hỏi đển thằng mocked object này tui sẽ
biểu nó ra nói chuyện.
- Thực ra trong bài viết này tác giả Billy McCafferty có thể sử dụng kĩ thuật Mock là đủ, nhưng
theo tui nghĩ bác Billy McCafferty muốn cho chúng ta thấy có những cách khác mà không cần
dùng Mock, vì thế nên có sự xuất hiện của lớp OrderDaoStub:
Code 5: Ví dụ một lớp Dao Stub dùng để test
- Khi implement 1 interface, buộc lòng chúng ta phải implement tất cả những gì được khai báo
trong interface đó nên các bạn thấy rằng lớp Stub này phải khai báo rất nhiều hàm trong khi
chúng ta chỉ muốn fake hàm GetByExample. Vì vậy dân đen như tụi mình cư dùng các kĩ thuật
Mock cho lành.
IV.3 Test Các Domain classes
- Trên nguyên tắc, tất cả các dòng code của bạn viết phải được test qua có nghĩa là từng
constructor, từng putblic setter, getter đều nên được test. Nhưng đối với những người có máu
lười như tôi thì có thể bỏ qua một số thứ. Các lớp để test các domain class được đặt trong folder
Domain bên trong project Test. Nếu bạn có 10 lớp Domain trong chương trình hãy viết 10 lớp
test tương ứng ví dụ như sau:
Code 6: Viết Unit Test cho các Domain Classes
IV. 4 Test Nhibernate Dao
- Trong phần 3 này chúng ta hãy cứ tiếp tục chấp nhận điều sau: Khi một Dao cần truy xuất
database, nó sẽ cần một Nhibernate Session để làm chuyện đó. Nó sẽ lấy Session này ở đâu? Nó
sẽ lấy Session nhờ vào lớp NhibernateSessionManager và kết hợp với một giá trị string chứa
đường dẫn của một file config chứa các setting cần thiết như Connection String đến database
thực. Và đường dẫn này được hard code như là một static property của lớp TestGlobals.cs. Để
tiếp tục, yêu cầu các bạn đang sử dụng db server SQL Express 2005 và đã có database
Northwind. Nếu chưa có các bạn có thể download ở đây rồi attach Northwnd.MDF vào db
server.
- Các lớp Nhibernate Dao là những lớp trực tiếp truy xuất database và chúng ta chuẩn bị test nó.
Để test các lớp Dao này chúng ta cần một database thực sư và chúng ta đã chuẩn bị như đã nói ở
trên. Xin nhắc lại một lần nữa là trên nguyên tắc, các hàm test nên không ảnh hưởng đến kết quả
test của những hàm test khác, điều này có nghĩa là dữ liệu trước và sau khi thực hiện một hàm
test là nhất quán. Để đạt được mục đích này, chúng ta tạo một lớp NhibernateTestCase, các lớp
test case khác sẽ inherit từ lớp này. Trước khi tìm hiểu tại sao làm vậy, hãy xem implementation
của nó:
Code 7: Lớp Test base
- Vậy bất kì lớp test nào inherit từ lớp này sẽ kế thừa được TestFixtureSetup và
TestFixtureTearDown của nó. Có nghĩa là trước khi một lớp test được thực thi, NHIbernate
Session Manager sẽ mở một transaction và rollback ngay sau khi test xong, nhờ thế dữ liệu test
sẽ không bao giờ bị thay đổi. Còn bây giờ là nội dung một lớp Dao Test:
Code 8: Lớp Test NHibernate Dao
- Trong phần 2, chúng ta đã có một lớp Generic Dao giúp tiết kiệm code cho rất nhiều Dao
Object khác nhau. Điều này dẫn đến việc là lớp Dao nào nên được test và lớp nào không? Để trả
lời câu hỏi này, tác giả đã đưa ra các kinh nghiệm của mình khi viết Test Class:
+ Phải thực hiện test mọi method của Generic Dao. Nếu như bạn có 10 lớp Daos inherit generic
Dao này thì chỉ một lớp bất kì trong số các lớp Daos này được test là đủ.
+ Phải test tất cả các method phụ của mỗi Dao nếu bạn có implement thêm.
+ Nếu có một lớp Dao nào không inherit từ Generic Dao như lớp HistoricalOrderSummaryDao
thì lớp đó phải được test.
+ Phải chắc chắn dữ liệu test nhất quán trước và sau khi một Dao unit test được gọi và các unit
test phải độc lập với nhau.
V. Tóm tắt & Kết luận
- Trong phần 3 này ta đã làm quen với Unit Testing, các tool và framework phụ trợ, ta cũng đã
tìm hiểu qua công dụng và ý nghĩa của từng lớp, từng folder bên trong một project Test. Cách tổ
chức lớp cũng như cách tác giả viết Unit Test rất tốt để tham khảo. Bản thân tôi cũng có viết Unit
Test nhưng sau khi xem bài viết của Billy McCafferty thì đã quyết định từ nay về sau nếu có viết
test sẽ theo cách làm của bác Billy.
- Viết Unit Test tuy không bắt buộc nhưng nó đóng vai trò quan trọng trong qúa trình làm phần
mềm. Đối với một số khách hàng lớn họ có thể yêu cầu chúng ta viết Unit Test và phải thoả mãn
cover 80% code chẳng hạn. Unit Test không hẳn chỉ để test chương trình, ta có thể sử dụng nó
như là một công cụ hỗ trợ debug nhanh khi implement một chức năng nào đó khá phức tạp. Kết
hợp với một số kĩ thuật Mock, ta có thể test ngay một module của chương trình khi chưa có hoặc
chưa hoàn thành xong các module khác…
- Chắc hẳn chúng ta vẫn còn nhiều thắc mắc đối với cách hoạt động của lớp
NhibernateSessionManager. Lớp này thực sự có công dụng gì và được tổ chức thế nào? Hãy chờ
hồi sau sẽ rõ

Weitere ähnliche Inhalte

Was ist angesagt?

Kiem thu phan mem
Kiem thu phan memKiem thu phan mem
Kiem thu phan memTIen Le
 
CONG NGHE PHAN MEM
CONG NGHE PHAN MEMCONG NGHE PHAN MEM
CONG NGHE PHAN MEMduc phong
 
Đồ án tốt nghiệp Xây dựng ứng dụng fastfood trên nền android
Đồ án tốt nghiệp Xây dựng ứng dụng fastfood trên nền androidĐồ án tốt nghiệp Xây dựng ứng dụng fastfood trên nền android
Đồ án tốt nghiệp Xây dựng ứng dụng fastfood trên nền androidlaonap166
 
De thi qlda cntt itc vdc trac nghiem 05-2006
De thi qlda cntt itc vdc trac nghiem 05-2006De thi qlda cntt itc vdc trac nghiem 05-2006
De thi qlda cntt itc vdc trac nghiem 05-2006Tran Tien
 
[Đồ án môn học] - Đề tài: Tìm hiểu Git và Github
[Đồ án môn học] - Đề tài: Tìm hiểu Git và Github[Đồ án môn học] - Đề tài: Tìm hiểu Git và Github
[Đồ án môn học] - Đề tài: Tìm hiểu Git và GithubMatt Colonel
 
Đồ án kiểm thử phần mềm
Đồ án kiểm thử phần mềmĐồ án kiểm thử phần mềm
Đồ án kiểm thử phần mềmNguyễn Anh
 
TÌM HIỂU CÁC KỸ THUẬT KIỂM THỬ PHẦN MỀM
TÌM HIỂU CÁC KỸ THUẬT KIỂM THỬ PHẦN MỀMTÌM HIỂU CÁC KỸ THUẬT KIỂM THỬ PHẦN MỀM
TÌM HIỂU CÁC KỸ THUẬT KIỂM THỬ PHẦN MỀMNguyễn Anh
 
Giới thiệu về Rational Rose và Các diagram
Giới thiệu về Rational Rose và Các diagramGiới thiệu về Rational Rose và Các diagram
Giới thiệu về Rational Rose và Các diagramHuy Vũ
 
Xây dựng hệ thống quản lý sân bóng sử dụng Yii Framework
Xây dựng hệ thống quản lý sân bóng sử dụng Yii FrameworkXây dựng hệ thống quản lý sân bóng sử dụng Yii Framework
Xây dựng hệ thống quản lý sân bóng sử dụng Yii FrameworkGMO-Z.com Vietnam Lab Center
 
Tìm hiểu các kỹ thuật kiểm thử phần mềm ứng dụng trong lập trình Java.
Tìm hiểu các kỹ thuật kiểm thử phần mềm  ứng dụng trong lập trình Java.Tìm hiểu các kỹ thuật kiểm thử phần mềm  ứng dụng trong lập trình Java.
Tìm hiểu các kỹ thuật kiểm thử phần mềm ứng dụng trong lập trình Java.Nguyễn Anh
 
Bài 1: Một số khái niệm cơ bản
Bài 1: Một số khái niệm cơ bảnBài 1: Một số khái niệm cơ bản
Bài 1: Một số khái niệm cơ bảnChâu Trần
 
Visual basic 6 ly thuyet
Visual basic 6 ly thuyetVisual basic 6 ly thuyet
Visual basic 6 ly thuyetphongchitien
 
Phân tích thiết kế hệ thống quản lý bán nước giải khát
Phân tích thiết kế hệ thống quản lý bán nước giải khátPhân tích thiết kế hệ thống quản lý bán nước giải khát
Phân tích thiết kế hệ thống quản lý bán nước giải khátMinh Nguyển
 

Was ist angesagt? (20)

Kiem thu phan mem
Kiem thu phan memKiem thu phan mem
Kiem thu phan mem
 
CONG NGHE PHAN MEM
CONG NGHE PHAN MEMCONG NGHE PHAN MEM
CONG NGHE PHAN MEM
 
Kiem thu
Kiem thuKiem thu
Kiem thu
 
Đồ án tốt nghiệp Xây dựng ứng dụng fastfood trên nền android
Đồ án tốt nghiệp Xây dựng ứng dụng fastfood trên nền androidĐồ án tốt nghiệp Xây dựng ứng dụng fastfood trên nền android
Đồ án tốt nghiệp Xây dựng ứng dụng fastfood trên nền android
 
De thi qlda cntt itc vdc trac nghiem 05-2006
De thi qlda cntt itc vdc trac nghiem 05-2006De thi qlda cntt itc vdc trac nghiem 05-2006
De thi qlda cntt itc vdc trac nghiem 05-2006
 
Chuong 2. cnpm
Chuong 2. cnpmChuong 2. cnpm
Chuong 2. cnpm
 
[Đồ án môn học] - Đề tài: Tìm hiểu Git và Github
[Đồ án môn học] - Đề tài: Tìm hiểu Git và Github[Đồ án môn học] - Đề tài: Tìm hiểu Git và Github
[Đồ án môn học] - Đề tài: Tìm hiểu Git và Github
 
Đồ án kiểm thử phần mềm
Đồ án kiểm thử phần mềmĐồ án kiểm thử phần mềm
Đồ án kiểm thử phần mềm
 
TÌM HIỂU CÁC KỸ THUẬT KIỂM THỬ PHẦN MỀM
TÌM HIỂU CÁC KỸ THUẬT KIỂM THỬ PHẦN MỀMTÌM HIỂU CÁC KỸ THUẬT KIỂM THỬ PHẦN MỀM
TÌM HIỂU CÁC KỸ THUẬT KIỂM THỬ PHẦN MỀM
 
Giới thiệu về Rational Rose và Các diagram
Giới thiệu về Rational Rose và Các diagramGiới thiệu về Rational Rose và Các diagram
Giới thiệu về Rational Rose và Các diagram
 
Jmeter tool
Jmeter toolJmeter tool
Jmeter tool
 
Xây dựng hệ thống quản lý sân bóng sử dụng Yii Framework
Xây dựng hệ thống quản lý sân bóng sử dụng Yii FrameworkXây dựng hệ thống quản lý sân bóng sử dụng Yii Framework
Xây dựng hệ thống quản lý sân bóng sử dụng Yii Framework
 
Test plan
Test planTest plan
Test plan
 
Tìm hiểu các kỹ thuật kiểm thử phần mềm ứng dụng trong lập trình Java.
Tìm hiểu các kỹ thuật kiểm thử phần mềm  ứng dụng trong lập trình Java.Tìm hiểu các kỹ thuật kiểm thử phần mềm  ứng dụng trong lập trình Java.
Tìm hiểu các kỹ thuật kiểm thử phần mềm ứng dụng trong lập trình Java.
 
Bài 1: Một số khái niệm cơ bản
Bài 1: Một số khái niệm cơ bảnBài 1: Một số khái niệm cơ bản
Bài 1: Một số khái niệm cơ bản
 
Visual basic 6 ly thuyet
Visual basic 6 ly thuyetVisual basic 6 ly thuyet
Visual basic 6 ly thuyet
 
Phân tích thiết kế hệ thống quản lý bán nước giải khát
Phân tích thiết kế hệ thống quản lý bán nước giải khátPhân tích thiết kế hệ thống quản lý bán nước giải khát
Phân tích thiết kế hệ thống quản lý bán nước giải khát
 
Đề tài: Quản lí Tour du lịch, HAY, 9đ
Đề tài: Quản lí Tour du lịch, HAY, 9đĐề tài: Quản lí Tour du lịch, HAY, 9đ
Đề tài: Quản lí Tour du lịch, HAY, 9đ
 
Đề tài: Quản lí kho, HAY
Đề tài: Quản lí kho, HAYĐề tài: Quản lí kho, HAY
Đề tài: Quản lí kho, HAY
 
Đề tài: Chương trình quản lý cho thuê nhà của cơ sở dịch vụ, HOT
Đề tài: Chương trình quản lý cho thuê nhà của cơ sở dịch vụ, HOTĐề tài: Chương trình quản lý cho thuê nhà của cơ sở dịch vụ, HOT
Đề tài: Chương trình quản lý cho thuê nhà của cơ sở dịch vụ, HOT
 

Ähnlich wie Bai tap testing junit…..

Test Driven development
Test Driven developmentTest Driven development
Test Driven developmentMU VN
 
Nunit framework for .NET application
Nunit framework for .NET applicationNunit framework for .NET application
Nunit framework for .NET applicationMinh Tri Lam
 
Unit Test with test JUNIT
Unit Test with test JUNIT Unit Test with test JUNIT
Unit Test with test JUNIT Cusanlui
 
Huong dan su_dung_cong_cu_hot_potatoes
Huong dan su_dung_cong_cu_hot_potatoesHuong dan su_dung_cong_cu_hot_potatoes
Huong dan su_dung_cong_cu_hot_potatoesnickaopccc
 
Android Nâng cao-Bài 6-Multi theme-adb tool-jUnit
Android Nâng cao-Bài 6-Multi theme-adb tool-jUnitAndroid Nâng cao-Bài 6-Multi theme-adb tool-jUnit
Android Nâng cao-Bài 6-Multi theme-adb tool-jUnitPhuoc Nguyen
 
Tai lieu huong_dan_tu_hoc_visual_studio2010
Tai lieu huong_dan_tu_hoc_visual_studio2010Tai lieu huong_dan_tu_hoc_visual_studio2010
Tai lieu huong_dan_tu_hoc_visual_studio2010Nhat Linh Luong
 
Selenium ide
Selenium ide Selenium ide
Selenium ide Pé Vịt
 
Phần mềm chấm thi trăc nghiệm VTS
Phần mềm chấm thi trăc nghiệm VTSPhần mềm chấm thi trăc nghiệm VTS
Phần mềm chấm thi trăc nghiệm VTSNguyen Ngoc
 
BÀI 4: Lệnh lựa chọn và quy trình phát triển phần mềm - Giáo trình FPT
BÀI 4: Lệnh lựa chọn và quy trình phát triển phần mềm - Giáo trình FPTBÀI 4: Lệnh lựa chọn và quy trình phát triển phần mềm - Giáo trình FPT
BÀI 4: Lệnh lựa chọn và quy trình phát triển phần mềm - Giáo trình FPTMasterCode.vn
 
6 câu hỏi phỏng vấn tester thông dụng năm 2021
6 câu hỏi phỏng vấn tester thông dụng năm 20216 câu hỏi phỏng vấn tester thông dụng năm 2021
6 câu hỏi phỏng vấn tester thông dụng năm 2021MDuyn83
 
Bài 18 tin hoc 11
Bài 18 tin hoc 11Bài 18 tin hoc 11
Bài 18 tin hoc 11HaBaoChau
 
Chuan viet code va thiet ke giao dien trong C#
Chuan viet code va thiet ke giao dien trong C#Chuan viet code va thiet ke giao dien trong C#
Chuan viet code va thiet ke giao dien trong C#Kuli An
 

Ähnlich wie Bai tap testing junit….. (20)

Unit Test
Unit TestUnit Test
Unit Test
 
Test Driven development
Test Driven developmentTest Driven development
Test Driven development
 
Nunit framework for .NET application
Nunit framework for .NET applicationNunit framework for .NET application
Nunit framework for .NET application
 
Adp junit
Adp junitAdp junit
Adp junit
 
Unit Test with test JUNIT
Unit Test with test JUNIT Unit Test with test JUNIT
Unit Test with test JUNIT
 
Bài 6
Bài 6Bài 6
Bài 6
 
Huong dan su_dung_cong_cu_hot_potatoes
Huong dan su_dung_cong_cu_hot_potatoesHuong dan su_dung_cong_cu_hot_potatoes
Huong dan su_dung_cong_cu_hot_potatoes
 
Android Nâng cao-Bài 6-Multi theme-adb tool-jUnit
Android Nâng cao-Bài 6-Multi theme-adb tool-jUnitAndroid Nâng cao-Bài 6-Multi theme-adb tool-jUnit
Android Nâng cao-Bài 6-Multi theme-adb tool-jUnit
 
Tai lieu huong_dan_tu_hoc_visual_studio2010
Tai lieu huong_dan_tu_hoc_visual_studio2010Tai lieu huong_dan_tu_hoc_visual_studio2010
Tai lieu huong_dan_tu_hoc_visual_studio2010
 
Selenium ide
Selenium ide Selenium ide
Selenium ide
 
Phần mềm chấm thi trăc nghiệm VTS
Phần mềm chấm thi trăc nghiệm VTSPhần mềm chấm thi trăc nghiệm VTS
Phần mềm chấm thi trăc nghiệm VTS
 
BÀI 4: Lệnh lựa chọn và quy trình phát triển phần mềm - Giáo trình FPT
BÀI 4: Lệnh lựa chọn và quy trình phát triển phần mềm - Giáo trình FPTBÀI 4: Lệnh lựa chọn và quy trình phát triển phần mềm - Giáo trình FPT
BÀI 4: Lệnh lựa chọn và quy trình phát triển phần mềm - Giáo trình FPT
 
6 câu hỏi phỏng vấn tester thông dụng năm 2021
6 câu hỏi phỏng vấn tester thông dụng năm 20216 câu hỏi phỏng vấn tester thông dụng năm 2021
6 câu hỏi phỏng vấn tester thông dụng năm 2021
 
Bài 18 tin hoc 11
Bài 18 tin hoc 11Bài 18 tin hoc 11
Bài 18 tin hoc 11
 
Bai tap thuc hanh
Bai tap thuc hanhBai tap thuc hanh
Bai tap thuc hanh
 
Ktlt lab full
Ktlt lab fullKtlt lab full
Ktlt lab full
 
Chuan viet code va thiet ke giao dien trong C#
Chuan viet code va thiet ke giao dien trong C#Chuan viet code va thiet ke giao dien trong C#
Chuan viet code va thiet ke giao dien trong C#
 
63 2601
63 260163 2601
63 2601
 
Giáo trình visual basic
Giáo trình visual basicGiáo trình visual basic
Giáo trình visual basic
 
Cơ bản về visual basic
Cơ bản về visual basicCơ bản về visual basic
Cơ bản về visual basic
 

Bai tap testing junit…..

  • 1. I. Bai tap Testing Junit….. 16/10/2009 · Tập tin được lưu ở: Uncategorized Đề Tài: Những người thực hiện: + Trần Đình Lương. MSSV + Nguyễn Ngọc Tuyền. + Hồ Thanh Bình. —————******—————- I. Giới thiệu JUnit JUnit là một framework đơn giản dùng cho việc tạo các unit testing tự động, và chạy các test có thể lặp đi lặp lại. Nó chỉ là một phần của họ kiến trúc xUnit cho việc tạo các unit testing. JUnit là một chuẩn trên thực tế cho unit testing trong Java. JUnit về nguồn gốc được viết bởi 2 tác giả Erich Gamma và Kent Beck 1 Giới thiệu: JUnit có những đặc điểm đáng lưu tâm như sau: • Xác nhận (assert) việc kiểm tra kết quả được mong đợi • Các Test Suite cho phép chúng ta dễ dàng tổ chức và chạy các test • Hỗ trợ giao diện đồ họa và giao diện dòng lệnh Các test case của JUnit là các lớp của Java, các lớp này bao gồm một hay nhiều các phương thức unit testing, và những test này lại được nhóm thành các Test Suite. Mỗi phương thức test trong JUnit phải được thực thi nhanh chóng. Tốc độ là điều tối quan trọng vì càng nhiều test được viết và tích hợp vào bên trong quá trình xây dựng phần mềm, cần phải tốn nhiều thời gian hơn cho việc chạy toàn bộ Test Suite. Các lập trình viên không muốn bị ngắt quãng trong một khoãng thời gian dài trong khi các test chạy, vì thế các test mà chạy càng lâu thì sẽ có nhiều khả năng là các lập trình viên sẽ bỏ qua bước cũng không kém phần quan trọng này. Các test trong JUnit có thể là các test được chấp nhận hay thất bại, các test này được thiết kế để khi chạy mà không cần có sự can thiệp của con người. Từ những thiết kế như thế, bạn có thể thêm các bộ test vào quá trình tích hợp và xây dựng phần mềm một cách liên tục và để cho các test chạy một cách tự động 1. Đặt vấn đề 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.
  • 2. 2. Lợi ích của JUnit 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ợ. 3. 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: public class Money { private double amount; private String currency; public Money(double amount, String currency) { this.amount = amount; this.currency = currency; } } 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) Do đó sẽ phải hiệu chỉnh class Money để giải quyết vấn đề đặt ra: public class Money { // Phần tương tự như trong Hình 1 đã được lược bớt public Money add(Money money) { return new Money(this.amount + money.amount, this.currency); } } import junit.framework.Assert; import junit.framework.TestCase;
  • 3. public class MoneyTest extends TestCase { public void testAdd() { Money m1 = new Money(200, “VND”); Money m2 = new Money(1000, “VND”); Money result = m1.add(m2); // đối tượng lưu kết quả tính toán Money expected = new Money(1200, “VND”); // kết quả dự kiến Assert.assertTrue(result.equals(expected)); // lệnh kiểm thử } } Phần kiểm thử có bốn điểm đáng chú ý sau: 1. Phương thức kiểm thử cần được đặt tên bắt đầu bằng từ test 2. Đối tượng lưu kết quả tính toán, 3. Đối tượng lưu kết quả dự kiến, và 4. Kiểm chứng sự trùng khớp giữa kết quả tính toán và kết quả dự kiến. Ở đâ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 (primitive), 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 (đoạn code dưới). public class Money { // Phần tương tự như trong Hình 2 đã được lược bớt public boolean equals(Object object) { if (object instanceof Money) { Money money = (Money)object; return this.amount == money.amount && this.currency == money.currency; } return false; } }
  • 4. 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() Ở đoạn code trên 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. 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 import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class MoneyBag { private List monies = new ArrayList(); public String toString() { String s = “{ “; Iterator iter = monies.iterator(); while (iter.hasNext()) s += iter.next() + ” “; return s + “}”; } public void add(Money money) { for (int i = 0; i < monies.size(); i++) { Money m = (Money)monies.get(i); if (money.getCurrency().equals(m.getCurrency())) { monies.set(i, m.add(money)); return; } } // money is a new currency monies.add(money);
  • 5. } } import junit.framework.Assert; import junit.framework.TestCase; public class MoneyBagTest extends TestCase { public void testAddWithToString() { MoneyBag bag = new MoneyBag(); Assert.assertEquals(bag.toString(), "{ }"); bag.add(new Money(1200, "VND")); Assert.assertEquals(bag.toString(), "{ [1200 VND] }"); bag.add(new Money(10, "USD")); Assert.assertEquals(bag.toString(), "{ [1200 VND] [10 USD] }"); } } Khi thi hành JUnit Test, ta sẽ gặp tín hiệu “đỏ”. Bằng việc chèn lệnh System.out.println() vào phương thức testAddWithToString(), ta sẽ phát hiện ra kết quả phải là { [1200.0 VND] }, thay vì { [1200 VND] }. Từ đó ta biết rằng cần phải hiệu chỉnh kết quả để được tín hiệu “xanh”. Điều này một lần nữa cho thấy điểm tế nhị khi phải thực hiện việc so sánh trên các số thực. Sau đây là câu hỏi dành cho bạn đọc trước khi 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. 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(). 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. public class MoneyBag { // Phần tương tự như trong Hình 7 đã được lược bớt private boolean contains(Money money) { Iterator iter = monies.iterator(); while (iter.hasNext()) {
  • 6. if (money.equals((Money)iter.next())) return true; } return false; } public boolean equals(Object object) { if (object instanceof MoneyBag) { MoneyBag bag = (MoneyBag)object; if (monies.size() != bag.monies.size()) return false; Iterator iter = bag.monies.iterator(); while (iter.hasNext()) { Money m = (Money)iter.next(); if (!monies.contains(m)) return false; } return true; } return false; } public static void main(String[] args) { MoneyBag bag = new MoneyBag(); Money m1 = new Money(1200, "VND"); Money m2 = new Money(10, "USD"); 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)); } } public class MoneyBagTest extends TestCase { public void testAddWithToString() { ………………. } public void testAddWithEquals() { MoneyBag bag1 = new MoneyBag(); MoneyBag bag2 = new MoneyBag(); Assert.assertTrue(bag1.equals(bag2));
  • 7. bag1.add(new Money(1200, "VND")); Assert.assertFalse(bag1.equals(bag2)); bag1.add(new Money(10, "USD")); bag2.add(new Money(10, "USD")); bag2.add(new Money(1200, "VND")); Assert.assertTrue(bag1.equals(bag2)); } } Để 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). (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; public class AllTests { public static Test suite() { TestSuite suite = new TestSuite(“Test for Money”); suite.addTest(new TestSuite(MoneyTest.class)); suite.addTest(new TestSuite(MoneyBagTest.class)); return suite; } } II. Giới thiệu Unit Testing - Xin mở đầu bằng một tình huống thế này: Anh Chuột được assign một task là viết một hàm kiểm tra tính hợp lệ cho dữ liệu nhập vào một text box. Dữ liệu này là một chuỗi các chữ số và dấu chấm của một số kiểu double có giá trị lớn hơn hoặc bằng 0. Giá trị chuỗi chỉ được chứa tối đa 1 chữ số sau dấu chấm. Chuỗi số này có thể là giá trị của số nguyên tức là không có dấu chấm nào cả, và cuối cùng giá trị của số nhập vào phải nằm trong khoảng 0 đến 100. Với nhiều điều kiện ràng buộc như vậy anh Chuối quyết định áp dụng Test Driven Development kết hợp với Unit Test để thực hiện và Anh Chuột đã viết một hàm test thể hiện mọi yêu cầu như sau: Code 1: Ví dụ về một hàm test - Tất nhiên sau khi viết hàm Test, Anh Chuột sẽ bắt tay vào implement class NumberChecker để cái test này pass. Anh Chuột cho rằng nên kết hợp Regular Expression và các hàm Parse của kiểu double là nhanh nhất , do đó Anh Chuột đã làm như sau: Code 2: Implement lớp checknumber để Unit Test pass - Lúc đó, trong team của Anh Chuột có chị Bưởi là một QC khét tiếng khó chịu, chị Bưởi này có tật là test và soi mói chương trình rất kĩ và đã phát hiện ra rất nhiều bug hiểm hóc mà một
  • 8. developer chân chính như Anh Chuột không ngờ tới. Sau khi test, chị Bưởi phát hiện ra rằng nếu người ta nhập vào 000 thì chương trinh vẫn cho nhập, ngược lại khi nhập +100 thì chương trình báo lỗi không hợp lệ. Phát hiện được bug này chị Bưởi rất vui và assign liền 1 bug cho anh Chuối với status là critical. - Rõ ràng là Anh Chuột đã tính toán thiếu một số trường hợp. Hàm test sẽ được thêm vào một số dòng code để check các trường hợp chị Bưởi liệt kê, đồng thời regular expression của Anh Chuột phải được sửa lại cho đúng. - Trong ngành phần mềm, thuật ngữ Unit Testing là một phương pháp dùng để kiểm tra tính đúng đắn của một đơn vị source code. Một Unit (đơn vị) source code là phần nhỏ nhất có thể test được của chương trình. Trong lập trình thủ tục, một unit có thể là cả chương trình, một function hay một procedure. Còn trong lập trình hướng đối tượng, đơn vị nhỏ nhất có lẽ là một method của một class nào đó. - Điều kiện lý tưởng nhất là mỗt test case phải độc lập với những test case khác. Người ta có thể dùng nhiều kĩ thuật như stubs, mock hoặc fake objects, … để phục vụ việc test các module trong chương trình. Viết Unit test là trách nhiệm, nghĩa vụ và quyền lợi của các lập trình viên, lập trình viên chúng ta nên sử dụng Unit Test để bảo đảm những gì mình viết chạy đúng như yêu cầu phần mềm, và nhất là đúng với cách mình hiểu. II.1 Lợi ích của Unit Test - Nhiều . Mục đích của Unit Test là cô lập từng phần của chương trình và đảm bảo những phần đó chạy đúng như yêu cầu. Unit test giúp bảo đảm tính chính xác của chương trình, nó giúp thiết lập những ràng buộc và những phần code của chúng ta phải thực hiện chính xác những ràng buộc đó. Kết quả là Unit Test đem lại rất nhiều lợi ích, nhưng rõ ràng nhất là nó giúp phát hiện lỗi và những vấn đề liên quan ngay từ những phase đầu tiên của quá trình phát triển phần mềm. II.2 Unit Test giúp cho việc sửa đổi dễ dàng hơn - Trên lý thuyết, Unit Test cho phép lập trình viên refactor code và bảo đảm những gì anh ta viết vẫn chạy đúng sau khi code bị thay đổi. Để làm được điều này, người ta buộc phải viết các test case cho tất cả các function và methods và do đó bất cứ một thay đổi nào làm chương trình chạy sai sẽ bị phát hiện kịp thời và buộc người gây lỗi phải fix ngay. Còn trong thực tế để những test case của bạn cover hết toàn bộ những trường hợp trong chương trình lại là một vấn đề khác. - Nếu như team của bạn có sử dụng những hệ thống build tự động như CruiseControl.NET có sử dụng Nunit test thì mỗi lần commit code gây lỗi sẽ dễ dàng phát hiện thủ phạm như hình dưới đây: Hình 1: Giao diện Report của CruiseControl.NET II.3 Unit test giúp tính hợp code dễ hơn - Bạn làm việc trong một team, mỗi người làm một phần của chương trình và mỗi phần bạn viết đều đã apply unit test kĩ càng. Đến khi kết hợp những thành phần của team với nhau, quá trình đó nói chung sẽ rất xuông sẽ và ít lỗi hơn nhiều so với việc mạnh ai nấy code rồi cuối cùng
  • 9. merge lại với nhau. II.4 Document và Design - Mỗi một test case bạn viết có thể được xem như API document cho chính method được test. Một team member vào sau bạn có thể dự vào test case đó để hiểu hàm này công dụng là gì, input thế nào và output ra sao. - Trong quá trình phát triển phần mềm, document của chương trình bao gồm các design, requirement có thể bị bỏ quên và trở nên “out of date” nhưng những Unit Test Cases sẽ luôn chính xác những gì chương trình thực hiện và vì vậy ở một khía cạnh nào đó, Unit Test có thể được xem như một dạng document của chương trình. II.5 Những hạn chế của Unit Test - Người ta khó có thể viết Unit Test để bắt tất cả các lỗi của 1 chương trình. Thêm vào đó, những test case ta viết chỉ kiểm lỗi những unit nhỏ nhất của chương trình do đó không thể nào lường trước những vấn đề có thể xảy ra khi kết hợp các module với nhau. Unit testing sẽ thể hiện được hiệu quả rõ nhất khi kết hợp nó với những kĩ thuật test khác và tất nhiên sẽ cần tới sức người. Unit Testing không thể nào thay thế được QC – Tester và cũng như nhiều kiểu test khác, nó chỉ có thể kiểm tra được những lỗi đã biết chứ không thể sử dụng nó để tìm ra các lỗi tiềm ẩn của chương trình. - Software testing là một tổ hợp của nhiều trường hợp. Ví dụ như để kiểm tra một hàm trả về kiểu boolean, tức là có hai trường hợp trả về chúng ta thường phải viết ít nhất hai dòng code để test lần gọi hàm đó. Anh Nguyễn Văn Chuối rất thường viết những hàm dài cả trăm dòng code với nhiều if / else, làm sao bảo đảm rằng anh Chuối có thể viết một test case có thể cover hết những trường hợp có thể xảy ra. Trong trường hợp đó anh Chuối có thể refactor code để chia nhỏ thân hàm thành nhiều hàm nhỏ hơn rồi từ đó test các hàm nhỏ đó. Nếu team của bạn có sử dụng một Continuous Enviroment với NCoverExplorer thì sẽ dễ dàng phát hiện test case của bạn cover bao nhiêu % chương trình: Hình 2: Giao diện Report của NCover trong dashboard của CruiseControl.NET - Có nhiều trường hợp khác chúng ta không thể nào sử dụng Unit Test, chẳng hạn như không thế test private class, private method, … nên nói chung Unit Test là một công cụ hỗ trợ chứ không thể thay thế các kĩ thuật test đang được nhiều người sử dụng. III. Một số tool và framework hỗ trợ Unit Testing III.1 NUnit: Là một unit-testing framework cho ngôn ngữ lập trình .NET được port từ Junit. NUnit có hai dạng là console và GUI. Thực sự thì NUnit thường được sử dụng kết hợp với CruiseControl.NET và dùng để test tự động trên build server, và lập trình viên bình thường cũng không cần download về máy làm gì. III.2 TestDriven.NET: Là một trong những tool không thể thiếu đối với dân .NET. Khi install vào máy, nó sẽ tích hợp một menu vào Visual Studio.NET và cho phép chúng test, debug các class/method rất tiện lợi,
  • 10. ngoài ra ta có thể sử dụng assembly nunit.framework trong thư mục cài đặt của TestDriven.NET để sử dụng cho project test. Hình 3: Menu run test khi cài TestDriven.NET III.3 NCover, NCoverExplorer: Như giới thiệu ở trên, các tool này giúp chúng ta kiểm soát mức độ cover của các test case đối với source code, và cũng giống như NUnit, chúng thường được kết hợp với CruiseControl.NET để report sau khi source code được build tự động. III.4 NMock, NMock2, Rhino Mock và TypeMock - Các tool trên giúp chúng ta giả lập một object để test một component của chương trình khi mà component này có reference đến một component khác. Chúng ta sẽ sử dụng các kĩ thuật mock này để test project EnterpriseSample.Core bằng cách giả lập các object kiểu IxxxDao mà không cần đến EnterpriseSample.Data. Cách sử dụng các tool trên tương đối giống nhau và sẽ được ví dụ bằng Rhino Mock trong phần dưới đây, thông dụng nhất có lẽ là Rhino Mock và Type Mock IV. Tạo project test sử dụng NUnit và Rhino Mock Bài viết này được dịch,- Người ta thường tạo một project dạng class library dành cho các test class. Project này theo đúng tên gọi của nó chỉ có ý nghĩa để test và không có vai trò gì trong sản phầm phần mềm cuối cùng. Thực ra NUnit có thể test bất kí test class nào bên trong một assembly bất kì nên project test có thể là Console application, window application, v.v nhưng thông thường người ta sẽ chọn project loại class library. Có một lưu ý là test class của bạn phải được khai báo public, test method cũng thế. Khi sử dụng NUnit.Framework, các bạn sẽ phải làm quen với những Attribute như [TestFixture], [Test], [Setup], [TearDown], … xin được giải thích ngắn gọn những Attribute thường được sử dụng nhất như sau: [TestFixture]: Dùng để đánh đấu 1 class là test class, những class khác không có Attribute này sẽ mặc định bị ignore khi NUnit test assembly của bạn. [Test]: Dùng để đánh dấu 1 method là test method, ý nghĩa của nó tương tự như TestFixture nhưng scope ở cấp method. [Setup]: Dùng để đánh dấu 1 method sẽ được gọi trước khi 1 test case được gọi. Nếu trong 1 test class có 10 method test, thì mỗi lần một method test được chạy thì NUnit sẽ chạy method được đánh dấu với Setup trước tiên. [TearDown]: Ngược với Setup, chạy sau mỗi test method. [TestFixtureSetup]: Tương tự như Setup nhưng ở cấp của class, khi 1 test class được test thì method nào được đánh dấu với attribute này sẽ được chạy trước tiên. [TestFixtureTearDown]: Ngược với TestFixtureSetup. - Vậy để apply NUnit Test thì công việc vô cùng đơn giản: tạo một project class library, thêm reference đến dll nunit.framework, thêm 1 class mới, khai báo nó thành public, thêm using nunit.framework, thêm attribute [TestFixture] vào đầu của class, viết một method test và khai báo với attribute [Test]. Cơ bản như vậy là đủ để test, bạn có thể kết hợp nhiều attribute khác cũng như nguyên tắc Inheritance của lập trình hướng đối tượng để có một project test uyển chuyển. Người ta thường sử dụng [Setup] để mở một transaction scope, sau đó dùng [TearDown] để roll back transaction khi test các Dao, như vậy sẽ không có dữ liệu bị thêm xóa vào database và bảo đảm dữ liệu test sẽ như nhau trước khi test các method. tóm tắt và bổ sung dựa vào bài viết NHibernate Best Practices with ASP.NET, 1.2nd Ed trên code Project. Các code sample trong bài dựa vào database Northwind của Microsoft và tham khảo 99,99% từ code mẫu của tác giả Billy McCafferty.
  • 11. IV.1 Tạo dữ liệu test với NUnit - Trên nguyên tắc, trước khi test bất kì một method test nào thì dữ liệu test phải như nhau. Ví dụ như bạn muốn test xem một Customer có thể thêm và xóa Order hay không thì trước khi test hàm AddOrder và DeleteOrder thông tin về Customer cũng như số lượng Order mà Customer đó đang giữ phải như nhau. Vì vậy người ta thường tạo những lớp Factory chỉ dành riêng để tạo ra dữ liệu Test nhất quán. - Dữ liệu test của chúng ta trong trường hợp này là các object Customer, Order và HistoricalOrderSummary. Thế nên ta sẽ tạo ra các lớp Factory để tạo các List những object này, các lớp Factory này được đặt trong folder TestFactories bên trong project Test. Ví dụ nội dung lớp TestCustomerFactory như sau: Code 3: Lớp Factory để tạo các object làm dữ liệu test IV.2 Tạo các Mock Factory và Stub objects - Nếu các bạn còn nhớ thì trong project EnterpriseSample.Core, ta đã khai báo các Interface DAO, các lớp Domain như Customer, Order sẽ reference đến những Interface này. Còn implementation thực sự của các interface Dao để truy xuất database được đặt ở project EnterpriseSample.Data. Như vậy khi test project EnterpriseSample.Core, người ta thường sử dụng các kĩ thuật Mock hoặc tạo một class implement các Interface này để test. Các Mock hay Stub này sẽ là cascadeur cho các lớp Dao khi ta test EnterpriseSample.Core. Đoạn code dưới đây sử dụng RhinoMock để tạo ra một mock object kiểu ICustomerDao, đóng thể cho CustomerDao: Code 4: Sử dụng Rhino Mock để tạo một Mocked Dao object - Anh Nguyễn Văn Chuối thuyết minh đoạn code trên như thế này: tui dùng MockRepository tạo ra một mock object thuộc kiểu ICustomerDao, đặt tên nó là mockedCustomerDao rồi nói với nó là: "lỡ ai có biểu mày lại hỏi mày có biết GetAll hay không thì mày trả lời là biết và đưa cho người ta danh sách Customer của thằng TestCustomersFactory. Còn ai hỏi mày biết GetById không thì cũng trả lời như vậy nghe chưa!". Cuối cùng tui dùng MockRepository để ghi nhớ thằng mock Object vừa được dặn dò kĩ lưỡng, bất cứ ai hỏi đển thằng mocked object này tui sẽ biểu nó ra nói chuyện. - Thực ra trong bài viết này tác giả Billy McCafferty có thể sử dụng kĩ thuật Mock là đủ, nhưng theo tui nghĩ bác Billy McCafferty muốn cho chúng ta thấy có những cách khác mà không cần dùng Mock, vì thế nên có sự xuất hiện của lớp OrderDaoStub: Code 5: Ví dụ một lớp Dao Stub dùng để test - Khi implement 1 interface, buộc lòng chúng ta phải implement tất cả những gì được khai báo trong interface đó nên các bạn thấy rằng lớp Stub này phải khai báo rất nhiều hàm trong khi chúng ta chỉ muốn fake hàm GetByExample. Vì vậy dân đen như tụi mình cư dùng các kĩ thuật Mock cho lành. IV.3 Test Các Domain classes
  • 12. - Trên nguyên tắc, tất cả các dòng code của bạn viết phải được test qua có nghĩa là từng constructor, từng putblic setter, getter đều nên được test. Nhưng đối với những người có máu lười như tôi thì có thể bỏ qua một số thứ. Các lớp để test các domain class được đặt trong folder Domain bên trong project Test. Nếu bạn có 10 lớp Domain trong chương trình hãy viết 10 lớp test tương ứng ví dụ như sau: Code 6: Viết Unit Test cho các Domain Classes IV. 4 Test Nhibernate Dao - Trong phần 3 này chúng ta hãy cứ tiếp tục chấp nhận điều sau: Khi một Dao cần truy xuất database, nó sẽ cần một Nhibernate Session để làm chuyện đó. Nó sẽ lấy Session này ở đâu? Nó sẽ lấy Session nhờ vào lớp NhibernateSessionManager và kết hợp với một giá trị string chứa đường dẫn của một file config chứa các setting cần thiết như Connection String đến database thực. Và đường dẫn này được hard code như là một static property của lớp TestGlobals.cs. Để tiếp tục, yêu cầu các bạn đang sử dụng db server SQL Express 2005 và đã có database Northwind. Nếu chưa có các bạn có thể download ở đây rồi attach Northwnd.MDF vào db server. - Các lớp Nhibernate Dao là những lớp trực tiếp truy xuất database và chúng ta chuẩn bị test nó. Để test các lớp Dao này chúng ta cần một database thực sư và chúng ta đã chuẩn bị như đã nói ở trên. Xin nhắc lại một lần nữa là trên nguyên tắc, các hàm test nên không ảnh hưởng đến kết quả test của những hàm test khác, điều này có nghĩa là dữ liệu trước và sau khi thực hiện một hàm test là nhất quán. Để đạt được mục đích này, chúng ta tạo một lớp NhibernateTestCase, các lớp test case khác sẽ inherit từ lớp này. Trước khi tìm hiểu tại sao làm vậy, hãy xem implementation của nó: Code 7: Lớp Test base - Vậy bất kì lớp test nào inherit từ lớp này sẽ kế thừa được TestFixtureSetup và TestFixtureTearDown của nó. Có nghĩa là trước khi một lớp test được thực thi, NHIbernate Session Manager sẽ mở một transaction và rollback ngay sau khi test xong, nhờ thế dữ liệu test sẽ không bao giờ bị thay đổi. Còn bây giờ là nội dung một lớp Dao Test: Code 8: Lớp Test NHibernate Dao - Trong phần 2, chúng ta đã có một lớp Generic Dao giúp tiết kiệm code cho rất nhiều Dao Object khác nhau. Điều này dẫn đến việc là lớp Dao nào nên được test và lớp nào không? Để trả lời câu hỏi này, tác giả đã đưa ra các kinh nghiệm của mình khi viết Test Class: + Phải thực hiện test mọi method của Generic Dao. Nếu như bạn có 10 lớp Daos inherit generic Dao này thì chỉ một lớp bất kì trong số các lớp Daos này được test là đủ. + Phải test tất cả các method phụ của mỗi Dao nếu bạn có implement thêm. + Nếu có một lớp Dao nào không inherit từ Generic Dao như lớp HistoricalOrderSummaryDao thì lớp đó phải được test. + Phải chắc chắn dữ liệu test nhất quán trước và sau khi một Dao unit test được gọi và các unit test phải độc lập với nhau. V. Tóm tắt & Kết luận
  • 13. - Trong phần 3 này ta đã làm quen với Unit Testing, các tool và framework phụ trợ, ta cũng đã tìm hiểu qua công dụng và ý nghĩa của từng lớp, từng folder bên trong một project Test. Cách tổ chức lớp cũng như cách tác giả viết Unit Test rất tốt để tham khảo. Bản thân tôi cũng có viết Unit Test nhưng sau khi xem bài viết của Billy McCafferty thì đã quyết định từ nay về sau nếu có viết test sẽ theo cách làm của bác Billy. - Viết Unit Test tuy không bắt buộc nhưng nó đóng vai trò quan trọng trong qúa trình làm phần mềm. Đối với một số khách hàng lớn họ có thể yêu cầu chúng ta viết Unit Test và phải thoả mãn cover 80% code chẳng hạn. Unit Test không hẳn chỉ để test chương trình, ta có thể sử dụng nó như là một công cụ hỗ trợ debug nhanh khi implement một chức năng nào đó khá phức tạp. Kết hợp với một số kĩ thuật Mock, ta có thể test ngay một module của chương trình khi chưa có hoặc chưa hoàn thành xong các module khác… - Chắc hẳn chúng ta vẫn còn nhiều thắc mắc đối với cách hoạt động của lớp NhibernateSessionManager. Lớp này thực sự có công dụng gì và được tổ chức thế nào? Hãy chờ hồi sau sẽ rõ