一、準備工作
我們新建一個項目,模板代碼會默認在build文件中添加JUnit的依賴,而單元測試代碼是放在src/test/java下面的,如下圖:
用鼠標右鍵點擊測試方法,選擇菜單中的“Run”選項就可以執行對應的單元測試。我執行了圖中的測試代碼,可以看到執行方法只用了6毫秒,整個過程不到2秒。
二、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
三、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.首先測試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抽象類,實現matches與describeTo方法。代碼如下:
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,效果如圖:
PS:計劃開始寫有關Android單元測試的內容,因為涉及的測試框架比較多,所以由簡至難開始,終再達到日常開發實用的階段。小編也盡量快速的更新這一系列。代碼已上傳至Github。希望大家多多點贊支持!
粵嵌科技13年專注IT人才培訓學習的專業機構,主要培訓課程為,嵌入式開發、Java大數據、Unity游戲開發、Python人工智能、HTML5前端開發、全棧UI設計、網絡營銷、CCIE網絡等專業課程