当前位置: 首页 > 工具软件 > AndroidJUnit4 > 使用案例 >

junit4 测试android,关于使用JUnit 4测试Android程序的一点分析

甘西岭
2023-12-01

JUnit 4是一种流行的Java单元测试工具。使用JUnit 4和androidx.test包可以为Android程序编写单元测试和Instrument测试。下面我对JUnit 4在Android程序上的使用做一些简单的分析。

JUnit 4中的3个核心概念

首先简单介绍一下JUnit 4。JUnit 4是一个单元测试框架,简单来说,JUnit 4的工作就是运行单元测试,然后将结果展示给用户。在这个过程中涉及3个核心概念:表示单元测试的Statement、运行单元测试的Runner,以及接收测试数据,展示结果的RunNotifier。

下面的代码摘录自org.junit.runners.ParentRunner,这段代码很好的展示了3个概念之间的关系:Runner运行Statement,将结果通知给RunNotifier。

@Override

public void run(final RunNotifier notifier) {

EachTestNotifier testNotifier = new EachTestNotifier(notifier, getDescription());

try {

Statement statement = classBlock(notifier);

statement.evaluate();

} catch (AssumptionViolatedException e) {

testNotifier.addFailedAssumption(e);

} catch (StoppedByUserException e) {

throw e;

} catch (Throwable e) {

testNotifier.addFailure(e);

}

}

Runner

org.junit.runner.Runner是一个抽象类,拥有两个抽象方法:

public abstract Description getDescription();

public abstract void run(RunNotifier notifier);

通常我们不会直接接触到Runner类。如果想自己编写Runner,可以从org.junit.runners.BlockJUnit4ClassRunner派生。BlockJUnit4ClassRunner也是JUnit 4默认的Runner类。如果不想使用默认Runner,可以在测试类上添加注解@RunWith,设置测试需要的Runner。在测试Android程序时,通常会加上

@RunWith(AndroidJUnit4::class)

这行代码会使用androidx.test.ext.junit.runners.AndroidJUnit4作为Runner。AndroidJUnit4是一个包装类,它会检查系统属性java.runtime.name,如果其中包含字符串android,AndroidJUnit4会使用androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner作为实际的Runner。否则将使用org.robolectric.RobolectricTestRunner。而AndroidJUnit4ClassRunner正式派生自BlockJUnit4ClassRunner。

这里说一下@RunWith生效的过程。在运行测试的时候(比如Gradle使用的org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor任务),首先通过org.junit.runner.Request得到ClassRequest对象,ClassRequest内部通过AllDefaultPossibilitiesBuilder按照下列顺序

ignoredBuilder

annotatedBuilder

suiteMethodBuilder

junit3Builder

junit4Builder

逐一寻找合适的RunnerBuilder,然后构建出Runner。听起来很复杂,实际上只需两行代码:

final Request request = Request.aClass((Class)testClass);

final Runner runner = request.getRunner();

Statement

Statement表示一个测试用例,测试用例只有一个动作:执行。因此Statement类非常简单:

public abstract class Statement {

public abstract void evaluate() throws Throwable;

}

如果测试失败,Statement抛出异常。如果执行成功,no news is good news。Statement虽然简洁,通过组合可以构造出非常丰富的用法。比如下面这段代码

protected Statement withBefores(FrameworkMethod method, Object target, Statement statement) {

List befores = getTestClass().getAnnotatedMethods(Before.class);

return befores.isEmpty() ? statement : new RunBefores(statement, befores, target);

}

这段代码摘自BlockJUnit4ClassRunner类。熟悉JUnit 4的人一看就会明白,这是处理@Before注解的地方。实际上,根据这段代码,大家应该就可以猜出RunBefores类大致是什么样子了。为了例子的完整性,我们把RunBefores类的代码也展示在这里。

package org.junit.internal.runners.statements;

import java.util.List;

import org.junit.runners.model.FrameworkMethod;

import org.junit.runners.model.Statement;

public class RunBefores extends Statement {

private final Statement next;

private final Object target;

private final List befores;

public RunBefores(Statement next, List befores, Object target) {

this.next = next;

this.befores = befores;

this.target = target;

}

@Override

public void evaluate() throws Throwable {

for (FrameworkMethod before : befores) {

before.invokeExplosively(target);

}

next.evaluate();

}

}

如同上面的@Before例子,JUnit 4的很多特性都是通过Statement组合实现的。下面这段代码也是BlockJUnit4ClassRunner的一部分,从这里我们可以看出,@Rule也是通过封装Statement实现的。

