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

1.前言

網上有許多關於單元測試的好處,這裏我就不去說了。我寫單元測試的理由很簡單粗暴,就是圖一個方便。試想一下這個場景:我們在寫一個新功能,每寫一部分,我們就安裝到手機上查看一下,這個過程中你要點擊到對應的頁面,做對應的操作,最後才能反饋給你結果。如果達到了預期效果,那麼恭喜你。可是一旦這次失敗了,是不是又要重複這一過程?是不是感到很麻煩?很費時間?如果你想早點寫完下班,那麼你就需要掌握單元測試。因爲它能大大的縮短你自我驗證的時間。

2.準備工作

我們新建一個項目,模板代碼會默認在build文件中添加JUnit的依賴,而單元測試代碼是放在src/test/java下面的,如下圖:

這裏寫圖片描述

用鼠標右鍵點擊測試方法,選擇菜單中的“Run”選項就可以執行對應的單元測試。我執行了圖中的測試代碼,可以看到執行方法只用了6毫秒,整個過程不到2秒。

3.JUnit介紹

JUnit是Java最基礎的測試框架,主要的作用就是斷言。

使用時在app的build文件中添加依賴。注意:用於測試環境框架一律是testCompile開頭。

dependencies {
    testCompile 'junit:junit:4.12'
}

Assert類中主要方法如下:

方法名 方法描述
assertEquals 斷言傳入的預期值與實際值是相等的
assertNotEquals 斷言傳入的預期值與實際值是不相等的
assertArrayEquals 斷言傳入的預期數組與實際數組是相等的
assertNull 斷言傳入的對象是爲空
assertNotNull 斷言傳入的對象是不爲空
assertTrue 斷言條件爲真
assertFalse 斷言條件爲假
assertSame 斷言兩個對象引用同一個對象,相當於“==”
assertNotSame 斷言兩個對象引用不同的對象,相當於“!=”
assertThat 斷言實際值是否滿足指定的條件

注意:上面的每一個方法,都有對應的重載方法,可以在前面加一個String類型的參數,表示如果斷言失敗時的提示。


JUnit 中的常用註解:

註解名 含義
@Test 表示此方法爲測試方法
@Before 在每個測試方法前執行,可做初始化操作
@After 在每個測試方法後執行,可做釋放資源操作
@Ignore 忽略的測試方法
@BeforeClass 在類中所有方法前運行。此註解修飾的方法必須是static void
@AfterClass 在類中最後運行。此註解修飾的方法必須是static void
@RunWith 指定該測試類使用某個運行器
@Parameters 指定測試類的測試數據集合
@Rule 重新制定測試類中方法的行爲
@FixMethodOrder 指定測試類中方法的執行順序

執行順序:@BeforeClass –> @Before –> @Test –> @After –> @AfterClass

4.JUnit用法

我們測試下面這個簡單的時間轉換工具類,來說明一下具體的用法。

public class DateUtil {

    /**
     * 英文全稱  如:2017-11-01 22:11:00
     */
    public static String FORMAT_YMDHMS = "yyyy-MM-dd HH:mm:ss";

    /**
     * 掉此方法輸入所要轉換的時間輸入例如("2017-11-01 22:11:00")返回時間戳
     *
     * @param time
     * @return 時間戳
     */
    public static long dateToStamp(String time) throws ParseException{
        SimpleDateFormat sdr = new SimpleDateFormat(FORMAT_YMDHMS, Locale.CHINA);
        Date date = sdr.parse(time);
        return date.getTime();
    }

    /**
     * 將時間戳轉換爲時間
     */
    public static String stampToDate(long lt){
        String res;
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(FORMAT_YMDHMS, Locale.CHINA);
        Date date = new Date(lt);
        res = simpleDateFormat.format(date);
        return res;
    }

}

1.基礎用法

1.首先測試stampToDate方法,測試時我認爲返回的結果等於“預期時間”這個字符串。測試方法執行後如下圖:

這裏寫圖片描述

可以看到預期值與實際結果不符,測試失敗!想要測試成功要麼預期值爲”2017-10-15 16:00:02”要麼使用assertNotEquals方法斷言。

2.接下來測試dateToStamp方法。

這裏寫圖片描述

很簡單,我認爲返回結果不等於4,結果測試通過。

3.我們注意到在dateToStamp方法中,有拋出一個解析異常(ParseException)。也就是當參數沒有按照規定格式去傳,就會導致這個異常。

這裏寫圖片描述

那我們怎麼驗證一個方法是否拋出了異常呢?可以給@Test註解設置expected參數來實現,如下:

這裏寫圖片描述

拋出了對應的異常則測試成功,反之則測試失敗。

2.參數化測試

這時,你是不是覺得還是很麻煩,因爲每次測試一個方法都要去設置對應的值,就不能連續用不不同的值去測試一個方法,省的我們不斷地修改。這時就用到了@RunWith@Parameters

首先在測試類上添加註解@RunWith(Parameterized.class),在創建一個由 @Parameters 註解的public static方法,讓返回一個對應的測試數據集合。最後創建構造方法,方法的參數順序和類型與測試數據集合一一對應。

