Bai tap testing junit…..
Upcoming SlideShare
Loading in...5
×
 

Bai tap testing junit…..

on

  • 943 views

 

Statistics

Views

Total Views
943
Slideshare-icon Views on SlideShare
943
Embed Views
0

Actions

Likes
0
Downloads
36
Comments
0

0 Embeds 0

No embeds

Accessibility

Categories

Upload Details

Uploaded via as Microsoft Word

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    Bai tap testing junit….. Bai tap testing junit….. Document Transcript

    • 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 JUnitJUnit 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 1Giớ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ệnhCá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ứcunit 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ọngvì 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ảitố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ắtquã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áctest chạy một cách tự động1. Đặ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 trongchươ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ôngviệ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ấtvà đả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ênphá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ìnhbà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ảnvà hợp lý để giải quyết bài toán đặt ra.
    • 2. Lợi ích của JUnitJUnit 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ánbằ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ệctổ 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ờigian 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 trongphươ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ươngtrì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ínhiệu “xanh” mà JUnit có thể hỗ trợ.3. Ví dụ minh họaSau đâ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ãythiế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ạnVND 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ệtNam. 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ớtpublic 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ánMoney expected = new Money(1200, “VND”); // kết quả dự kiếnAssert.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ừ test2. Đố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ý dogây ra lỗi bên trong chương trình. (Thi hành việc kiểm thử JUnit trong môi trườngEclipse: 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ứcequals() 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ớtpublic 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ànhhai 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ườngEclipse: Kích vào biến cục bộ cần chuyển → menu Refactor → Convert LocalVariable 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ềnkhá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 khithêm 10 USD, ta sẽ có 1200 VND và 10 USD. Hãy thiết kế chương trình để thực hiệnyê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ớiimport 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 currencymonies.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ệnhSystem.out.println() vào phương thức testAddWithToString(), ta sẽ phát hiện ra kếtquả phải là { [1200.0 VND] }, thay vì { [1200 VND] }. Từ đó ta biết rằng cần phải hiệuchỉ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ị khiphả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ôngviế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ứcequals() và một phương thức bổ trợ, được đặt tên là contains(), để kiểm tra xem đốitượ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 nhaugiữa hai đối tượng MoneyBag trong phương thức equals(), ta chỉ cần kiểm chứng đốitượ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ầnbằ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ươngthức bổ trợ contains() là private nên ta không thể kiểm thử riêng phương thức nàytrong 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ớtprivate 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ểmthử (TestSuit). (Trong môi trường Eclipse: Kích vàomenu 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àmkiể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ấmnà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ềukiệ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ớiUnit 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ểudouble 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ếungườ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ìnhbá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 anhChuố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ộtphả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 functionhay 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 methodcủ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 trongchươ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ìnhviê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ầnmề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ếtlậ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ếtvẫ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 testcase 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ạysai 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 testcase 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.NETII.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ì, inputthế 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ônchí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ữngtest 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ườngtrướ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 đượchiệ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ủachươ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 codevớ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ếtnhữ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ụngmột Continuous Enviroment với NCoverExplorer thì sẽ dễ dàng phát hiện test case của bạn coverbao 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ôngthể 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 TestingIII.1 NUnit:Là một unit-testing framework cho ngôn ngữ lập trình .NET được port từ Junit. NUnit có haidạng là console và GUI. Thực sự thì NUnit thường được sử dụng kết hợp với CruiseControl.NETvà 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ầndownload 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ợpmộ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.NETIII.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 đốivớ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 mocknày để test project EnterpriseSample.Core bằng cách giả lập các object kiểu IxxxDao mà khôngcầ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 MockIV. Tạo project test sử dụng NUnit và Rhino MockBà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 testclass. 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ảnphầ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ộtassembly bất kì nên project test có thể là Console application, window application, v.v nhưngthô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àmquen với những Attribute như [TestFixture], [Test], [Setup], [TearDown], … xin được giải thíchngắ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ư TestFixturenhư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 testclass 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êmreference đến dll nunit.framework, thêm 1 class mới, khai báo nó thành public, thêm usingnunit.framework, thêm attribute [TestFixture] vào đầu của class, viết một method test và khaibá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áccũng như nguyên tắc Inheritance của lập trình hướng đối tượng để có một project test uyểnchuyể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 databasevà 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àiviết NHibernate Best Practices with ASP.NET, 1.2nd Ed trên code Project. Các code sampletrong bài dựa vào database Northwind của Microsoft và tham khảo 99,99% từ code mẫu của tácgiả 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àmAddOrder và DeleteOrder thông tin về Customer cũng như số lượng Order mà Customer đó đanggiữ 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ệuTest 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 dunglớp TestCustomerFactory như sau:Code 3: Lớp Factory để tạo các object làm dữ liệu testIV.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 InterfaceDAO, các lớp Domain như Customer, Order sẽ reference đến những Interface này. Cònimplementation thực sự của các interface Dao để truy xuất database được đặt ở projectEnterpriseSample.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 hayStub này sẽ là cascadeur cho các lớp Dao khi ta test EnterpriseSample.Core. Đoạn code dưới đâysử 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ạora 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 chongười ta danh sách Customer của thằng TestCustomersFactory. Còn ai hỏi mày biết GetByIdkhô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ưngtheo 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ầndù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áotrong 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 khichú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ậtMock 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ừngconstructor, từng putblic setter, getter đều nên được test. Nhưng đối với những người có máulườ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 folderDomain 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ớptest tương ứng ví dụ như sau:Code 6: Viết Unit Test cho các Domain ClassesIV. 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ấtdatabase, 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 databasethự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ó databaseNorthwind. Nếu chưa có các bạn có thể download ở đây rồi attach Northwnd.MDF vào dbserver.- 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àmtest 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ớptest 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 implementationcủ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, NHIbernateSession Manager sẽ mở một transaction và rollback ngay sau khi test xong, nhờ thế dữ liệu testsẽ 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 DaoObject 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 genericDao 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 HistoricalOrderSummaryDaothì 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 unittest 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 UnitTest 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ếttest 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ầnmề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ãncover 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ếthợ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ặcchư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ớpNhibernateSessionManager. 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õ