Java單元測試初體驗(JUnit4)


 

什么是單元測試

  我們在編寫大型程序的時候,需要寫成千上萬個方法或函數,這些函數的功能可能很強大,但我們在程序中只用到該函數的一小部分功能,並且經過調試可以確定,這一小部分功能是正確的。但是,我們同時應該確保每一個函數都完全正確,因為如果我們今后如果對程序進行擴展,用到了某個函數的其他功能,而這個功能有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、本博客的目的是知識交流所用,轉載自其它博客或網站,作為自己的參考資料的,感謝這些文章的原創人員


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM