Junit 基础元素
最近阅读了JUnit 的源代码,记录一下。首先我画了一张JUnit 基础元素图如下:
基本上涵盖了JUnit 的核心组件和类。下面我们会选一部分比较重要的元素来作一些解释。
Runner
首先是 Runner 类,Runner是用来运行测试的主要类,下图是Runner的继承结构:
其中 ParentRunner是一个比较重要的Runner,里面承载了很多测试业务的代码。读者可以详细阅读 ParentRunner。好了,我们看一下 Runner的抽象基类:
public abstract class Runner implements Describable {
/*
* (non-Javadoc)
* @see org.junit.runner.Describable#getDescription()
*/
public abstract Description getDescription();
/**
* Run the tests for this runner.
*
* @param notifier will be notified of events while tests are being run--tests being
* started, finishing, and failing
*/
public abstract void run(RunNotifier notifier);
/**
* @return the number of tests to be run by the receiver
*/
public int testCount() {
return getDescription().testCount();
}
}
其中的 run 方法是用来跑测试的方法,run 方法中有一个 RunNotifier 的参数,主要是用来通知测试的生命周期我们下面会提到它,以 ParentRunner来作例子,run 方法如下:
public abstract class ParentRunner<T> extends Runner implements Filterable,
Orderable {
private final TestClass testClass;
...
@Override
public void run(final RunNotifier notifier) {
EachTestNotifier testNotifier = new EachTestNotifier(notifier,
getDescription());
testNotifier.fireTestSuiteStarted();
try {
Statement statement = classBlock(notifier);
statement.evaluate();
} catch (AssumptionViolatedException e) {
testNotifier.addFailedAssumption(e);
} catch (StoppedByUserException e) {
throw e;
} catch (Throwable e) {
testNotifier.addFailure(e);
} finally {
testNotifier.fireTestSuiteFinished();
}
}
...
protected Statement classBlock(final RunNotifier notifier) {
Statement statement = childrenInvoker(notifier);
if (!areAllChildrenIgnored()) {
statement = withBeforeClasses(statement);
statement = withAfterClasses(statement);
statement = withClassRules(statement);
statement = withInterruptIsolation(statement);
}
return statement;
}
...
}
在上述代码中 testClass 是类型为 TestClass的对象,包装了被测试类,后面会提到。可以看到,在run方法中实际上是运行 Statement 的evaluate,主要使用了责任链的设计模式,Runner 会创建一系列的 Statement,以BlockJUnit4ClassRunner为例子:
protected Statement methodBlock(final FrameworkMethod method) {
...
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);
statement = withInterruptIsolation(statement);
return statement;
}
可以看到我们测试时候常用的一些生命周期,比如 @Before, @After 以及我们在测试中设置的一些 @get:Rule,都会通过解析Annotation之后生成 statement 来串行的运行。
TestClass
TestClass 实现 Annotatable 接口,提供方法校验和注解搜索的功能。
public interface Annotatable {
/**
* Returns the model elements' annotations.
*/
Annotation[] getAnnotations();
/**
* Returns the annotation on the model element of the given type, or @code{null}
*/
<T extends Annotation> T getAnnotation(Class<T> annotationType);
}
Runner 会invoke其中的getAnnotatedMethods来获取被测试类中的标注方法。以 @BeforeClass 标注为例子:在运行 Runner 的 run 方法时,会调用withBeforeClasses 函数如下:
protected Statement withBeforeClasses(Statement statement) {
List<FrameworkMethod> befores = testClass
.getAnnotatedMethods(BeforeClass.class);
return befores.isEmpty() ? statement :
new RunBefores(statement, befores, null);
}
在这个函数里调用了 getAnnotatedMethods 方法参数是 BeforeClass.class,这样就获取到了被测试类中被 @BeforeClass标注的方法, 然后将其封装在RunBefores类中, RunBefores 是一个Statement的子类其中的evaluate方法会调用个测试类中被@BeforeClasse 的方法。
RunNotifier
RunNotifier 是负责通知测试的生命周期回调。其中有一个私有的抽象内部类 SafeNotifier. 并且提供了一系列的生命周期回调方法,在对应的时机调用:
public class RunNotifier {
private final List<RunListener> listeners = new CopyOnWriteArrayList<RunListener>();
...
public void fireTestRunFinished(final Result result) {
new SafeNotifier() {
@Override
protected void notifyListener(RunListener each) throws Exception {
each.testRunFinished(result);
}
}.run();
}
...
public void fireTestSuiteStarted(final Description description) {
new SafeNotifier() {
@Override
protected void notifyListener(RunListener each) throws Exception {
each.testSuiteStarted(description);
}
}.run();
}
}
调用位置以JUnitCore为例子: notifier.fireTestRunStarted(runner.getDescription());
public Result run(Runner runner) {
Result result = new Result();
RunListener listener = result.createListener();
notifier.addFirstListener(listener);
try {
notifier.fireTestRunStarted(runner.getDescription());
runner.run(notifier);
notifier.fireTestRunFinished(result);
} finally {
removeListener(listener);
}
return result;
}
我们可以看到在开始运行测试的时候,通过调用 notifier 的 fireTestRunStarted 来通知测试开始。
Statement
Statement是一个抽象基类:有一些具体实现类比如 RunAfters, RunRules.
public abstract class Statement {
/**
* Run the action, throwing a {@code Throwable} if anything goes wrong.
*/
public abstract void evaluate() throws Throwable;
}
我们以RunAfters 为例子:
public class RunAfters extends Statement {
private final Statement next;
private final Object target;
private final List<FrameworkMethod> afters;
public RunAfters(Statement next, List<FrameworkMethod> afters, Object target) {
this.next = next;
this.afters = afters;
this.target = target;
}
@Override
public void evaluate() throws Throwable {
List<Throwable> errors = new ArrayList<Throwable>();
try {
next.evaluate();
} catch (Throwable e) {
errors.add(e);
} finally {
for (FrameworkMethod each : afters) {
try {
invokeMethod(each);
} catch (Throwable e) {
errors.add(e);
}
}
}
MultipleFailureException.assertEmpty(errors);
}
/**
* @since 4.13
*/
protected void invokeMethod(FrameworkMethod method) throws Throwable {
method.invokeExplosively(target);
}
}
其中 next 指向责任链的下一个节点。不难看出 afters 方法在 finally 代码块中执行,即无论测试是失败还是成功都会run @After 标注的代码。
TestRule
我们在测试的时候经常会遇到需要使用 TestRule 的情况:
比如在测试koltin 的协程代码的时候就会遇到需要添加自定义的Rule:
@get:Rule
val testCoroutineRule = TestCoroutineRule()
自定义或者JUnit 内部定义的一系例Rule 都是继承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);
}
其中的 apply 方法是用来基于 base Statement 加上测试Rule 来创建一个新的Statement用于evaluate。举个例子Verifier:
public abstract class Verifier implements TestRule {
public Statement apply(final Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
base.evaluate();
verify();
}
};
}
/**
* Override this to add verification logic. Overrides should throw an
* exception to indicate that verification failed.
*/
protected void verify() throws Throwable {
}
}
我们看到在 Verifier 的 apply 方法中首先调用了 baseStatement的 evaluate 方法,然后调用自己 verify 方法。如何使用呢?我们看一下测试例子。
public class VerifierRuleTest {
private static String sequence;
public static class UsesVerifier {
@Rule
public Verifier collector = new Verifier() {
@Override
protected void verify() {
sequence += "verify ";
}
};
@Test
public void example() {
sequence += "test ";
}
}
@Test
public void verifierRunsAfterTest() {
sequence = "";
assertThat(testResult(UsesVerifier.class), isSuccessful());
assertEquals("test verify ", sequence);
}
}
我们看到测试例子中 assertEquals("test verify ", sequence); 是通过的,结合 Verifier 的代码,我们可以看到verifier 在测试代码运行之后调用。
JUnit的框架代码不是很多,很适合新手开始阅读,所以我的一系列框架阅读之旅就从简单的开始,这一篇是JUnit的基础篇,下一篇会讲到一些具体的使用情况。