這裏寫圖片描述

上圖就是一個簡單的例子,可以看到連續執行了三次測試,其中第二次測試沒有拋出異常,測試失敗!

3.assertThat用法

上面我們所用到的一些基本的斷言,如果我們沒有設置失敗時的輸出信息,那麼在斷言失敗時只會拋出AssertionError,無法知道到底是哪一部分出錯。而assertThat就幫我們解決了這一點。它的可讀性更好。

assertThat(T actual, Matcher<? super T> matcher);

assertThat(String reason, T actual, Matcher<? super T> matcher); 

其中reason爲斷言失敗時的輸出信息,actual爲斷言的值,matcher爲斷言的匹配器。

常用的匹配器整理:

匹配器 說明 例子
is 斷言參數等於後面給出的匹配表達式 assertThat(5, is (5));
not 斷言參數不等於後面給出的匹配表達式 assertThat(5, not(6));
equalTo 斷言參數相等 assertThat(30, equalTo(30));
equalToIgnoringCase 斷言字符串相等忽略大小寫 assertThat(“Ab”, equalToIgnoringCase(“ab”));
containsString 斷言字符串包含某字符串 assertThat(“abc”, containsString(“bc”));
startsWith 斷言字符串以某字符串開始 assertThat(“abc”, startsWith(“a”));
endsWith 斷言字符串以某字符串結束 assertThat(“abc”, endsWith(“c”));
nullValue 斷言參數的值爲null assertThat(null, nullValue());
notNullValue 斷言參數的值不爲null assertThat(“abc”, notNullValue());
greaterThan 斷言參數大於 assertThat(4, greaterThan(3));
lessThan 斷言參數小於 assertThat(4, lessThan(6));
greaterThanOrEqualTo 斷言參數大於等於 assertThat(4, greaterThanOrEqualTo(3));
lessThanOrEqualTo 斷言參數小於等於 assertThat(4, lessThanOrEqualTo(6));
closeTo 斷言浮點型數在某一範圍內 assertThat(4.0, closeTo(2.6, 4.3));
allOf 斷言符合所有條件,相當於&& assertThat(4,allOf(greaterThan(3), lessThan(6)));
anyOf 斷言符合某一條件,相當於或 assertThat(4,anyOf(greaterThan(9), lessThan(6)));
hasKey 斷言Map集合含有此鍵 assertThat(map, hasKey(“key”));
hasValue 斷言Map集合含有此值 assertThat(map, hasValue(value));
hasItem 斷言迭代對象含有此元素 assertThat(list, hasItem(element));

下圖爲使用assertThat測試失敗時所顯示的具體錯誤信息。可以看到錯誤信息很詳細!

這裏寫圖片描述

當然了匹配器也是可以自定義的。這裏我自定義一個字符串是否是手機號碼的匹配器來演示一下。

只需要繼承BaseMatcher抽象類,實現matchesdescribeTo方法。代碼如下:

public class IsMobilePhoneMatcher extends BaseMatcher<String> {

    /**
     * 進行斷言判定,返回true則斷言成功,否則斷言失敗
     */

    @Override
    public boolean matches(Object item) {
        if (item == null) {
            return false;
        }

        Pattern pattern = Pattern.compile("(1|861)(3|5|7|8)\\d{9}$*");
        Matcher matcher = pattern.matcher((String) item);

        return matcher.find();
    }

    /**
     * 給期待斷言成功的對象增加描述
     */
    @Override
    public void describeTo(Description description) {
        description.appendText("預計此字符串是手機號碼!");
    }

    /**
     * 給斷言失敗的對象增加描述
     */
    @Override
    public void describeMismatch(Object item, Description description) {
        description.appendText(item.toString() + "不是手機號碼!");
    }
}

執行單元測試如下:

正確的手機號碼測試成功:

這裏寫圖片描述

錯誤號碼測試失敗:

這裏寫圖片描述

5.@Rule用法

還記得一開始我們在@Before@After註解的方法中加入”測試開始”的提示信息嗎?假如我們一直需要這樣的提示,那是不是需要每次在測試類中去實現它。這樣就會比較麻煩。這時你就可以使用@Rule來解決這個問題,它甚至比@Before@After還要強大。

自定義@Rule很簡單,就是實現TestRule 接口,實現apply方法。代碼如下:

public class MyRule implements TestRule {

    @Override
    public Statement apply(final Statement base, final Description description) {

        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                // evaluate前執行方法相當於@Before
                String methodName = description.getMethodName(); // 獲取測試方法的名字
                System.out.println(methodName + "測試開始!");

                base.evaluate();  // 運行的測試方法

                // evaluate後執行方法相當於@After
                System.out.println(methodName + "測試結束!");
            }
        };
    }

}

我們使用一下我們自定義的MyRule,效果如圖:

這裏寫圖片描述

5.參考

PS:計劃開始寫有關Android單元測試的內容,因爲涉及的測試框架比較多,所以由簡至難開始,最終達到日常開發實用的階段。(沒想到這篇前後就用了一整天。。。)我也儘量快速的更新這一系列。代碼已上傳至Github。希望大家多多點贊支持!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章