Android單元測試(三):JUnit單元測試框架的使用

咱們寫單元測試,通常都會用到一個或多個單元測試框架,在這裏,咱們介紹一下JUnit4這個測試框架。這是Java界用的最普遍,也是最基礎的一個框架,其餘的不少框架,包括咱們後面會看到的Robolectric,都是基於或兼容JUnit4的。
然而首先要解決的問題是。。。html

爲何要使用單元測試框架呢

或者換句話說,單元測試框架可以爲咱們作什麼呢?
從最基本的開始提及,假如咱們有這樣一個類:java

public class Calculator {
    public int add(int one, int another) {
        // 爲了簡單起見,暫不考慮溢出等狀況。
        return one + another;
    }

    public int multiply(int one, int another) {
        // 爲了簡單起見,暫不考慮溢出等狀況。
        return one * another;
    }
}

若是不用單元測試框架的話,咱們要怎麼寫測試代碼呢?咱們恐怕得寫出下面這樣的代碼:android

public class CalculatorTest {
    public static void main(String[] args) {
        Calculator calculator = new Calculator();
        int sum = calculator.add(1, 2);
        if(sum == 3) {
            System.out.println("add() works!")
        } else {
            System.out.println("add() does not works!")
        }

        int product = calculator.multiply(2, 4);
        if (product == 8) {
            System.out.println("multiply() works!")
        } else {
            System.out.println("multiply() does not works!")
        }
    }
}

而後咱們再經過某種方式,好比命令行或IDE,運行這個CalculatorTestmain方法,在看着terminal的輸出,才知道測試是經過仍是失敗。想一想一下,若是咱們有不少的類,每一個類都有不少方法,那麼就要寫一堆這樣的代碼,每一個類對於一個含有main方法的test類,同時main方法裏面會有一堆代碼。這樣既寫起來痛苦,跑起來更痛苦,好比說,你怎麼樣一次性跑全部的測試類呢?因此,一個測試框架爲咱們作的最基本的事情,就是容許咱們按照某種更簡單的方式寫測試代碼,把每個測試單元寫在一個測試方法裏面,而後它會自動找出全部的測試方法,而且根據你的須要,運行全部的測試方法,或者是運行單個測試方法,或者是運行部分測試方法等等。
對於上面的Calculator例子,若是使用Junit的話,咱們能夠按照以下的方式寫測試代碼:git

public class CalculatorTest {

    @Test
    public void testAdd() throws Exception {
        Calculator calculator = new Calculator();
        int sum = calculator.add(1, 2);
        Assert.assertEquals(3, sum);
    }

    @Test
    public void testMultiply() throws Exception {
        Calculator calculator = new Calculator();
        int product = calculator.multiply(2, 4);
        Assert.assertEquals(8, product);
    }

}

每個被測試的方法(add(), multiply()),寫一個對應的測試方法(testAdd(), testMultiply())。那JUnit怎麼知道那些是測試方法,哪些不是呢?這個是經過前面的@Test註解來標誌的,只要有這個註解,JUnit4就會當作是一個測試方法,方法名實際上是能夠隨意起的。固然,名字仍是應該起的更有可讀性一點,讓人一看就知道,這個測試方法是測試了被測的類的那個方法,或者是測試了那個功能點等等。
除了幫咱們找出全部的測試方法,而且方便運行意外,單元測試框架還幫咱們作了其餘事情。在這個系列的第一篇文章中咱們提到,一個測試方法主要包括三個部分:github

  1. setup框架

  2. 執行操做ide

  3. 驗證結果函數

而一個單元測試框架,可讓咱們更方便的寫上面的每一步的代碼,尤爲是第一步和第三部。好比說,在上面的CalculatorTest中,testAdd()testMultiply()都有相同的setup: Calculator calculator = new Calculator();,若是Calculator還有其餘的方法的話,這行代碼就得重複更屢次,這種duplication是不必的。絕大多數單元測試框架考慮到了這一點,它們知道一個測試類的不少測試方法可能須要相同的setup,因此爲咱們提供了便捷方法。對於JUnit4,是經過@Before來實現的:單元測試

