Junit系列一 基本概述

JUnit是一個開源的java自動化單元測試框架。由 Erich Gamma 和 Kent Beck 與1997年開發(fā)完成。 Erich Gamma 是 GOF的作者 之一;Kent Beck 則在 XP 中有重要的貢獻(xiàn)。Junit常常被開發(fā)者用來做單元測試的自動化測試框架。但是按照測試人員的角度來說,Junit更嚴(yán)格來講是一個測試驅(qū)動器,我們不僅僅可以用其來做單元測試,也是可以以其為核心來做集成測試、系統(tǒng)測試。很多人分析Junit喜歡從開發(fā)角度從設(shè)計模式來分析,但是對于測試人員來說需要的是從中學(xué)習(xí)自動化測試框架的基本組成部分與結(jié)構(gòu)模式。學(xué)習(xí)Junit不僅僅是學(xué)習(xí)其用法,更要從中學(xué)習(xí)一個自動化測試框架的有哪些組成部分?各個組成部分之間是如何協(xié)調(diào)運作來形成一個可擴展穩(wěn)健的自動化測試框架。Junit可謂“麻雀雖小,五臟俱全?!?,其包含一個自動化測試框架所有的要素部分,很多的測試框架,千變?nèi)f化其實都離不開一個測試框架必有的組件與模式。本系列嘗試從一個手工測用例轉(zhuǎn)化為自動化執(zhí)行以后的模式,來總結(jié)出一個自動化測試所必須的一個組成部分,并借用Junit來說明Junit中是如何實現(xiàn)這幾個部分的。

基本概述

讓我們從平時做測試的一個簡單情形開始。想想平時我們是如何做測試的,或者說是如何執(zhí)行測試過程的?是不是有用例?我們編寫了用例,在用例里面說明了測試預(yù)置環(huán)境,測試輸出數(shù)據(jù),執(zhí)行步驟,和預(yù)期的結(jié)果。例如:

拿到這個用例,是不是要一步步按照用例的說明來操作。在人工測試的情況下類似如下的情況,

在這個過程當(dāng)中,測試人員是執(zhí)行者,讀取測試用例內(nèi)容,操作被測對象,并判斷結(jié)果是否正確。

那么,如果要把上述的過程自動化需要如何做呢?在自動化的過程中,人不在了,那么誰來讀取測試用例呢?準(zhǔn)確點說由誰來讀取測試用例的文件,并逐條執(zhí)行里面的用例呢?首先想到就是寫一個程序,此程序能讀取文件,并識別里面的逐條的測試用例,然后執(zhí)行。這樣的一個程序叫做測試執(zhí)行器(Test Runner)。

有了執(zhí)行器以后,又面臨一個問題,在人工操過程中,人是可以識別文件里的文本用例,讀懂它的步驟然后操作,但執(zhí)行器就不行了,它是程序他無法識別文本的意思。程序識別程序,那么自然而然想到的方法就是把測試用例方法化,即把測試用例用程序的方法來表示,即測試方法(Test Method),因此執(zhí)行器執(zhí)行用例,就轉(zhuǎn)化為執(zhí)行器調(diào)用方法的過程。

我們進一步觀察,測試方法,實時上,一個方法()Test Method 就代表一個測試用例,一個測試用例主要包含的重要信息,就是用例的執(zhí)行環(huán)境是什么,執(zhí)行步驟,預(yù)期值是什么,等等。類似的測試方法也需要包含四個步驟,稱為測試四步驟(Four-Phase Test)

  • 環(huán)境設(shè)置
  • SUT調(diào)用
  • 結(jié)果驗證
  • 環(huán)境清理

回顧之前所述,在測當(dāng)中一個重要的步驟就是驗證,手動測試中,肉眼查看運行結(jié)果并和預(yù)期對比來得出測試是否通過的結(jié)果。在自動化過程中自然需要這個過程,讓測試方法自動驗證測試結(jié)果呢?這部自動化測試中稱為斷言(Assert)。

在整個的測試過程當(dāng)中還包過如何過濾測試方法、、測試數(shù)據(jù)管理、最終測試結(jié)果報告等等。

如上述,我們總結(jié)一下,在自動化測試當(dāng)中需要涉及的部分包括:

  • 測試執(zhí)行器(Runner):讀取測試用例、執(zhí)行測試用例、測試報告輸出
  • 測試輸入數(shù)據(jù)管理:如果測試需要外部輸入數(shù)據(jù),這些數(shù)據(jù)如何組織管理
  • 環(huán)境設(shè)置(setUp tearDown):主要設(shè)置用例執(zhí)行前的環(huán)境、測試結(jié)束后清理測試環(huán)境;
  • 結(jié)果斷言(Assert):如何來判斷測試結(jié)果是否符合預(yù)期
  • 測試組織與流程控制:如何來組織測試用例,測試用例的過程如何控制

