前言
在阅读本文之前,强烈建议仔细阅读上文了解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(),然后,通过自定义构造函数,为每组测试参数赋值,然后调用测试方法进行测试:
/**
* 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"
}
}
参考资料
JUnit4用法详解 http://www.blogjava.net/jnbzwm/archive/2010/12/15/340801.html
参数化测试 http://blog.csdn.net/wangpeng047/article/details/9630203
Junit4总结 http://caizhitao.com/2015/07/20/android-test-use-junit4/
测试源码地址: https://github.com/zhitaocai/UnitTest/tree/master