JUnit4 源碼 之 基礎模塊
今天突發奇想 想看看JUnit的實現,於是就翻閱了JUnit的官網和GitHub Project,並初步有所瞭解,就此進行簡單源碼記錄。
- JUnitCore
- RunnerBuilder
- Statement
- annotation
- Runner
JUnitCore 運行入口
- main方法 一般作爲命令行入口
- static runClasses(…) 一般用於手動入口(基本不這麼用)Result result = JUnitCore.runClasses(AddPencilsTest.class);
- run(…) IDE集成入口
// from JUnitCore
public Result run(Computer computer, Class<?>... classes)
{ return run(Request.classes(computer, classes)); }
// from Request
public static Request classes(Computer computer, Class<?>... classes) {
// 通過Builder進行構建Runner對象(下面重點介紹)
AllDefaultPossibilitiesBuilder builder = new AllDefaultPossibilitiesBuilder(true);
Runner suite = computer.getSuite(builder, classes);
return runner(suite);
}
// from JUnitCore
public Result run(Runner runner) {
Result result = new Result();
RunListener listener = result.createListener();
notifier.addFirstListener(listener);
try {
notifier.fireTestRunStarted(runner.getDescription());
runner.run(notifier); // 啓動單元測試,其他部分均爲Listener相關內容
notifier.fireTestRunFinished(result);
} finally { removeListener(listener); }
return result;
}
RunnerBuilder 測試類的包裝運行類
- Ignore[Class]Builder – 標記爲@Ignore的Test類
- AnnotatedBuilder – 標記爲@RunWith的Test類
- JUnit4Builder – 普通類
- AllDefaultPossibilitiesBuilder – 所有RunnerBuilder的通用工廠類
// AllDefaultPossibilitiesBuilder
@Override
public Runner runnerForClass(Class<?> testClass) throws Throwable {
List<RunnerBuilder> builders = Arrays.asList(
ignoredBuilder(), // @Ignore
annotatedBuilder(), // @RunWith
suiteMethodBuilder(), // method name == suite
junit3Builder(),
junit4Builder()); // other (no annotation Test class)
for (RunnerBuilder each : builders) {
Runner runner = each.safeRunnerForClass(testClass);
if (runner != null) { return runner; }
}
return null;
}
// from AnnotatedBuilder
@Override
public Runner runnerForClass(Class<?> testClass) throws Exception {
for (Class<?> currentTestClass = testClass; currentTestClass != null;
currentTestClass = getEnclosingClassForNonStaticMemberClass(currentTestClass)) {
RunWith annotation = currentTestClass.getAnnotation(RunWith.class);
if (annotation != null) { return buildRunner(annotation.value(), testClass); }
}
return null;
}
public Runner buildRunner(Class<? extends Runner> runnerClass,
Class<?> testClass) throws Exception {
try {
return runnerClass.getConstructor(Class.class).newInstance(testClass);
} catch (NoSuchMethodException e) {
try {
return runnerClass.getConstructor(Class.class,
RunnerBuilder.class).newInstance(testClass, suiteBuilder);
} catch (NoSuchMethodException e2) {
String simpleName = runnerClass.getSimpleName();
throw new InitializationError(String.format(
CONSTRUCTOR_ERROR_FORMAT, simpleName, simpleName));
}
}
}
Statement (各測試方法的對應封裝類)
- RunAfters
- RunBefores
- InvokeMethod
- FailOnTimeout
// from RunAfters
@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 { each.invokeExplosively(target); } catch (Throwable e) { errors.add(e); }}
}
MultipleFailureException.assertEmpty(errors);
}
// from RunBefores
@Override
public void evaluate() throws Throwable {
for (FrameworkMethod before : befores) { before.invokeExplosively(target); }
next.evaluate();
}
// from InvokeMethod
@Override
public void evaluate() throws Throwable { testMethod.invokeExplosively(target); }
// from FailOnTimeout
@Override
public void evaluate() throws Throwable {
CallableStatement callable = new CallableStatement();
FutureTask<Throwable> task = new FutureTask<Throwable>(callable);
threadGroup = new ThreadGroup("FailOnTimeoutGroup");
Thread thread = new Thread(threadGroup, task, "Time-limited test");
thread.setDaemon(true);
thread.start();
callable.awaitStarted();
Throwable throwable = getResult(task, thread);
if (throwable != null) {
throw throwable;
}
}
private Throwable getResult(FutureTask<Throwable> task, Thread thread) {
try {
if (timeout > 0) { return task.get(timeout, timeUnit); }
else { return task.get(); }
} catch (InterruptedException e) {
return e; // caller will re-throw; no need to call Thread.interrupt()
} catch (ExecutionException e) {
// test failed; have caller re-throw the exception thrown by the test
return e.getCause();
} catch (TimeoutException e) {
return createTimeoutException(thread);
}
}
private class CallableStatement implements Callable<Throwable> {
private final CountDownLatch startLatch = new CountDownLatch(1);
public Throwable call() throws Exception {
try { startLatch.countDown(); originalStatement.evaluate(); }
catch (Exception e) { throw e; }
catch (Throwable e) { return e; }
return null;
}
public void awaitStarted() throws InterruptedException { startLatch.await(); }
}
annotation
- Base – 核心使用部分,不與介紹
- BeforeClass | Before | Test | After | AfterClass | Ignore
- [Class]Rule – 當使用與Method和Field上時,分別對應不同的接口:MethodRule、TestRule。不過目前MethodRule接口只有一個實現類:TestWatchman,並且該實現類已經被標記爲Deprecated。
- Advanced
- RunWith – 使用指定Runner來對測試用例進行測試(主要是用於 環境部署 及 分類組裝 等)
- Suite & Parameterized 這兩個爲JUnit自帶用於RunWith註解中的 運行器 (本人暫且這麼稱呼)
- Suite 配合 SuiteClasses 一起使用,用於整合多個測試用例一起運行測試
- Parameterized 當需要測試某功能在不同參數下的運行情況時,可以使用該 運行器 來提供參數集
- 具體核心代碼邏輯 在下篇中講解
Runner
用於包裝一個Test類,與Test Method的對應關係爲 Test Method: Runner = M : 1
可以這麼描述,一般都會使用ParentRunner抽象類,其中有一個屬性爲children,該屬性中包裹着Test Method
簡單鋪墊一下Runner的常用實現類的繼承關係,以便於下篇文章講解
- ParentRunner --> Suite --> Parameterized
- ParentRunner --> BlockJUnit4ClassRunner --> BlockJUnit4ClassRunnerWithParameters