那么作為一個自動化的測試框架,Junit如何對上述各個組成部分進行處理呢,我們將從一個Junti入門例子開始,先對Junit做大致介紹,然后逐一演示Junit是如何實現(xiàn)上述各個部分的?只要大家理解了Junit對上述幾個部分是怎么做的,后續(xù)學(xué)習(xí)其他測試驅(qū)動器就可以快速入手,比如TestNG-在講完Junit后,我們會針對TestNG如何實現(xiàn)上是問題做簡單說明,只要大家牢固掌握J(rèn)unit實現(xiàn)上述問題的本質(zhì),學(xué)習(xí)testNG也就是分分鐘的事情。

在Eclipse中使用JUnit4

在開始之前我們先把Junit引入到我們的Eclipse的項目中來,在Eclipse項目右鍵選中,如下圖所示

在彈出的屬性窗口中,首先在左邊選擇“Java Build Path”,然后到右上選擇“Libraries”標(biāo)簽,之后在最右邊點擊“Add Library…”按鈕,如下圖所示:

然后在新彈出的對話框中選擇JUnit4并點擊確定,如上圖所示,JUnit4軟件包就被包含進我們這個項目了。

Junit 入門例子

假設(shè)我們有一個計算器類,包含兩個方法,add和multiply。

public class Calculator {
    
    public int add(int one, int another) {
        return one + another;
    }

    public int multiply(int one, int another) {
        return one * another;
    }
}

對這兩個方法,我們寫兩條happy path的測試用例:

要把這兩條測試用例自動化執(zhí)行,要涉及前面所述的幾個步驟,首先是要測試用例腳本化,把測試用例的步驟用代碼方法來表示,我們建一個類叫CalculatorTest,包含這兩條測試用例:

public class CalculatorTest {
    
    public void testAdd(){
        
        Calculator calculator = new Calculator();
        int sum = calculator.add(6,7);
        if(sum == 13) {
            System.out.println("add() SUCCESS!");
        } else {
            System.out.println("add() FAIL!");
        }
    }
    
    public void testMultiply(){
        
        Calculator calculator = new Calculator();
        int product = calculator.multiply(8,9);
        if (product == 56) {
            System.out.println("multiply() SUCCESS!");
        } else {
            System.out.println("multiply() FAILs!");
        }
    }   
}

測試方法是不是要執(zhí)行器來執(zhí)行?如果我沒有Junit這樣的框架,我們怎么執(zhí)行上述的測試方法呢?如下也許是一種方法:

public class Client {
    public static void main(String[] args) {
        CalculatorTest test = new CalculatorTest();
        test.testAdd();
        test.testMultiply();
    }
}

然后再通過某種方式運行這個main 方法,查看打印的輸出,來驗證測試是成功還是失敗。想想一下,如果我們有很多的類,每個類都有很多方法,每個方法都要寫很多的分支來判斷結(jié)果,而且還要寫個main方法一個個去調(diào)用各個類方法,明顯是不可行呢?如果有一個程序--執(zhí)行器,能夠自動創(chuàng)建一個測試類,并自動調(diào)用里面的各個測試方法那么就簡化很多了。Junit框架就提供了這樣的功能,Junit內(nèi)置的執(zhí)行器,能夠讀取一個類,并執(zhí)行里面的測試方法,要讓執(zhí)行者自動找到測試方法并調(diào)用,必須要有一定的約定,比如告訴執(zhí)行器說用test開頭的方法就是測試方法(Junit 3所采用的方法)或者給測試方法某個標(biāo)注來表示測試方法,那么執(zhí)行器就會按著這個約定去調(diào)用類里的有特定命名方式或者有特定標(biāo)志的測試方法。我們來Junit來改寫上述的測試:

public class CalculatorTest {
    
    @Test
    public void testAdd(){
        
        Calculator calculator = new Calculator();
        int sum = calculator.add(6,7);
        if(sum == 13) {
            System.out.println("add() SUCCESS!");
        } else {
            System.out.println("add() FAIL!");
        }
    }
    
    @Test
    public void testMultiply(){
        
        Calculator calculator = new Calculator();
        int product = calculator.multiply(8,9);
        if (product == 56) {
            System.out.println("multiply() SUCCESS!");
        } else {
            System.out.println("multiply() FAILs!");
        }
    }       
}

大家注意到每個測試方多了一個注解@Test,這個注解的作用的就是告訴Junit的Test Runner 這是一個測試方法,Test Runner就會調(diào)用這個方法。這時候你就不用自己寫一個main來執(zhí)行這些方法,在Eclipse下右鍵 Run as Junit Test,Junit的測試執(zhí)行器就會自動運行里面的測試方法。