protected Statement methodBlock(FrameworkMethod method) {

Object test;

try {

test = new ReflectiveCallable() {

@Override

protected Object runReflectiveCall() throws Throwable {

return createTest();

}

}.run();

} catch (Throwable e) {

return new Fail(e);

}

Statement statement = methodInvoker(method, test);

statement = possiblyExpectingExceptions(method, test, statement);

statement = withPotentialTimeout(method, test, statement);

statement = withBefores(method, test, statement);

statement = withAfters(method, test, statement);

statement = withRules(method, test, statement);

return statement;

}

为了方便读者参考,我们把org.junit.rules.TestRule接口和RunRules类贴在这里。

public interface TestRule {

Statement apply(Statement base, Description description);

}

package org.junit.rules;

import org.junit.runner.Description;

import org.junit.runners.model.Statement;

public class RunRules extends Statement {

private final Statement statement;

public RunRules(Statement base, Iterable rules, Description description) {

statement = applyAll(base, rules, description);

}

@Override

public void evaluate() throws Throwable {

statement.evaluate();

}

private static Statement applyAll(Statement result, Iterable rules,

Description description) {

for (TestRule each : rules) {

result = each.apply(result, description);

}

return result;

}

}

@BeforeClass注解也使用了Statement,下面的代码来自ParentRunner。ParentRunner是BlockJUnit4ClassRunner的父类。

protected Statement classBlock(final RunNotifier notifier) {

Statement statement = childrenInvoker(notifier);

if (!areAllChildrenIgnored()) {

statement = withBeforeClasses(statement);

statement = withAfterClasses(statement);

statement = withClassRules(statement);

}

return statement;

}

RunNotifier

Runner负责执行测试用例,测试的结果通知给RunNotifier。RunNotifier是一个RunListener集合,测试信息最终由RunListener处理。我们运行单元测试时,控制台输出的信息就是由TextListener生成的。

public class RunListener {

public void testRunStarted(Description description) throws Exception {}

public void testRunFinished(Result result) throws Exception {}

public void testStarted(Description description) throws Exception {}

public void testFinished(Description description) throws Exception {}

public void testFailure(Failure failure) throws Exception {}

public void testAssumptionFailure(Failure failure) {}

public void testIgnored(Description description) throws Exception {}

}

在运行测试时,ParentRunner负责将测试事件通知给RunNotifier。

protected final void runLeaf(Statement statement, Description description, RunNotifier notifier) {

EachTestNotifier eachNotifier = new EachTestNotifier(notifier, description);

eachNotifier.fireTestStarted();

try {

statement.evaluate();

} catch (AssumptionViolatedException e) {

eachNotifier.addFailedAssumption(e);

} catch (Throwable e) {

eachNotifier.addFailure(e);

} finally {

eachNotifier.fireTestFinished();

}

}

ParentRunner和BlockJUnit4ClassRunner

在前面介绍Runner、Statement和RunNotifier时,已经不止一次提到了BlockJUnit4ClassRunner和ParentRunner。这是两个非常重要的类,有了前面的基础,在这里我们可以做一些深入分析。

ParentRunner是一个抽象类,它有两个重要的函数:run和runLeaf,这两个函数在前面已经介绍过了。ParentRunner还有两个重要接口:

protected abstract void runChild(T child, RunNotifier notifier);

protected abstract List getChildren();

runChild负责执行一个测试方法。正如方法classBlock所暗示的,ParentRunner运行一个测试类。ParentRunner将一组测试方法看作自己的child。child通过getChildren获得。ParentRunner将各个child所代表的测试用例通过childrenInvoker封装成一个Statement,在加上@BeforeClass和@AfterClass,构造成最终的Statement。

@Override

public void run(final RunNotifier notifier) {

EachTestNotifier testNotifier = new EachTestNotifier(notifier, getDescription());

try {

Statement statement = classBlock(notifier);

statement.evaluate();

} catch (AssumptionViolatedException e) {

testNotifier.addFailedAssumption(e);

} catch (StoppedByUserException e) {

throw e;

} catch (Throwable e) {

testNotifier.addFailure(e);

}

}

protected Statement classBlock(final RunNotifier notifier) {

Statement statement = childrenInvoker(notifier);

if (!areAllChildrenIgnored()) {

statement = withBeforeClasses(statement);

statement = withAfterClasses(statement);

statement = withClassRules(statement);

}

return statement;

}

protected Statement childrenInvoker(final RunNotifier notifier) {

return new Statement() {

@Override

public void evaluate() {

runChildren(notifier);

}

};

}

private void runChildren(final RunNotifier notifier) {

final RunnerScheduler currentScheduler = scheduler;

try {

for (final T each : getFilteredChildren()) {

currentScheduler.schedule(new Runnable() {

public void run() {

ParentRunner.this.runChild(each, notifier);

}

});

}

} finally {

currentScheduler.finished();

}

}