public class CalculatorTest {
    Calculator mCalculator;

    @Before
    public void setup() {
        mCalculator = new Calculator();
    }

    @Test
    public void testAdd() throws Exception {
        int sum = mCalculator.add(1, 2);
        assertEquals(3, sum);  //爲了簡潔,每每會static import Assert裏面的全部方法。
    }

    @Test
    public void testMultiply() throws Exception {
        int product = mCalculator.multiply(2, 4);
        assertEquals(8, product);
    }

}

若是一個方法被@Before修飾過了,那麼在每一個測試方法調用以前,這個方法都會獲得調用。因此上面的例子中,testAdd()被運行以前,setup()會被調用一次,把mCalculator實例化,接着運行testAdd()testMultiply()被運行以前,setup()又會被調用一次,把mCalculator再次實例化,接着運行testMultiply()。若是還有其餘的測試方法,則以此類推。
對應於@Before的,有一個@After,做用估計你也猜獲得,那就是每一個測試方法運行結束以後,會獲得運行的方法。好比一個測試文件操做的類,那麼在它的測試類中,可能@Before裏面須要去打開一個文件,而每一個測試方法運行結束以後,都須要去close這個文件。這個時候就能夠把文件close的操做放在@After裏面,讓它自動去執行。
相似的,還有@BeforeClass@AfterClass@BeforeClass的做用是,在跑一個測試類的全部測試方法以前,會執行一次被@BeforeClass修飾的方法,執行完全部測試方法以後,會執行一遍被@AfterClass修飾的方法。這兩個方法能夠用來setup和release一些公共的資源,須要注意的是,被這兩個annotation修飾的方法必須是靜態的。測試

前面講的是單元測試框架對於一個測試方法的第一步「setup」,爲咱們作的事情。而對於第三部「驗證結果」,則通常是經過一些assert方法來完成的。JUnit爲咱們提供的assert方法,多數都在Assert這個類裏面。最經常使用的那些以下:

assertEquals(expected, actual)
驗證expected的值跟actual是同樣的,若是是同樣的話,測試經過,否則的話,測試失敗。若是傳入的是object,那麼這裏的對比用的是equals()

assertEquals(expected, actual, tolerance)
這裏傳入的expected和actual是float或double類型的,你們知道計算機表示浮點型數據都有必定的誤差,因此哪怕理論上他們是相等的,可是用計算機表示出來則可能不是,因此這裏運行傳入一個誤差值。若是兩個數的差別在這個誤差值以內,則測試經過,否者測試失敗。

assertTrue(boolean condition)
驗證contidion的值是true

assertFalse(boolean condition)
驗證contidion的值是false

assertNull(Object obj)
驗證obj的值是null

assertNotNull(Object obj)
驗證obj的值不是null

assertSame(expected, actual)
驗證expected和actual是同一個對象,即指向同一個對象

assertNotSame(expected, actual)
驗證expected和actual不是同一個對象,即指向不一樣的對象

fail()
讓測試方法失敗

注意:上面的每個方法,都有一個重載的方法,能夠在前面加一個String類型的參數,表示若是驗證失敗的話,將用這個字符串做爲失敗的結果報告。
好比:
assertEquals("Current user Id should be 1", 1, currentUser.id());
currentUser.id()的值不是1的時候,在結果報道里面將顯示"Current user Id should be 1",這樣可讓測試結果更具備可讀性,更清楚錯誤的緣由是什麼。
比較有意思的是最後一個方法,fail(),你或許會好奇,這個有什麼用呢?其實這個在不少狀況下仍是有用的,好比最明顯的一個做用就是,你能夠驗證你的測試代碼真的是跑了的。
此外,它還有另一個重要做用,那就是驗證某個被測試的方法會正確的拋出異常,不過這點能夠經過下面講到的方法,更方便的作到,因此就不講了。
這部分相對來講仍是很好理解的,不作過多解釋。

