如果你也正在学习单元测试的相关内容,请看:
Junit是一个单元测试框架。
基本用法
- 测试方法必须用@Test修饰。
- 测试方法必须使用public void修饰,不能带参数。
- 单元测试一般存放在test目录下。
- @Test将一个普通方法修饰成一个测试方法。
- @BeforeClass 注解的方法会在所有方法执行前执行,static方法,全局只会执行一次且第一个执行。
- @AfterClass 注解的方法会在所有方法执行后执行,static方法,全局只会执行一次切最后一个执行。
- @Before 会在每个测试方法被运行前执行一次。
- @After 会在每个测试方法被运行后执行一次。
- @Ignore 修饰的方法会被运行器忽略。
- @RunWith 更改测试运行器org.junit.runner.Runner
1. @Test
Test只能注解public void的方法。
public class Example {
@Test
public void method() {
org.junit.Assert.assertTrue( new ArrayList().isEmpty() );
}
}
Test可指定expected和timeout
@Test(expected=IndexOutOfBoundsException.class)
public void outOfBounds() {
new ArrayList<Object>().get(1);
}
@Test(timeout=100)
public void infinity() {
while(true);
}
2. @Rule
注解@Rule用来注释成员变量或方法,被注释的成员变量必须是public,非static,并且是TestRule(推荐)或MethodRule(已被TestRule取代)的子类型。被注释的方法必须是public,非static,并且返回一个TestRule或MethodRule的子类型。
public static class HasTempFolder {
@Rule
public TemporaryFolder folder= new TemporaryFolder();
@Test
public void testUsingTempFolder() throws IOException {
File createdFile= folder.newFile("myfile.txt");
File createdFolder= folder.newFolder("subfolder");
// ...
}
}
public static class HasTempFolder {
private TemporaryFolder folder= new TemporaryFolder();
@Rule
public TemporaryFolder getFolder() {
return folder;
}
@Test
public void testUsingTempFolder() throws IOException {
File createdFile= folder.newFile("myfile.txt");
File createdFolder= folder.newFolder("subfolder");
// ...
}
}
注解@ClassRule也可以用来注解成员变量或方法,被注解的成员变量必须是public,static,并且是TestRule的子类型。被注解的方法必须是public,static,并且必须返回一个TestRule的子类型。
@Rule和@ClassRule是两个注解,其中@Rule是方法级别的,@ClassRule是类级别的。
@RunWith(Suite.class)
@SuiteClasses({A.class, B.class, C.class})
public class UsesExternalResource {
public static Server myServer= new Server();
@ClassRule
public static ExternalResource resource= new ExternalResource() {
@Override
protected void before() throws Throwable {
myServer.connect();
}
@Override
protected void after() {
myServer.disconnect();
}
};
}
@RunWith(Suite.class)
@SuiteClasses({A.class, B.class, C.class})
public class UsesExternalResource {
public static Server myServer= new Server();
@ClassRule
public static ExternalResource getResource() {
return new ExternalResource() {
@Override
protected void before() throws Throwable {
myServer.connect();
}
@Override
protected void after() {
myServer.disconnect();
}
};
}
}
每个测试方法执行之前都会运行@Rule注解的内容,类似于@Before,
每个测试类执行时都会执行一次被@ClassRule注解的内容,类似于@BeforeClass。
如果存在多个被注解的方法或成员变量,则被注解的方法比成员变量先执行,多个成员变量或多个方法之间的顺序无法确定,不过可通过order指定他们的执行顺序,比如:
public class ThreeRules {
@Rule(order = 0)
public LoggingRule outer = new LoggingRule("outer rule");
@Rule(order = 1)
public LoggingRule middle = new LoggingRule("middle rule");
@Rule(order = 2)
public LoggingRule inner = new LoggingRule("inner rule");
// ...
}
被@Rule或@ClassRule注解的内容必须是TestRule类型的,接下来就学习TestRule的相关内容,MethodRule已经被代替,这里不再学习。
TestRule是一个接口,TestRule能完成之前由@Before, @After, @BeforeClass和@AfterClass注解方法实现的功能,并且它能更方便的在项目或类之间共享。
我们可以自定义TestRule,也可以使用Junit内置的,内置的TestRule如下:
-
TemporaryFolder - 用来在测试中创建文件,并会在测试完成时删除已创建的文件;
public static class HasTempFolder {
@Rule
public TemporaryFolder folder= new TemporaryFolder();
@Test
public void testUsingTempFolder() throws IOException {
File createdFile= folder.newFile("myfile.txt");
File createdFolder= folder.newFolder("subfolder");
// ...
}
}
-
TestName - 用来在测试中获取测试方法的名称;
public class TestNameTest {
@Rule
public TestName name= new TestName();
@Test
public void testA() {
assertEquals("testA", name.getMethodName());
}
@Test
public void testB() {
assertEquals("testB", name.getMethodName());
}
}
-
Timeout - 用来给类中全部的测试方法设置超时时间;
public static class HasGlobalLongTimeout {
@Rule
public Timeout globalTimeout = Timeout.millis(20);
@Test
public void run1() throws InterruptedException {
Thread.sleep(100);
}
@Test
public void infiniteLoop() {
while (true) {}
}
}
设置Timeout后每个测试用例都运行在一个子线程。
-
ErrorCollector - 用来在测试中收集异常,并统一上报;
public static class UsesErrorCollectorTwice {
@Rule
public ErrorCollector collector= new ErrorCollector();
@Test
public void example() {
collector.addError(new Throwable("first thing went wrong"));
collector.addError(new Throwable("second thing went wrong"));
collector.checkThat(getResult(), not(containsString("ERROR!")));
// all lines will run, and then a combined failure logged at the end.
}
}
-
ExpectedException - 支持在测试中抛出指定类型的异常;
public class SimpleExpectedExceptionTest {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void throwsNothing() {
// no exception expected, none thrown: passes.
}
@Test
public void throwsExceptionWithSpecificType() {
thrown.expect(NullPointerException.class);
throw new NullPointerException();
}
}
如果只抛异常,不加ExceptedException逻辑,则test case失败。
-
ExternalResource - 用来测试外部资源的抽象类,比如文件,socket,数据库等,并保证在使用之后关闭它。它是TemporaryFolder的父类。
public static class UsesExternalResource {
Server myServer= new Server();
@Rule
public ExternalResource resource= new ExternalResource() {
@Override
protected void before() throws Throwable {
myServer.connect();
};
@Override
protected void after() {
myServer.disconnect();
};
};
@Test
public void testFoo() {
new Client().run(myServer);
}
}
-
TestWatcher - 用来记录测试用例的执行结果
public static class WatchmanTest {
private static String watchedLog;
@Rule(order = Integer.MIN_VALUE)
public TestWatcher watchman= new TestWatcher() {
@Override
protected void failed(Throwable e, Description description) {
watchedLog+= description + "\n";
}
@Override
protected void succeeded(Description description) {
watchedLog+= description + " " + "success!\n";
}
};
@Test
public void fails() {
fail();
}
@Test
public void succeeds() {
}
}
建议设置TestWatcher的order为Integer.MIN_VALUE,这样它可以覆盖全部的场景。
-
Verifier - 是一个抽象类,可以用来进行测试验证,如果验证失败可以修改验证结果为失败。
public static class ErrorLogVerifier {
private ErrorLog errorLog = new ErrorLog();
@Rule
public Verifier verifier = new Verifier() {
@Override public void verify() {
assertTrue(errorLog.isEmpty());
}
}
@Test public void testThatMightWriteErrorLog() {
// ...
}
}
自定义TestRule
public interface TestRule {
/**
* Modifies the method-running {@link Statement} to implement this
* test-running rule.
*
* @param base The {@link Statement} to be modified
* @param description A {@link Description} of the test implemented in {@code base}
* @return a new statement, which may be the same as {@code base},
* a wrapper around {@code base}, or a completely new Statement.
*/
Statement apply(Statement base, Description description);
}
TestRule是一个接口。
class CustomRule: TestRule {
override fun apply(base: Statement?, description: Description?): Statement {
return CustomStatement(base, description)
}
private class CustomStatement(val base: Statement?, val description: Description?): Statement() {
override fun evaluate() {
println("start: " + description?.methodName)
base?.evaluate()
println("end: " + description?.methodName)
}
}
}
自定义TestRule可以参考内置TestRule的源码,从目前的执行结果看,每个测试用例在执行之前会先执行Statement的evaluate方法,然后是Before,然后是测试用例,最后是After,输出结果如下:
start: setFilterAllTasks_tasksAddViewVisible
Before
setFilterAllTasks
after
end: setFilterAllTasks_tasksAddViewVisible
3. @RunWith
Junit测试用例是在Runner中执行的,通过RunWith,我们可以为测试类指定一个特定的Runner,而不是在内置的Runner中运行测试用例。
Runner是用来执行测试用例的,至于其内部实现,此处不深究。
我们常用的Runner有:Suite,Parameterized,AndroidJUnit4,BlockJUnit4ClassRunner等,下面一一进行学习。
Suite
@RunWith(Suite.class)
@SuiteClasses({ATest.class, BTest.class, CTest.class})
public class ABCSuite {
}
Suite是测试套件的意思,通过它可以指定多个测试类同时执行。
Parameterized
@RunWith(Parameterized.class)
public class AdditionTest {
@Parameters(name = "{index}: {0} + {1} = {2}")
public static Iterable<Object[]> data() {
return Arrays.asList(new Object[][] { { 0, 0, 0 }, { 1, 1, 2 },
{ 3, 2, 5 }, { 4, 3, 7 } });
}
private int firstSummand;
private int secondSummand;
private int sum;
public AdditionTest(int firstSummand, int secondSummand, int sum) {
this.firstSummand = firstSummand;
this.secondSummand = secondSummand;
this.sum = sum;
}
@Test
public void test() {
assertEquals(sum, firstSummand + secondSummand);
}
}
用于使用多个参数组合多次执行同一个测试用例,要有一个 public static 的方法被 @Parameters 标注,并且该方法只能返回 Iterable 类型或数组类型的数据,要有构造函数,参数是传入构造函数的。
也可以使用字段注入来代替构造方法,比如:
@RunWith(Parameterized.class)
public class AdditionTest {
@Parameters(name = "{index}: {0} + {1} = {2}")
public static Iterable<Object[]> data() {
return Arrays.asList(new Object[][] { { 0, 0, 0 }, { 1, 1, 2 },
{ 3, 2, 5 }, { 4, 3, 7 } });
}
@Parameter(0)
public int firstSummand;
@Parameter(1)
public int secondSummand;
@Parameter(2)
public int sum;
@Test
public void test() {
assertEquals(sum, firstSummand + secondSummand);
}
}
AndroidJUnit4
AndroidJUnit4是运行Android测试的Junit4 runner。它支持在Robolectric运行测试用例,支持通过UiThreadTest指定测试用例运行在UI线程,如果运行本地测试,默认是在主线程,如果运行仪器测试,则默认是在子线程。
@RunWith(AndroidJUnit4.class)
Ending
参考: