天天看點

走進JavaWeb技術世界11:單元測試架構Junit

本系列文章将整理到我在GitHub上的《Java面試指南》倉庫,更多精彩内容請到我的倉庫裡檢視

https://github.com/h2pl/Java-Tutorial

喜歡的話麻煩點下Star哈

文章首發于我的個人部落格:

www.how2playlife.com

本文是微信公衆号【Java技術江湖】的《走進JavaWeb技術世界》其中一篇,本文部分内容來源于網絡,為了把本文主題講得清晰透徹,也整合了很多我認為不錯的技術部落格内容,引用其中了一些比較好的部落格文章,如有侵權,請聯系作者。

該系列博文會告訴你如何從入門到進階,從servlet到架構,從ssm再到SpringBoot,一步步地學習JavaWeb基礎知識,并上手進行實戰,接着了解JavaWeb項目中經常要使用的技術群組件,包括日志元件、Maven、Junit,等等内容,以便讓你更完整地了解整個JavaWeb技術體系,形成自己的知識架構。

如果對本系列文章有什麼建議,或者是有什麼疑問的話,也可以關注公衆号【Java技術江湖】聯系作者,歡迎你參與本系列博文的創作和修訂。

簡介

測試 在軟體開發中是一個很重要的方面,良好的測試可以在很大程度決定一個應用的命運。