JUnit的其餘功能

Ignore一些測試方法

不少時候,由於某些緣由(好比正式代碼尚未實現等),咱們可能想讓JUnit忽略某些方法,讓它在跑全部測試方法的時候不要跑這個測試方法。要達到這個目的也很簡單,只須要在要被忽略的測試方法前面加上@Ignore就能夠了,以下:

public class CalculatorTest {
    Calculator mCalculator;

    @Before
    public void setup() {
        mCalculator = new Calculator();
    }

    // Omit testAdd() and testMultiply() for brevity

    @Test
    @Ignore("not implemented yet")
    public void testFactorial() {
    }
}

驗證方法會拋出某些異常

有的時候,拋出異常是一個方法正確工做的一部分。好比一個除法函數,當除數是0的時候,它應該拋出異常,告訴外界,傳入的被除數是0,示例代碼以下:

public class Calculator {

    // Omit testAdd() and testMultiply() for brevity

    public double divide(double divident, double dividor) {
        if (dividor == 0) throw new IllegalArgumentException("Dividor cannot be 0");

        return divident / dividor;
    }}

那麼如何測試當傳入的除數是0的時候,這個方法應該拋出IllegalArgumentException異常呢?
在Junit中,能夠經過給@Test annotation傳入一個expected參數來達到這個目的,以下:

public class CalculatorTest {
    Calculator mCalculator;

    @Before
    public void setup() {
        mCalculator = new Calculator();
    }

    // Omit testAdd() and testMultiply() for brevity

    @Test(expected = IllegalArgumentException.class)
    public void test() {
        mCalculator.divide(4, 0);
    }

}

@Test(expected = IllegalArgumentException.class)表示驗證這個測試方法將拋出IllegalArgumentException異常,若是沒有拋出的話,則測試失敗。

在Android項目裏面使用JUnit

在Android項目裏面使用JUnit是很簡單的,你只須要將JUnit這個library加到你的dependencies裏面。
testCompile 'junit:junit:4.12'
若是你經過AndroidStudio建立一個項目,這個dependency默認是加上了的,因此你甚至這步均可以省略。
此外,你須要把測試代碼放到src/test/java 目錄下面。
接下來關於怎麼樣運行測試代碼,怎麼樣看結果,請參考這個系列的第一篇文章的相關部分,由於圖比較多,這邊就不重複了。
這裏讓你們看一下運行的結果是什麼樣子的,其中有一個失敗的測試用例是故意的。若是你直接在AndroidStudio裏面跑上面的測試類CalculatorTest的全部測試方法的話,會看到以下的結果:

左邊能夠看到全部的測試方法,以及每一個方法跑出來的結果,綠色表示測試經過的測試方法,黃色的感嘆號或紅色的表示測試失敗的。第三個那個有條紋的球球表示被忽略的測試方法。
若是是經過terminal跑的話,則會看到以下的測試結果:

這篇文章的相關代碼能夠在github的這個project看到。

小結

這篇文字大概簡單介紹了JUnit的使用,相對來講是比較簡單,也是比較容易理解的,但願能幫助到你們。其中Assert部分,能夠幫咱們驗證一個方法的返回結果。然而,這些只能幫咱們測試有返回值的那些方法。在第一篇文章裏面咱們講了,一個類的方法分兩種,一是有返回值的方法,這些能夠經過咱們今天講的JUnit來作測試。而另一種沒有返回值的方法,即void方法,則要經過另一個框架,Mockito,來驗證它的正確性。至於怎麼樣驗證void方法的正確性,以及Mockito的使用,請關注下一篇文章。

最後,若是你也對安卓單元測試感興趣的話,歡迎加入咱們的交流羣:

參考:
http://junit.org/junit4/
http://www.vogella.com/tutorials/JUnit/article.html

做者 小創 更多文章 | Github | 公衆號

相關文章
相關標籤/搜索