BlockJUnit4ClassRunner派生自ParentRunner。同样如方法methodBlock所暗示,BlockJUnit4ClassRunner更关注测试方法层面的工作:根据注解寻找测试方法,并将测试方法封装成Statement。

@Override

protected void runChild(final FrameworkMethod method, RunNotifier notifier) {

Description description = describeChild(method);

if (isIgnored(method)) {

notifier.fireTestIgnored(description);

} else {

runLeaf(methodBlock(method), description, notifier);

}

}

protected Statement methodBlock(FrameworkMethod method) {

Object test;

try {

test = new ReflectiveCallable() {

@Override

protected Object runReflectiveCall() throws Throwable {

return createTest();

}

}.run();

} catch (Throwable e) {

return new Fail(e);

}

Statement statement = methodInvoker(method, test);

statement = possiblyExpectingExceptions(method, test, statement);

statement = withPotentialTimeout(method, test, statement);

statement = withBefores(method, test, statement);

statement = withAfters(method, test, statement);

statement = withRules(method, test, statement);

return statement;

}

这里简单说明一下methodBlock方法。FrameworkMethod代表一个Java方法,也就是@Test测试方法。methodBlock首先建立测试类实例,然后用methodInvoker将测试方法和类实例封装成Statement,再加上@Before等注解,构造出完成的Statement。

AndroidJUnit4ClassRunner和UIThreadStatement

现在介绍一下Android单元测试中遇到的AndroidJUnit4ClassRunner。AndroidJUnit4ClassRunner派生自BlockJUnit4ClassRunner,核心代码是重写了方法methodInvoker。

@Override

protected Statement methodInvoker(FrameworkMethod method, Object test) {

if (UiThreadStatement.shouldRunOnUiThread(method)) {

return new UiThreadStatement(super.methodInvoker(method, test), true);

}

return super.methodInvoker(method, test);

}

我们知道methodInvoker将测试方法封装为Statement,AndroidJUnit4ClassRunner封装的UIThreadStatement是做什么用的呢?顾名思义,在UI线程中测试。

@Override

public void evaluate() throws Throwable {

if (runOnUiThread) {

final AtomicReference exceptionRef = new AtomicReference<>();

runOnUiThread(

new Runnable() {

@Override

public void run() {

try {

base.evaluate();

} catch (Throwable throwable) {

exceptionRef.set(throwable);

}

}

});

Throwable throwable = exceptionRef.get();

if (throwable != null) {

throw throwable;

}

} else {

base.evaluate();

}

}

shouldRunOnUiThread的判断标准也很简单,检查是否有UiThread注解。

public static boolean shouldRunOnUiThread(FrameworkMethod method) {

Class extends Annotation> deprecatedUiThreadTestClass = loadUiThreadClass("android.test.UiThreadTest");

if (hasAnnotation(method, deprecatedUiThreadTestClass)) {

return true;

} else {

// to avoid circular dependency on Rules module use the class name directly

@SuppressWarnings("unchecked") // reflection

Class extends Annotation> uiThreadTestClass = loadUiThreadClass("androidx.test.annotation.UiThreadTest");

if (hasAnnotation(method, deprecatedUiThreadTestClass)

|| hasAnnotation(method, uiThreadTestClass)) {

return true;

}

}

return false;

}

AndroidTestRule和AndroidStatement

对于Instrument测试,还需要在测试类中声明ActivityTestRule。

@get:Rule

val activityRule = ActivityTestRule(MainActivity::class.java)

ActivityTestRule将测试方法封装为AndroidStatement。AndroidStatement在运行前发送指令到设备,启动应用。在测试后关闭应用。

@Override

public void evaluate() throws Throwable {

MonitoringInstrumentation instrumentation =

ActivityTestRule.this.instrumentation instanceof MonitoringInstrumentation

? (MonitoringInstrumentation) ActivityTestRule.this.instrumentation

: null;

try {

if (activityFactory != null && instrumentation != null) {

instrumentation.interceptActivityUsing(activityFactory);

}

if (launchActivity) {

launchActivity(getActivityIntent());

}

base.evaluate();

} finally {

if (instrumentation != null) {

instrumentation.useDefaultInterceptingActivityFactory();

}

T hardActivityRef = activity.get();

if (hardActivityRef != null) {

finishActivity();

}

activityResult = null;

ActivityLifecycleMonitorRegistry.getInstance().removeLifecycleCallback(lifecycleCallback);

}

}

androidx包中的其他规则

除了AndroidTestRule,androidx还提供了下列规则:

规则

说明

GrantPermissionRule

运行测试方法前申请权限。

ProviderTestRule

测试ContentProvider。

ServiceTestRule

测试服务。

PortForwardRule

转发端口。

分析这些规则,只要看apply方法返回了什么Statement,以及这些Statement的evaluate做了什么。

 类似资料: