JUnit4源码初探
Junit是广泛使用的Java单元测试类库,官网地址:junit4主页,junit4开源地址(github)。网上有许多关于junit的使用教程,这里附上两个教程供大家参考:junit教程一,junit教程二(英文)。Junit4使用注解实现测试执行,这篇文章主要粗略分析Junit4的实现原理,代码使用目前的最新版本junit-4.13-SNAPSHOT进行分析。
JUnit4框架使用
Junit4使用非常方便,编写好测试类,在命令行中编译运行即可。
javac -cp .;junit-4.XX.jar XXX.java //编译
java -cp .:junit-4.XX.jar:hamcrest-core-1.3.jar org.junit.runner.JUnitCore XXXTest //运行
JUnitCore是Junit4的入口类,在调用主函数mian后,依次调用runMain->JUnitCommandLineParseResult.parse->Request.class->JUnitCore.run,其中Runner是junit框架的核心类,代表一个测试抽象。Ruquest相当于Runner工厂,通过调用Request.class方法生成特定的Runner。调用次序如下图:
Junit4可嵌入代码中调用,如下:
public static void main(String[] args) {
Result result = JUnitCore.runClasses(TestJunit.class);
for (Failure failure : result.getFailures()) {
System.out.println(failure.toString());
}
System.out.println(result.wasSuccessful());
}
JUnitCore.runClasses中同样调用Request.class生成特定Runner。其中,Result代表测试结果类。
Runner/RunnerBuilder
Runner是junit框架中的核心类,由RunnerBuilder创建。junit4默认使用AllDefaultPossibilitiesBuilder生成不同的RunnerBuilder。runnerForClass方法是RunnerBuilder生成Runner的方法,AllDefaultPossibilitiesBuilder重写runnerForClass方法如下:
public Runner runnerForClass(Class<?> testClass) throws Throwable {
List<RunnerBuilder> builders = Arrays.asList(
ignoredBuilder(),
annotatedBuilder(),
suiteMethodBuilder(),
junit3Builder(),
junit4Builder());
for (RunnerBuilder each : builders) {
Runner runner = each.safeRunnerForClass(testClass);
if (runner != null) {
return runner;
}
}
return null;
}
可以看出,AllDefaultPossibilitiesBuilder建立builder列表并依照列表依次找到合适的RunnerBuilder,其查找顺序是:
不同的RunnerBuilder生成不同的Runner。Junit4框架中,BlockJUnit4ClassRunner是主要的实现的Runner。BlockJUnit4ClassRunner继承ParentRunner,ParentRunner是实现形成父子关系的抽象测试类,其父子测试关系呈父子关系。在BlockJUnit4ClassRunner中,其子测试为FrameworkMethod,代表测试类的方法单元。
Suite是junit4中另一个值得关注的Runner,Suite继承ParentRunner,其子测试是其他Runner,代表一组测试的抽象类。在JUnitCore中,通过调用Request.class方法生成的Runner为一个Suite,其中包含其他Runner测试类。
Statement机制
Statement机制是junit4中实现测试的重要一环,Runner利用Statment进行实际的测试运行。Statment的定义很简单,包含一个evaluate接口。
public abstract class Statement {
public abstract void evaluate() throws Throwable;
}
利用Statement容易实现对测试事件的拦截和扩展,类似AOP。例如在ParentRunner的classBlock方法实现block逻辑。
protected Statement classBlock(final RunNotifier notifier) {
Statement statement = childrenInvoker(notifier); //封装子测试
if (!areAllChildrenIgnored()) {
statement = withBeforeClasses(statement); //封装@BeforeClass测试,返回RunBefores
statement = withAfterClasses(statement); //封装@AfterClass测试,返回RunAfters
statement = withClassRules(statement); //封装@ClassRule测试,返回RunRules
}
return statement;
}
protected Statement withBeforeClasses(Statement statement) {
List<FrameworkMethod> befores = testClass
.getAnnotatedMethods(BeforeClass.class);
return befores.isEmpty() ? statement :
new RunBefores(statement, befores, null); //生成RunBefores对象
}
RunBefores,RunAfters,RunRules都是Statement对象的代理类,其构造函数传入Statement引用,通过控制evaluate方法的调用实现对原有测试的控制,并实现对测试事件不同时期的拦截。其中,RunBefores的实现如下:
public class RunBefores extends Statement {
private final Statement next;
private final Object target;
private final List<FrameworkMethod> befores;
public RunBefores(Statement next, List<FrameworkMethod> 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();
}
}
BlockJUnit4ClassRunner
BlockJUnit4ClassRunner是JUnit4的默认实现Runner,父类ParentRunner实现了@BeforeClass,@AfterClass,@ClassRule逻辑,BlockJUnit4ClassRunner主要工作是实现runChild方法,并调用测试类方法,runChild实现如下:
protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
Description description = describeChild(method);
if (isIgnored(method)) {
// @Ignore注解
notifier.fireTestIgnored(description);
} else {
Statement statement;
try {
statement = methodBlock(method); //调用methodBlock
}
catch (Throwable ex) {
statement = new Fail(ex); // 封装error
}
// 运行statement
runLeaf(statement, description, notifier);
}
}
protected Statement methodBlock(final FrameworkMethod method) {
Object test;
try {
// 获取测试类实例
test = new ReflectiveCallable() {
@Override
protected Object runReflectiveCall() throws Throwable {
return createTest(method);
}
}.run();
} catch (Throwable e) {
return new Fail(e); // 封装error
}
Statement statement = methodInvoker(method, test); // 封装测试方法
statement = possiblyExpectingExceptions(method, test, statement);
statement = withPotentialTimeout(method, test, statement);
statement = withBefores(method, test, statement); // 封装@Before测试
statement = withAfters(method, test, statement); // 封装@After测试
statement = withRules(method, test, statement); // 封装@Rule测试
return statement;
}
runChild调用methodBlock方法对测试方法进行封装,然后调用runLeaf运行statement.evaluate()。
Assert/Assume
Assert类提供了很多断言方法,如assertTrue,assertEquals,assertNull等,在JUnit框架比较常用,当判断失败时抛出AssertError。Assume与Assert相似,提供如assumeTrue等方法,判断失败时抛出AssumptionViolatedException,表示条件不满足是放弃当前测试。
AssertError和AssumptionViolatedException的捕获在BlockJUnit4ClassRunner的runLeaf方法中进行,runLeaf实现如下:
protected final void runLeaf(Statement statement, Description description,
RunNotifier notifier) {
EachTestNotifier eachNotifier = new EachTestNotifier(notifier, description);
eachNotifier.fireTestStarted();
try {
// 运行statement
statement.evaluate();
} catch (AssumptionViolatedException e) {
eachNotifier.addFailedAssumption(e); //捕获AssumptionViolatedException
} catch (Throwable e) {
eachNotifier.addFailure(e); //捕获AssertError和其他异常
} finally {
eachNotifier.fireTestFinished();
}
}
@RunWith
JUnit4框架可以使用@RunWith注解改变默认Runner,AllDefaultPossibilitiesBuilder在遇到@RunWith注解的测试类时使用AnnotatedBuilder创建Runner,AnnotatedBuilder重写runnerForClass方法如下:
// AnnotatedBuilder的runnerForClass方法
public Runner runnerForClass(Class<?> testClass) throws Exception {
for (Class<?> currentTestClass = testClass; currentTestClass != null;
currentTestClass = getEnclosingClassForNonStaticMemberClass(currentTestClass)) {
RunWith annotation = currentTestClass.getAnnotation(RunWith.class); // @RunWith注解
if (annotation != null) {
// 根据注解创建Runner
return buildRunner(annotation.value(), testClass);
}
}
return null;
}
JUnit4框架提供许多有用的Runner,如Suite,Parameterized,Category等。
Junit之源码测试
大部分java程序使用JUnit框架进行测试,而这放在JUnit代码上时会出现问题,JUnit源码使用什么来测试代码?在当前使用的junit-4.13-SNAPSHOT版本上,junit使用自身进行测试。至于junit怎样使用自身进行测试保证编写代码正确,这是一个有趣的问题,由于时间关系没有深入,留个坑,以后来填。