前言
在閱讀本文之前,強烈建議仔細閱讀上文了解Junit3的一些相關内容,或者提前熟悉Junit3的一些簡單使用規則,這樣學junit4會更容易
1.junit3 和 junit4之間差別
先回顧一下junit3 的特點:
1.需要繼承TestCase
2.需要以test開頭才可以架構識别
3.需要寫較多的代碼完成一個簡單的測試
4.初始化和回收代碼在每個測試方法之前調用,一些方法其實可以統一初始化的,而
5.無需每次測試前都初始化一遍,也即不支援類初始化
Junit4是在junit3的基礎上進行擴充,增加一些java 1.5的新特性,而最大的就是支援注解方式,簡化備援代碼量,更容易寫測試代碼。
1.1 寫法
首先需要引入junit4的測試環境,(這些新版IDE工具已經幫你完成了)
本文使用 android studio 2.2.3 ,gradle com.android.tools.build:gradle:2.2.3,建立工程自動引入單元測試依賴 junit:junit:4.12,需要注意的是系統sdk中內建的是 junit3,需要用新的測試架構,需要自動或者手動依賴 junit 4的jar
apply plugin: 'com.android.application'
android {
compileSdkVersion 25
buildToolsVersion "25.0.1"
defaultConfig {
applicationId "nuoyuan.com.myapplication"
minSdkVersion 16
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.1.0'
testCompile 'junit:junit:4.12'
在這裡進行一個對比(使用自動生成的方式,如何自動生成,上篇bolg有介紹)
Junit3
package nuoyuan.com.myapplication.junit3;
import junit.framework.TestCase;
/**
* Created by weichyang on 2017/1/16.
* junit 3單元測試
*/
public class MathUtilsTest extends TestCase {
public void testAdd() throws Exception {
}
public void testJian() throws Exception {
}
public void testCheng() throws Exception {
}
public void testChu() throws Exception {
}
Junit4
package nuoyuan.com.myapplication.junit4;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Created by weichyang on 2017/1/16.
*/
public class MathUtilsTest {
@Before
public void setUp() throws Exception {
}
@After
public void tearDown() throws Exception {
}
@Test
public void add() throws Exception {
}
@Test
public void jian() throws Exception {
}
@Test
public void cheng() throws Exception {
}
@Test
public void chu() throws Exception {
}
}
具體的寫法就可以在具體的測試方法的中調用。這裡不進行贅述……..
1.2 易用性
1.首先從結構上面看,很明顯junit4的結構更加清晰明了,命名也更加靈活。
2.使用注解,讓測試代碼寫起來也更加得心應手,友善自不用說,用過的人都說好。
3.junit增加一些新的測試Api,既然是junit3的一個擴充,在相容junit3現有功能上又增加 幾個常用的測試Api。
2.junit4常用Api介紹
這裡依然使用上面的生成代碼進行介紹
@Before @After
Junit3中,我們重寫TestCase的setUp和tearDown方法就可以實作每個測試方法測試前後的初始化和回收,Junit4中,僅需要在方法前面加上@Before、@After即可快速實作相同的功能:
ackage nuoyuan.com.myapplication.junit4;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import nuoyuan.com.myapplication.utils.MathUtils;
/**
* Created by weichyang on 2017/1/16.
*/
public class MathUtilsTest {
@Before
public void setUp() throws Exception {
System.out.print("start 執行\n");
}
@After
public void tearDown() throws Exception {
System.out.print("end 執行 \n");
}
@Test
public void add() throws Exception {
System.out.print("+++++++++++++++++++++++++++++++add 執行\n");
Assert.assertEquals(2, MathUtils.add(1, 1));
}
@Test
public void jian() throws Exception {
System.out.print("--------------------------------jian 執行\n");
Assert.assertEquals(2, MathUtils.jian(3, 1));
}
@Test
public void cheng() throws Exception {
}
@Test
public void chu() throws Exception {
}
}
運作結果:
@BeforeClass @AfterClass
隻需要在測試類中添加相應的方法,并在前面添加相應的注解就可以實作類參數初始化任務(添加@BeforeClass @AfterClass 注解的方法應該被static進行修飾)
import nuoyuan.com.myapplication.utils.MathUtils;
/**
* Created by weichyang on 2017/1/16.
*/
public class MathUtilsTest {
@BeforeClass
public static void beforeClass() throws Exception {
System.out.print("beforeClass 執行\n");
}
@AfterClass
public static void afterClass() throws Exception {
System.out.print("afterClass 執行\n");
}
...
...
...
}
結果
@Ignore
import nuoyuan.com.myapplication.utils.MathUtils;
/**
* Created by weichyang on 2017/1/16.
*/
public class MathUtilsTest {
@BeforeClass
public static void beforeClass() throws Exception {
System.out.print("beforeClass 執行\n");
}
@AfterClass
public static void afterClass() throws Exception {
System.out.print("afterClass 執行\n");
}
@Test
public void ignore() {
System.out.print("ignore 執行\n");
Assert.assertEquals(3, MathUtils.add(1, 1));
}
...
...
...
}
注意上面Ignore()方法是個異常
現在我們修改相應的注解@Ignore
mport nuoyuan.com.myapplication.utils.MathUtils;
/**
* Created by weichyang on 2017/1/16.
*/
public class MathUtilsTest {
@BeforeClass
public static void beforeClass() throws Exception {
System.out.print("beforeClass 執行\n");
}
@AfterClass
public static void afterClass() throws Exception {
System.out.print("afterClass 執行\n");
}
@Ignore
public void ignore() {
System.out.print("ignore 執行\n");
Assert.assertEquals(3, MathUtils.add(1, 1));
}
...
...
...
}
運作
即使該方法測試不通過,添加@Ignore 注解,整體測試不會受到影響
異常測試
由于時間限制,不再重複造輪子。這裡引用這邊blog作者的總結,這裡表示感謝
package com.czt.saisam.unittest.util.junit4;
import junit.framework.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import java.util.ArrayList;
import java.util.List;
import static org.hamcrest.core.Is.is;
/**
* 某些場合下,方法會抛出異常,但是并不是說抛出異常就表示方法出現問題,比如參數不合法異常,就表示參數傳入有問題,方法是沒有問題的,是以我們需要捕捉這種"正常"的異常
* <p/>
* 無論是expected還是expect都表示期望抛出的異常,
* 假如某一方法,當參數為某一值時會抛出異常,
* 第一種方法:必須為該參數單獨寫一個測試方法來測試異常,而無法與其他參數值一同寫在一個測試方法裡,是以顯得累贅。
* 第二種方法:習慣上是這樣寫,進行捕獲然後抛出。
* 第三種方法:不僅能動态更改期望抛出的異常,與斷言語句結合的也非常好,是以推薦使用該方法來測試異常。
*
* @author zhitao
* @since 2015-07-06 23:43
*/
public class ExceptionJunit4TestCase {
/**
* 第一種異常捕捉測試
*/
@Test(expected = IndexOutOfBoundsException.class)
public void empty() {
new ArrayList<Object>().get(0);
}
/**
* 第二種異常捕捉測試
*/
@Test
public void testExceptionMessage() {
try {
new ArrayList<Object>().get(1);
Assert.fail("Expected an IndexOutOfBoundsException to be thrown");
} catch (IndexOutOfBoundsException anIndexOutOfBoundsException) {
org.junit.Assert.assertThat(anIndexOutOfBoundsException.getMessage(), is("Index: 0, Size: 0"));
}
}
// 第三種異常捕捉測試
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void shouldTestExceptionMessage() throws IndexOutOfBoundsException {
List<Object> list = new ArrayList<Object>();
thrown.expect(IndexOutOfBoundsException.class);
thrown.expectMessage("Index: 0, Size: 0");
list.get(0);
Assert.assertEquals(1, list.get(0));
}
}
運作,如果符合的自己預期的異常被捕獲,測試通過,否則抛出異常測試不通過
運作
限時測試
顧名思義就是在規定的時間内完成測試,否則測試不通過,使用場景就是測試異步任務等一些的需要不同線程配合的任務
package com.czt.saisam.unittest.util.junit4;
import com.czt.saisam.unittest.util.AsyncTask;
import junit.framework.Assert;
import org.junit.Test;
/**
* by weichayang
*/
public class AsyncTaskJunit4TestCase {
@Test(timeout = 3000)
public void sync_getOnlineConfig() {
Assert.assertEquals("value1", new AsyncTask().sync_getOnlineConfig("key1"));
}
}
測試的方法
package com.czt.saisam.unittest.util;
/**
* by weichaoyang
*/
public class AsyncTask {
public AsyncTask() {
super();
}
public String sync_getOnlineConfig(String key) {
// 采用線程睡眠模拟耗時操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if ("key1".equals(key)) {
return "value1";
}
return null;
}
public void async_getOnlineConfig(String key, onFinishListener listener) {
String value = sync_getOnlineConfig(key);
if (listener != null) {
listener.onFinish(key, value);
}
}
public interface onFinishListener {
void onFinish(String key, String value);
}
}
結果:
異常,修改時間為500ms
public class AsyncTaskJunit4TestCase {
@Test(timeout = 500)
public void sync_getOnlineConfig() {
Assert.assertEquals("value1", new AsyncTask().sync_getOnlineConfig("key1"));
}
}
結果
@RunWith
在上文Junit3使用中,我們說到,Junit的運作模式: TestCase -> TestSuite -> TestRunner ==> TestResult,但是上文并沒有怎麼講到TestRunner,這是因為在Junit4中講解比較容易了解。
參數化測試 @Parameterized
在Junit3中的參數化測試,不斷copy同一個測試方法進行判斷類似:
Assert.assertEquals(2, MathUtils.add(1, 1));
Assert.assertEquals(2, MathUtils.add(2, 1));
Assert.assertEquals(2, MathUtils.add(3, 1));
Assert.assertEquals(2, MathUtils.add(4, 1));
而在JUnit4中可以更加優雅的進行測試,隻需要使用參數化注解進行修飾就可以
同時,将所有的參數列為一個數組,并修飾為@Parameterized.Parameters(),然後,通過自定義構造函數,為每組測試參數指派,然後調用測試方法進行測試:該類必須提供至少三個實體:
1.一種生成和傳回測試資料的靜态方法,
2.存儲測試資料的單個構造函數,和
3.一個測試。
生成的測試資料的方法,必須以進行注釋@Parameters,并且它必須傳回陣列的集合。每個數組表示在特定測試運作中使用的資料。每個數組中的元素數量必須與類的構造函數中的參數數量相對應,因為每個數組元素都将被傳遞給構造函數,一次一個,因為類被反複執行個體化。
當調用測試運作器時,将執行資料生成方法,并且它将傳回數組集合,其中每個數組是一組測試資料。然後測試運作器将執行個體化類并将第一組測試資料傳遞給構造函數。構造函數将在其字段中存儲資料。然後将執行每個測試方法,并且每個測試方法将通路該第一組測試資料。在每個測試方法執行後,對象将被再次執行個體化,這次使用數組集合中的第二個元素,依此類推。
/**
* by weichaoyang
*/
@RunWith(Parameterized.class)
public class StringUtilJunit4TestCase {
@Parameterized.Parameters()
public static Iterable<Object[]> data() {
return Arrays.asList(new Object[][] {
{true, 123}, {true, new String[] {null, null}}, {true, new String[] {""}}, {true, new String[] {"", ""}},
{false, new String[] {"123"}}, {true, new String[] {"123", ""}}, {false, new String[] {"23", "6"}}
});
}
private boolean mExpected=true;
private String[] mArgs;
public StringUtilJunit4TestCase(boolean expected, String... args) {
mExpected = expected;
mArgs = args;
}
@Test
public void isStringNull() throws Exception {
Assert.assertEquals(mExpected, StringUtil.isStringNull(mArgs));
}
}
注意:必須要有對應參數順序個構造函數,否則測試不通過
打包/套件測試 @Site.SuiteClasses
在Junit3中,我們定義一個TestSuite,在運作時,預設是運作在TestSuite中
而在junit4中需要使用注解進行聲明
@RunWith(Suite.class) 指定測試環境
@Suite.SuiteClasses({MathUtilJunit4TestCase.class, StringUtilJunit4TestCase.class}) 指定包含測試的值
ackage com.czt.saisam.unittest.util.junit4;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
/**
* Junit4
*
* @author byweichyang
* @since
*/
@RunWith(Suite.class)
@Suite.SuiteClasses({MathUtilJunit4TestCase.class, StringUtilJunit4TestCase.class})
public class Junit4TestSuite {
}
指定測試方法執行順序 @FixMethodOrde
package com.czt.saisam.unittest.util.junit4;
import org.junit.Test;
/**
* Created by weichyang on 2017/1/16.
*/
public class order {
@Test
public void a() {
System.out.print("RUN A \n");
}
@Test
public void b() {
System.out.print("RUN b \n");
}
@Test
public void c() {
System.out.print("RUN c \n");
}
@Test
public void d() {
System.out.print("RUN d \n");
}
@Test
public void e() {
System.out.print("RUN e \n");
}
}
預設情況
指定順序常量有三種 可以一一測試,這裡不進行贅述。
指令行運作測試
可能有些大神會選擇指令行模式進行單元腳本執行,這裡進行簡單的介紹
通過運作 ./gradlew test 即可運作項目的單元測試用例
測試報告
生成必要的測試報告是很有必要的。
這裡需要導入相應的包,預設的junit是不會産生測試報告的
也可以在 build.gradle 檔案中重新制定測試報告的位置
android {
testOptions {
// 預設測試報告路徑build/reports/androidTests/
// 可以通過下面代碼自定義測試路徑
resultsDir = "${project.buildDir}/testReport"
}
}
參考資料