軟體測試中,主要有3大種類:

  • 單元測試
    單元測試主要是用于測試程式子產品,確定代碼運作正确。單元測試是由開發者編寫并進行運作測試。一般使用的測試架構是 [JUnit](http://junit.org/junit4/) 或者 [TestNG](https://github.com/cbeust/testng)。測試用例一般是針對_方法_ 級别的測試。           
  • 內建測試
    內建測試用于檢測系統是否能正常工作。內建測試也是由開發者共同進行測試,與單元測試專注測試個人代碼元件不同的是,內建測試是系統進行跨元件測試。           
  • 功能性測試
    功能性測試是一種品質保證過程以及基于測試軟體元件的規範下的由輸入得到輸出的一種黑盒測試。功能性測試通常由不同的測試團隊進行測試,測試用例的編寫要遵循元件規範,然後根據測試輸入得到的實際輸出與期望值進行對比,判斷功能是否正确運作。
               

概述

本文隻對 

 進行介紹,主要介紹如何在 

Android Studio

 下進行單元測試,單元測試使用的測試架構為 

JUnit

好處

可能目前仍有很大一部分開發者未使用 

 對他們的代碼進行測試,一方面可能是覺得沒有必要,因為即使沒有進行單元測試,程式照樣運作得很好;另一方面,也許有些人也認同單元測試的好處,但是由于需要額外的學習成本,是以很多人也是沒有時間或者說是沒有耐心進行學習······

這裡我想說的是,如果大家去看下 

github

 上目前主流的開源架構,star 數比較多的項目,一般都有很詳盡的測試用例。是以說,單元測試對于我們的項目開發,還是挺有好處的。

至于單元測試的好處,我這裡提及幾點:

  • 保證代碼運作與我們預想的一樣,代碼正确性可以得到保證
  • 程式運作出錯時,有利于我們對錯誤進行查找(因為我們忽略我們測試通過的代碼)
  • 有利于提升代碼架構設計(用于測試的用例應力求簡單低耦合,是以編寫代碼的時候,開發者往往會為了對代碼進行測試,将其他耦合的部分進行解耦處理)
    ······
               

Junit單元測試

本文執行個體講述了java單元測試JUnit架構原理與用法。分享給大家供大家參考,具體如下:

1 簡介

JUnit是一個Java語言的單元測試架構,它由 Kent Beck 和 Erich Gamma 建立,逐漸成為 xUnit 家族中最為成功的一個。

JUnit有它自己的JUnit擴充生态圈,多數Java的開發環境都已經內建了JUnit作為單元測試的工具。在這裡,一個單元可以是一個方法、類、包或者子系統。

是以,單元測試是指對代碼中的最小可測試單元進行檢查和驗證,以便確定它們正常工作。例如,我們可以給予一定的輸入測試輸出是否是所希望得到的結果。在本篇部落格中,作者将着重介紹 JUnit 4.X 版本的特性,這也是我們在日常開發中使用最多的版本。

2 特點

JUnit提供了注釋以及确定的測試方法;
JUnit提供了斷言用于測試預期的結果;
JUnit測試優雅簡潔不需要花費太多的時間;
JUnit測試讓大家可以更快地編寫代碼并且提高品質;
JUnit測試可以組織成測試套件包含測試案例,甚至其他測試套件;
Junit顯示測試進度,如果測試是沒有問題條形是綠色的,測試失敗則會變成紅色;
JUnit測試可以自動運作,檢查自己的結果,并提供即時回報,沒有必要通過測試結果報告來手動梳理。
           

3 内容

3.1 注解

@Test :該注釋表示,用其附着的公共無效方法(即用public修飾的void類型的方法 )可以作為一個測試用例;

@Before :該注釋表示,用其附着的方法必須在類中的每個測試之前執行,以便執行測試某些必要的先決條件;

@BeforeClass :該注釋表示,用其附着的靜态方法必須執行一次并在類的所有測試之前,發生這種情況時一般是測試計算共享配置方法,如連接配接到資料庫;

@After :該注釋表示,用其附着的方法在執行每項測試後執行,如執行每一個測試後重置某些變量,删除臨時變量等;

@AfterClass :該注釋表示,當需要執行所有的測試在JUnit測試用例類後執行,AfterClass注解可以使用以清理建立方法,如斷開資料庫連接配接,注意:附有此批注(類似于BeforeClass)的方法必須定義為靜态;

@Ignore :該注釋表示,當想暫時禁用特定的測試執行可以使用忽略注釋,每個被注解為@Ignore的方法将不被執行。

/
* JUnit 注解示例
*/
@Test
public void testYeepay(){
  Syetem.out.println("用@Test标示測試方法!");
}
@AfterClass
public static void paylus(){
  Syetem.out.println("用@AfterClass标示的方法在測試用例類執行完之後!");
}
           

3.2 斷言

在這裡,作者将介紹一些斷言方法,所有這些方法都來自 org.junit.Assert 類,其擴充了 java.lang.Object 類并為它們提供編寫測試,以便檢測故障。簡而言之,我們就是通過斷言方法來判斷實際結果與我們預期的結果是否相同,如果相同,則測試成功,反之,則測試失敗。

void assertEquals([String message], expected value, actual value) :斷言兩個值相等,值的類型可以為int、short、long、byte、char 或者
java.lang.Object,其中第一個參數是一個可選的字元串消息;
void assertTrue([String message], boolean condition) :斷言一個條件為真;
void assertFalse([String message],boolean condition) :斷言一個條件為假;
void assertNotNull([String message], java.lang.Object object) :斷言一個對象不為空(null);
void assertNull([String message], java.lang.Object object) :斷言一個對象為空(null);
void assertSame([String message], java.lang.Object expected, java.lang.Object actual) :斷言兩個對象引用相同的對象;
void assertNotSame([String message], java.lang.Object unexpected, java.lang.Object actual) :斷言兩個對象不是引用同一個對象;
void assertArrayEquals([String message], expectedArray, resultArray) :斷言預期數組和結果數組相等,數組的類型可以為int、long、short、char、byte 或者 java.lang.Object
           

4 JUnit 3.X 和 JUnit 4.X 的差別

4.1 JUnit 3.X

(1)使用 JUnit 3.X 版本進行單元測試時,測試類必須要繼承于 TestCase 父類;

(2)測試方法需要遵循的原則:

① public的;

② void的;

③ 無方法參數;

④方法名稱必須以 test 開頭;

(3)不同的測試用例之間一定要保持完全的獨立性,不能有任何的關聯;

(4)要掌握好測試方法的順序,不能依賴于測試方法自己的執行順序。

/
* 用 JUnit 3.X 進行測試
*/
import junit.framework.Assert;
import junit.framework.TestCase;
public class TestOperation extends TestCase {
  private Operation operation;
  public TestOperation(String name) { // 構造函數
    super(name);
  }
  @Override
  public void setUp() throws Exception { // 在每個測試方法執行 [之前] 都會被調用,多用于初始化
    System.out.println("歡迎使用Junit進行單元測試...");
    operation = new Operation();
  }
  @Override
  public void tearDown() throws Exception { // 在每個測試方法執行 [之後] 都會被調用,多用于釋放資源
    System.out.println("Junit單元測試結束...");
  }
  public void testDivideByZero() {
    Throwable te = null;
    try {
      operation.divide(6, 0);
      Assert.fail("測試失敗"); //斷言失敗
    } catch (Exception e) {
      e.printStackTrace();
      te = e;
    }
    Assert.assertEquals(Exception.class, te.getClass());
    Assert.assertEquals("除數不能為 0 ", te.getMessage());
  }
}
           

4.2 JUnit 4.X

(1)使用 JUnit 4.X 版本進行單元測試時,不用測試類繼承TestCase父類;

(2)JUnit 4.X 版本,引用了注解的方式進行單元測試;

(3)JUnit 4.X 版本我們常用的注解包括:

@Before 注解:與JUnit 3.X 中的 setUp() 方法功能一樣,在每個測試方法之前執行,多用于初始化;

@After 注解:與 JUnit 3.X 中的 tearDown() 方法功能一樣,在每個測試方法之後執行,多用于釋放資源;

@Test(timeout = xxx) 注解:設定目前測試方法在一定時間内運作完,否則傳回錯誤;

@Test(expected = Exception.class) 注解:設定被測試的方法是否有異常抛出。抛出異常類型為:Exception.class;

此外,我們可以通過閱讀上面的第二部分“2 注解”了解更多的注解。

/
* 用 JUnit 4.X 進行測試
*/
import static org.junit.Assert.*;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
public class TestOperation {
  private Operation operation;
  @BeforeClass
  public static void globalInit() { // 在所有方法執行之前執行
    System.out.println("@BeforeClass标注的方法,在所有方法執行之前執行...");
  }
  @AfterClass
  public static void globalDestory() { // 在所有方法執行之後執行
    System.out.println("@AfterClass标注的方法,在所有方法執行之後執行...");
  }
  @Before
  public void setUp() { // 在每個測試方法之前執行
    System.out.println("@Before标注的方法,在每個測試方法之前執行...");
    operation = new Operation();
  }
  @After
  public void tearDown() { // 在每個測試方法之後執行
    System.out.println("@After标注的方法,在每個測試方法之後執行...");
  }
  @Test(timeout=600)
  public void testAdd() { // 設定限定測試方法的運作時間 如果超出則傳回錯誤
    System.out.println("測試 add 方法...");
    int result = operation.add(2, 3);
    assertEquals(5, result);
  }
  @Test
  public void testSubtract() {
    System.out.println("測試 subtract 方法...");
    int result = operation.subtract(1, 2);
    assertEquals(-1, result);
  }
  @Test
  public void testMultiply() {
    System.out.println("測試 multiply 方法...");
    int result = operation.multiply(2, 3);
    assertEquals(6, result);
  }
  @Test
  public void testDivide() {
    System.out.println("測試 divide 方法...");
    int result = 0;
    try {
      result = operation.divide(6, 2);
    } catch (Exception e) {
      fail();
    }
    assertEquals(3, result);
  }
  @Test(expected = Exception.class)
  public void testDivideAgain() throws Exception {
    System.out.println("測試 divide 方法,除數為 0 的情況...");
    operation.divide(6, 0);
    fail("test Error");
  }
  public static void main(String[] args) {
  }
}
           

4.3 特别提醒

通過以上兩個例子,我們已經可以大緻知道 JUnit 3.X 和 JUnit 4.X 兩個版本的差別啦!

首先,如果我們使用 JUnit 3.X,那麼在我們寫的測試類的時候,一定要繼承 TestCase 類,但是如果我們使用 JUnit 4.X,則不需繼承 TestCase 類,直接使用注解就可以啦!

在 JUnit 3.X 中,還強制要求測試方法的命名為“ testXxxx ”這種格式;

在 JUnit 4.X 中,則不要求測試方法的命名格式,但作者還是建議測試方法統一命名為“ testXxxx ”這種格式,簡潔明了。

此外,在上面的兩個示例中,我們隻給出了測試類,但是在這之前,還應該有一個被測試類,也就是我們真正要實作功能的類。現在,作者将給出上面示例中被測試的類,即 Operation 類:

/
* 定義了加減乘除的法則
*/
public class Operation {
  public static void main(String[] args) {
    System.out.println("a + b = " + add(1,2));
    System.out.println("a - b = " + subtract(1,2));
    System.out.println("a * b = " + multiply(1,2));
    System.out.println("a / b = " + divide(4,2));
    System.out.println("a / b = " + divide(1,0));
  }
  public static int add(int a, int b) {
    return a + b;
  }
  public static int subtract(int a, int b) {
    return a - b;
  }
  public static int multiply(int a, int b) {
    return a * b;
  }
  public static int divide(int a, int b) {
    return a / b;
  }
}
           

5 測試示例

5.1 示例一:簡單的 JUnit 3.X 測試

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import java.util.ArrayList;
import java.util.Collection;
/
 * 1、建立一個測試類,繼承TestCase類
 */
public class SimpleTestDemo extends TestCase {
  public SimpleTestDemo(String name) {
    super(name);
  }
  /
   * 2、寫一個測試方法,斷言期望的結果
   */
  public void testEmptyCollection(){
    Collection collection = new ArrayList();
    assertTrue(collection.isEmpty());
  }
  /
   * 3、寫一個suite()方法,它會使用反射動态的建立一個包含所有的testXxxx方法的測試套件
   */
  public static Test suit(){
    return new TestSuite(SimpleTestDemo.class);
  }
  /
   * 4、寫一個main()方法,以文本運作器的方式友善的運作測試
   */
  public static void main(String[] args) {
    junit.textui.TestRunner.run(suit());
  }
}
           

6 個人建議

有些童鞋可能會有一些誤解,認為寫測試代碼沒有用,而且還會增大自己的壓力,浪費時間。但事實上,寫測試代碼與否,還是有很大差別的,如果是在小的項目中,或許這種差別還不太明顯,但如果在大型項目中,一旦出現錯誤或異常,用人力去排查的話,那将會浪費很多時間,而且還不一定排查的出來,但是如果用測試代碼的話,JUnit 就是自動幫我們判斷一些代碼的結果正确與否,進而節省的時間将會遠遠超過你寫測試代碼的時間。

是以,個人建議:要養成編寫測試代碼的習慣,碼一點、測一點;再碼一點,再測一點,如此循環。在我們不斷編寫與測試代碼的過程中,我們将會對類的行為有一個更為深入的了解,進而可以有效的提高我們的工作效率。下面,作者就給出一些具體的編寫測試代碼的技巧和較好的實踐方法:

1. 不要用 TestCase 的構造函數初始化 Fixture,而要用 setUp() 和 tearDown() 方法;

2. 不要依賴或假定測試運作的順序,因為 JUnit 會利用 Vector 儲存測試方法,是以不同的平台會按不同的順序從 Vector 中取出測試方法;

3. 避免編寫有副作用的 TestCase,例如:如果随後的測試依賴于某些特定的交易資料,就不要送出交易資料,隻需要簡單的復原就可以了;

4. 當繼承一個測試類時,記得調用父類的 setUp() 和 tearDown() 方法;

5. 将測試代碼和工作代碼放在一起,同步編譯和更新;

6. 測試類和測試方法應該有一緻的命名方案,如在工作類名前加上 test 進而形成測試類名;

7. 確定測試與時間無關,不要使用過期的資料進行測試,以至于導緻在随後的維護過程中很難重制測試;

8. 如果編寫的軟體面向國際市場,那麼編寫測試時一定要考慮國際化的因素;

9. 盡可能地利用 JUnit 提供地 assert 和 fail 方法以及異常處理的方法,其可以使代碼更為簡潔;

10. 測試要盡可能地小,執行速度快;

11. 不要硬性規定資料檔案的路徑;

12. 使用文檔生成器做測試文檔。

8 大單元測試架構

走進JavaWeb技術世界11:單元測試架構Junit

1.Arquillian

Arquillian是一個基于JVM的高度可擴充的測試平台,允許開發人員建立Java的自動化內建,功能和驗收測試。Arquillian允許你在運作态時執行測試。Arquillian可用于管理容器(或容器)的生命周期,綁定測試用例,依賴類和資源。它還能夠将壓縮包部署到容器中,并在容器中執行測試并捕獲結果并建立報告。

Arquillian內建了熟悉的測試架構,如JUnit 4、TestNG 5,并允許使用現有的IDE啟動測試。并且由于其子產品化設計,它能夠運作Ant和Maven測試插件。Arquillian目的是簡化項目內建測試和功能測試的編寫,讓它們能像單元測試一樣簡單。

走進JavaWeb技術世界11:單元測試架構Junit

2.JTEST

JTest也被稱為“Parasoft JTest”,是Parasoft公司生産的自動化Java軟體測試和靜态分析軟體。 JTest包括用于單元測試用例生成和執行,靜态代碼分析,資料流靜态分析和度量分析,回歸測試,運作時錯誤檢測的功能。

還可以進行結對的代碼審查流程自動化和運作時錯誤檢測,例如:條件,異常,資源和記憶體洩漏,安全攻擊漏洞等。

走進JavaWeb技術世界11:單元測試架構Junit

3.The Grinder

“The Grinder”是一個Java負載測試架構。并且通過使用大量負載注射器來為分布式測試提供便利。Grinder可以對具有Java API的任何内容加載測試。這包括HTTP Web伺服器,SOAP、REST Web服務、應用程式伺服器,包括自定義協定。測試腳本用強大的Jython和Clojure語言編寫。Grinder的GUI控制台允許對多個負載注射器進行監控和控制,并自動管理用戶端連接配接和Cookie,SSL,代理感覺和連接配接限制。您可以在這裡找到關于磨床功能的更多深入資訊。

走進JavaWeb技術世界11:單元測試架構Junit

4.TestNG

走進JavaWeb技術世界11:單元測試架構Junit

TestNG受JUnit和NUnit的啟發,是為Java程式設計語言而設計的測試架構。TestNG主要設計用于覆寫更廣泛的測試類别,如單元,功能,端到端,內建等。它還引入了一些新功能,使其更強大,更易于使用,如:注解,運作在大線程池中進行各種政策測試,多線程安全驗證代碼測試,靈活的測試配置,資料驅動的參數測試支援等等。

TestNG有各種工具和插件(如Eclipse,IDEA,Maven等)支援。

走進JavaWeb技術世界11:單元測試架構Junit

5.JUnit

JUnit是為Java程式設計語言設計的單元測試架構。JUnit在測試驅動開發架構的開發中發揮了重要作用。它是單元測試架構之一,統稱為由SUnit起源的xUnit。

走進JavaWeb技術世界11:單元測試架構Junit

6.JWalk

JWalk被設計為用于Java程式設計語言的單元測試工具包。它被設計為支援稱為“Lazy系統單元測試”的測試範例。

走進JavaWeb技術世界11:單元測試架構Junit

JWalkTester工具對任何由程式員提供的編譯的Java類執行任何測試。它能夠通過靜态和動态分析以及來自程式員的提示來測試懶惰Lazy規範的一緻性。

走進JavaWeb技術世界11:單元測試架構Junit

7.Mockito

Mockito被設計為用于Java的開源測試架構,MIT許可證。Mockito允許程式員為了測試驅動開發(TDD)或行為驅動開發(BDD)而在自動化單元測試中建立和測試雙對象(Mock對象)。

走進JavaWeb技術世界11:單元測試架構Junit

8 Powermock

PowerMock是用于對源代碼進行單元測試的Java架構,它可以作為其他模拟架構的擴充,比如原型Mockito或EasyMock,但具有更強大的功能。PowerMock利用自定義的類加載器和位元組碼操縱器來實作靜态方法,構造函數,最終類和方法以及私有方法等的模拟。它主要是為了擴充現有的API,使用少量的方法和注解來實作額外的功能。

參考文章

https://www.jianshu.com/p/0530cb31c3b2 https://blog.csdn.net/Dream_Weave/article/details/83859750 https://blog.csdn.net/qq_26295547/article/details/83145642 http://www.sohu.com/a/145107423_731023

微信公衆号

個人公衆号:程式員黃小斜

微信公衆号【程式員黃小斜】新生代青年聚集地,程式員成長充電站。作者黃小斜,職業是阿裡程式員,身份是斜杠青年,希望和更多的程式員交朋友,一起進步和成長!專注于分享技術、面試、職場等成長幹貨,這一次,我們一起出發。

關注公衆号後回複“2019”領取我這兩年整理的學習資料,涵蓋自學程式設計、求職面試、算法刷題、Java技術學習、計算機基礎和考研等8000G資料合集。

關注公衆号後回複“2020”領取我這兩年整理的學習資料,涵蓋自學程式設計、求職面試、算法刷題、Java技術學習、計算機基礎和考研等8000G資料合集。

技術公衆号:Java技術江湖

微信公衆号【Java技術江湖】一位阿裡 Java 工程師的技術小站,專注于 Java 相關技術:SSM、SpringBoot、MySQL、分布式、中間件、叢集、Linux、網絡、多線程,偶爾講點Docker、ELK,同時也分享技術幹貨和學習經驗,緻力于Java全棧開發!

關注公衆号後回複“PDF”即可領取200+頁的《Java工程師面試指南》強烈推薦,幾乎涵蓋所有Java工程師必知必會的知識點。