還有兩個地方:一個是設(shè)置部分,每個測試方法都創(chuàng)建了一個Calculator,這個重復(fù)的代碼是否可以抽取出來呢?還有判斷結(jié)果部分分支復(fù)雜,能否簡化了?Junit提供fixture和斷言Assert相關(guān)方案來解決這兩個問題,看再一次用Junit簡化后的測試代碼:

public class CalculatorTest {
    
    // fixture部分
    public Calculator calculator;
    
    @Before
    public void setUp(){
        calculator = new Calculator();
    }
    
    //測試方法
    @Test
    public void testAdd(){
        
        calculator = new Calculator();
        int sum = calculator.add(6,7);
        //斷言部分
        Assert.assertEquals(13, sum);
    }
    
    @Test
    public void testMultiply(){
        
        calculator = new Calculator();
        int product = calculator.multiply(8,9);
        //斷言部分
        Assert.assertEquals(72, product);   
    }       

}

上述@Berfore 表示此方法在每個測試方法運行之前運行一次。Assert.assertEquals判斷實際結(jié)果與預(yù)期是否一致。至此,這就是一個簡單完整的Junit測試代碼。用Junit執(zhí)行器運行后有如下圖的測試結(jié)果展示:

上述的代碼中有疑問的地方先不管,這只是讓大家體會一些一個Junit測試的整體流程,各個注解后面會有詳細(xì)介紹。這邊講一些測試的統(tǒng)一結(jié)構(gòu)和命名問題。

統(tǒng)一的測試結(jié)構(gòu)

回顧我們的第一個Junit測試,可以總結(jié)出一個良好的測試方法包含四個步驟,就是上回我們提到的:

  • 設(shè)置:稱為Fixture
  • 執(zhí)行:掉測試方法
  • 斷言:判斷結(jié)果是否預(yù)期
  • 清理:清理測試,相關(guān)例子后續(xù)會看到

極力建議再寫測試過程中中應(yīng)該在每個階段開始時做好注釋。

測試方法命名規(guī)則

Junit4之前,注解,Junit必須采用一種方法來區(qū)分普通的方法和測試方法,當(dāng)時采用的辦法標(biāo)識 以test為前綴的方法名、public、非static、void、無參的方法為測試方法,因此,測試方法名稱都testFoo,testBar諸如此類。

但是Junit設(shè)計師認(rèn)為這種的命名方式有時候無法真實揭示測試方法真實的用意,當(dāng)一個方法對應(yīng)的測試方法多了以后,甚至還會造成一些混亂,因此在Junit采用@Test注解來標(biāo)識測試方法后,Junit取消測試方法名稱必須test開頭的規(guī)定,而是可以隨意命名,但是大部分人還是以test開頭。

后面有人提出 行為表達(dá)式(Behavior-expressing patterns)的命名方式:

[UnitOfWork_StateUnderTest]

下劃線可以用with、if 等此替換。這種方式的作用是希望能讓測試用例和代碼文檔一樣易于閱讀,比如上述的代碼寫久了,回頭去看不知道方法testMultiply在乘積溢出情況下會返回什么,如果你的測試名稱是這么寫的:

multiplyOverflowWithException()

那么看測試方法的人就知道這測試方法,在溢出時會拋異常

集體怎么命名取決與你們的團隊是如何約定,看個人喜好。

總結(jié)

我們討論手工測試自動化后,需要處理的幾個步驟,包括測試方法的讀取與執(zhí)行、測試執(zhí)行環(huán)境的設(shè)置、測試結(jié)果的判斷、測試報告、測試數(shù)據(jù)的管理、測試用例的組織等方面,一般的自動化測試框架要解決也就這幾個方面,對于Junit testNg無不如此。對于Junit 我們將從測試執(zhí)行、斷言、執(zhí)行器、異常測試、測試組織等幾個大方面詳細(xì)介紹它的使用

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,659評論 19 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,282評論 6 342
  • 1.測試與軟件模型 軟件開發(fā)生命周期模型指的是軟件開發(fā)全過程、活動和任務(wù)的結(jié)構(gòu)性框架。軟件項目的開發(fā)包括:需求、設(shè)...
    宇文臭臭閱讀 6,877評論 5 101
  • 1.測試與軟件模型 軟件開發(fā)生命周期模型指的是軟件開發(fā)全過程、活動和任務(wù)的結(jié)構(gòu)性框架。軟件項目的開發(fā)包括:需求、設(shè)...
    Mr希靈閱讀 22,409評論 7 278
  • 簡介 測試 在軟件開發(fā)中是一個很重要的方面,良好的測試可以在很大程度決定一個應(yīng)用的命運。軟件測試中,主要有3大種類...
    Whyn閱讀 6,003評論 0 2

友情鏈接更多精彩內(nèi)容