1. 程式人生 > >Android單元測試(一):JUnit框架的使用

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’ } 1 2 3 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 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); 1 2 3 4 其中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抽象類,實現matches與describeTo方法。程式碼如下:

public class IsMobilePhoneMatcher extends BaseMatcher {

/**
 * 進行斷言判定,返回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() + "不是手機號碼!");
}

} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 執行單元測試如下:

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

直播系統開發找上海捌躍網路科技有限公司QQ3343874032 錯誤號碼測試失敗:

[email protected]用法 還記得一開始我們在@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 + "測試結束!");
        }
    };
}

} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 我們使用一下我們自定義的MyRule,效果如圖: