什么是單元測試
我們在編寫大型程序的時候,需要寫成千上萬個方法或函數,這些函數的功能可能很強大,但我們在程序中只用到該函數的一小部分功能,並且經過調試可以確定,這一小部分功能是正確的。但是,我們同時應該確保每一個函數都完全正確,因為如果我們今后如果對程序進行擴展,用到了某個函數的其他功能,而這個功能有bug的話,那絕對是一件非常郁悶的事情。所以說,每編寫完一個函數之后,都應該對這個函數的方方面面進行測試,這樣的測試我們稱之為單元測試。傳統的編程方式,進行單元測試是一件很麻煩的事情,你要重新寫另外一個程序,在該程序中調用你需要測試的方法,並且仔細觀察運行結果,看看是否有錯。這樣的話太過於麻煩了,本文簡要介紹一下在Eclipse中使用JUnit4進行單元測試的方法。用更加通俗的話來描述單元測試就是:寫了個類,要給別人用,會不會有bug?怎么辦?測試一下。用main方法測試好不好?這種方法我們經常用,就是寫一個方法實現一些功能,把方法的調用方式放在main函數中。這樣的測試方式一個是使得main函數太過於混亂,再者測試過程需要人的仔細觀察來辨別每個函數的功能實現,哪一個函數出錯了,哪一個函數沒有輸出之類的問題層出不窮,單元測試就是來解決這些問題的。下面我會就單元測試的每一步給出詳細的圖解和描述。
第一部分
我們先創建一個Java Project,名字就叫做JUnit4,然后創建兩個包com.itcast.junit4和com.itcast.junit.test,如下圖所示
其中com.itcast.junit4用於我們自己寫的類和方法的存放,com.itcast.junit4.test用於我們完成單元測試
在com.itcast.junit4包中創建一個類叫做T(名字可以是任意的,這里是為了方便),在類中添加兩個方法add和divide實現兩個數的加法和除法運算
package com.itcast.junit4; public class T { public int add(int x,int y){ return x+y; } public int divide(int x,int y){ return x/y; } public static void main(String[] args) { int z=new T().add(3, 5); System.out.println(z); } }
我們傳統測試的方法通常都是按上面的方式去看add函數是否可以實現我們想要的功能,把方法的調用放在main函數中。下面我們看一下單元測試到底是什么東西:
在com.itcast.junit4.test包中創建一個類,叫做TTest(單元測試命名規范:a) 類放在test包中;b) 類名用XXXTest結尾;c) 方法用testMethod命名;)
【步驟提示】com.itcast.junit4.test包-->右鍵-->New--Junit Test Case,然后選擇New Junit 4 test,那個Junit 3已經過時了。下面的一行Class under test,單擊右側的Browser,在彈出的輸入框中輸入我們想要測試的類名T,選中單擊next出現了一個界面要我們選擇需要測試的方法,我們這里選擇add方法和divide;
接着出現了下面的界面:
這一步提示我們是否將我們需要的JUnit 4的相關包加入到我們項目的ClassPath路徑下,點擊OK就行,因為Eclipse中包含JUnit的jar包,我們暫且先用Eclipse自帶的Junit4去測試,后面后將如何用我們自己下載的JUnit4 JAR包。
做完上面的步驟會創建一個這樣的測試類
import static org.junit.Assert.*; import org.junit.Test; public class TTest { @Test public void testAdd() { fail("Not yet implemented"); } @Test public void testDivide() { fail("Not yet implemented"); } }
上面的org.junit.Assert.*;就是靜態導入的我們實現單元測試要用到的一些方法;【注意】這是靜態引入,可以把方法直接引入,org.junit.Assert是一個類,不是一個包,當然這些方法肯定都是靜態方法了。出現的代碼都是Assert類中的一些方法,"@Test”表明下面這個方法是一個測試方法,我們先刪除自動生成的fail()函數的代碼。添加以下代碼:
public class TTest { @Test public void testAdd() { int z=new T().add(2, 4); //判斷z==6,以往的assert assertEquals(6, z); } @Test public void testDivide() { //測試T類中的divide方法 int z=new T().divide(8, 2); System.out.println(z); } }
我們現在開始進行測試:要測試的方法-->右鍵-->Run As-->JUnit Test
如果你想兩個方法一塊測試,則Run As-->選擇Run Configurations:按圖中選擇相應的選項,然后點擊Run
點擊run會出現下面的結果:
綠條顯示兩個方法的功能沒有錯誤,有這樣的調試准則:keep the bar green,to keep the code clean,綠色代表測試成功,其中Error:程序出錯 Failures:測試失敗
Error:是程序有問題,比如我們在testAdd方法中加上這一句:int a=8/0;再次測試這個方法則會出現一個Error
public void testAdd() { int z=new T().add(2, 4); //判斷z==6,以往的assert assertEquals(6, z); int a=8/0; }
可以看到最下方提示我們,我們寫的方法中出現了除數為0的情況
Failures:測試失敗,比如我們在方法改成下面的形式:
public void testAdd() { int z=new T().add(2, 4); //判斷z==6,以往的assert assertEquals(6, z); assertTrue(z<3); // int a=8/0; }
再次測試一下我們的方法:則會出現調試失敗的情況
第二部分
通過上面的學習我們已經了解基本的單元測試的步驟,我們查看一下JUnit API可以看到org.junit.Assert類有很多類似於assertEquals(6, z);assertTrue(z<3);之類的方法的使用;
我們可以看到有很多方法都是以重載的形式出現的,比如我們前面的例子,在testAdd()方法中添加assertTrue("z too small",z>10);前面的字符串用於在我們測試失敗的情況下給我們提示:因為8<10,所以會在測試失敗的情況下給我們提示"z too small";
重磅出擊:assertThat
assertThat(來自hamcrest包,所以我們需要下載hamcrest這個包,這里共享給大家,里面有很多我們平時都可以用到的JAR包和文件,地址:鏈接:http://pan.baidu.com/s/1sl02DOD 密碼:ci5m): assertThat(actual, matcher);的出現可以替代其他所有的assert。放棄舊的斷言,使用hamcrest斷言。其中actual參數是實際的值,matcher可以是一個匹配器。在以后的項目開發中我們就可以使用assertThat代替前面出現的類如assertEquals(6, z); assertTrue(z<3);方法。
首先第一步,我們想使用assertThat,需要添加兩個jar包hamcrest-core-1.2和hamcrest-library-1.2,這兩個包都在我的共享里了,想在把這兩個包添加進我們的Java Project中
JUnit4 Test-->右鍵-->Build Path-->Add External Archives,將這兩個Jar加進去
添加以后:我們就可以使用assertThat了
我們把testAdd()方法改成下面的形式:
@Test public void testAdd() { int z=new T().add(2, 4); assertThat(z, is(8)); //判斷z==6,以往的assert // assertEquals(6, z); // assertTrue(z<3); // int a=8/0; }
代碼中的is()方法是在import static org.hamcrest.Matchers(這個類在我們加進來的hamcrest-core-1.2.jar內)類的一個方法我們需要將其靜態引入,所以在最上面要加上下面這一句,應該就可以了;
import static org.hamcrest.Matchers.*;
但是,測試又出現了這樣的錯誤:
我們可以看到Failure Trace第一行什么ClassLoader的錯誤,這是因為我們在這里用了兩種包,一個是hamcrest包,一個是JUnit4的包,這兩個包它們的ClassLoader用的不是一個(不清楚啥是ClassLoader,不要緊,先學會怎么解決,以后再研究)。解決方法很簡單:在我們的JUnit4項目中-->右鍵JUnit->Build Path-->Remove from Build Path即可,如圖所示:
然后我們自己將JUnit包引入進來(Junit也在我的分享文件中)
JUnit4 Test-->右鍵-->Build Path-->Add External Archives,選擇我們JUnit包中的junit-4.10,如下圖所示:
ok!測試成功!
assert的使用是測試代碼更加自然(諸如這樣的理解:z is 8),自己可以體會一下,下面給出一些實例,大家可以自己動手試一下
示例 a)assertThat( n, allOf( greaterThan(1), lessThan(15) ) ); assertThat( n, anyOf( greaterThan(16), lessThan(8) ) ); assertThat( n, anything() ); assertThat( str, is( "bjsxt" ) ); assertThat( str, not( "bjxxt" ) ); b)assertThat( str, containsString( "bjsxt" ) ); assertThat( str, endsWith("bjsxt" ) ); assertThat( str, startsWith( "bjsxt" ) ); assertThat( n, equalTo( nExpected ) ); assertThat( str, equalToIgnoringCase( "bjsxt" ) ); assertThat( str, equalToIgnoringWhiteSpace( "bjsxt" ) ); c)assertThat( d, closeTo( 3.0, 0.3 ) ); assertThat( d, greaterThan(3.0) ); assertThat( d, lessThan (10.0) ); assertThat( d, greaterThanOrEqualTo (5.0) ); assertThat( d, lessThanOrEqualTo (16.0) ); d)assertThat( map, hasEntry( "bjsxt", "bjsxt" ) ); assertThat( iterable, hasItem ( "bjsxt" ) ); assertThat( map, hasKey ( "bjsxt" ) ); assertThat( map, hasValue ( "bjsxt" ) );
第三部分 JUnit4 Annotation
幾種常見的注釋形式:
@Test: 測試方法 a) (expected=XXException.class) b) (timeout=xxx) @Ignore: 被忽略的測試方法 @Before: 每一個測試方法之前運行 @After: 每一個測試方法之后運行 @BeforeClass: 所有測試開始之前運行 @AfterClass: 所有測試結束之后運行
我們分別進行解釋
1)@Test,前面已經說明了,@Test注解表明下面的方法是一個測試方法,a), b)兩種形式,比如@Test(expected=java.lang.ArithmeticException.class,timeout=100)a是在測試出現異常的情況下告知我們出現的異常信息,類似與try-catch中的e.printstacktrace() 方法,比較簡單。b中的timeout=100,運行時間限制在100ms以內(通常在測試代碼運行效率時這樣設置)
2)@ignore: 被忽略的測試方法(就是測試的時候跳過ignor標記的模塊或方法)
有時候某些方法還不具備測試的條件,暫時還不能測試或者某些方法已經不需要再做測試了,這就可以進行忽略的操作了。
有時方法的測試條件還沒滿足,整個項目還差一個模塊,則可以采用該方法假定測試條件成立。
3)@after和@before
我們把代碼改成這個樣子:為方便起見先把那個divide方法刪掉
public class TTest { @Before public void before() { System.out.println("befor"); } @Test public void testAdd() { int z = new T().add(2, 4); assertThat(z, is(8)); // 判斷z==6,以往的assert // assertEquals(6, z); // assertTrue(z<3); // int a=8/0; } @After public void after() { System.out.println("after"); } }
當然需要在上面需要添加這兩句:
import org.junit.After; import org.junit.Before;
測試一下我們的testAdd()方法,控制台輸出:
befor after
說明@Before在每一個測試方法(@Test方法)之前運行 @After:在每一個測試方法之后運行。它們兩個的應用場合: 有些方法需要執行的時候需要一些先決條件,比如打開某文件、獲取資源,搭建環境,執行完之后需要關閉文件、釋放資源、卸載環境這就需要before和after操作。
4)@BeforeClass;@AfterClass,它們兩個都是靜態的方法。我們繼續改寫代碼:
public class TTest { @BeforeClass public static void beforeClass(){ System.out.println("before class.."); } @Before public void before() { System.out.println("befor"); } @Test public void testAdd() { int z = new T().add(2, 4); assertThat(z, is(6)); // 判斷z==6,以往的assert // assertEquals(6, z); // assertTrue(z<3); // int a=8/0; } @After public void after() { System.out.println("after"); } @AfterClass public static void afterClass(){ System.out.println("after class.."); } }
當然也要加上:
import org.junit.BeforeClass; import org.junit.AfterClass;
輸出結果:
before class.. befor after after class..
這就說明了@BeforeClass 所有測試開始之前運行;@AfterClass: 所有測試結束之后運行【一定要注意】這兩個方法都是靜態方法,想想也應該明白類一加載就執行這兩個方法,此時還沒有創建任何對象,能執行的肯定就是靜態方法了。
總結:
用了一天的時間學習了一下JUnit單元測試,其實還有很多東西沒有看到,但對於單元測試的步驟和套路也算了解了一些,寫在這里也方便自己以后的查閱和復習,待以后用到更深入的時候再更新一些單元測試在Spring/Mock中的應用,有錯誤的地方歡迎大家指出,再次謝謝大家的閱讀!
本文為博主原創文章,轉載請注明出處:http://www.cnblogs.com/ysw-go/
1、本博客的原創原創文章,都是本人平時學習所做的筆記,如有錯誤,歡迎指正。
2、如有侵犯您的知識產權和版權問題,請通知本人,本人會即時做出處理文章。
3、本博客的目的是知識交流所用,轉載自其它博客或網站,作為自己的參考資料的,感謝這些文章的原創人員