junit4原理揭秘
intellij运行junit启动观察
首先我们在idea中运行一个最简单的Test,查看堆栈信息
总结:
- 由上面堆栈可以看出运行启动类是JUnitStarter#main(args).
不过是intellij中提供的,具体有兴趣的同学可以下去深究
(com.intellij.junit4.JUnit4IdeaTestRunner) - . 从上图中可以看出,
- 首先通过Request#aClass创建一个ClassRequest
- 然后通过ClassRequest#getRunner 创建一个Runner进行测试
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.
* (这个Runner就是用来运行tests的)
*
* @param notifier will be notified of events while tests are being run--tests being
* started, finishing, and failing
* (参数notifier将在运行测试时收到事件通知. 包括test开始,结束和失败阶段,而notifier根据代码也可以看出拥有监听器listener )
*/
public abstract void run(RunNotifier notifier);
/**
* @return the number of tests to be run by the receiver
*/
public int testCount() {
return getDescription().testCount();
}
}
总结:
- 可以得出Runner#run( org.junit.runner.Runner)就是用来运行test的入口。
参数RunNotifier ,就是有各种监听器的notifier,估计大部分用不到,可以忽略
Runner的创建-org.junit.runner.Request
Junit中提供一个抽象的Request来创建Runner。设计模式之工厂方法模式
可以用 ctrl+H 查看下Request的类结构图,具体子类包括SortingRequest,RunnerRequest,FilterRequest,ClassRequest。
public abstract class Request {
public abstract Runner getRunner();
}
- . Request的子类都是用来创建Runner对象的,根据名字和具体代码可以得出,此处是设计模式之装饰模式。(动态增加和修改对象的行为,针对客户端是透明化的,可见的)
简单介绍一下,测试Junit的大致逻辑,就是利用最原始的ClassReuest创建Runner来执行测试,
然后就会获取@Test的方法,然后依次调用
RunnerRequest
- 更简单了,就是直接构造传入Runner对象来创建
SortingRequest
- 大致作用就是可以将@Test的方法进行排序。
Runner对象需要实现org.junit.runner.manipulation.Sortable
FilterRequest
- 大致作用就是可以将@Test的方法进行过滤,即表示是否运行,重写shouldRun方法。
Runner对象需要实现org.junit.runner.manipulation.Filterable - 跟@Ignore差不多,只不过FilterRequest是编程式,@Ignore是注解式
因为junit是intellij发起入口测试的,所以上面的SortingRequest和FilterRequest其实不能手动自己去创建,所以可以暂时忽略,除非研究下怎么自己创建。或者扩展intellij的junit插件等。
ClassRequest
该类ClassRequest是基础类,就是根据我们的测试类Test,来创建Runner的
- 值得一提的是,getRunner这里利用了一个DCL(双重检查锁)来创建Runner单例对象
(注意 一定要用volatile来修饰,保证内存可见性和防止指令重排,如果看到没有使用volatile,肯定是错误的(除非你的cpu是单核的,也不太可能))
AllDefaultPossibilitiesBuilder
- 此类利用了RunnerBuilder,有一个抽象方法#runnerForClass来创建Runner,
- 根据代码来看,是利用了根据工厂方法模式来创建的对象(和Request#getRunner是相同的手法)。(从名字来看,好像是构建者模式,不要被误导了)
IgnoredBuilder
- 如果测试类存在@Ignore(org.junit.Ignore),则可以从IgnoredClassRunner中发现,
实际上最后就代表什么都不做do nothing,一个@Test都不会运行。
AnnotatedBuilder
- 如果测试类存在@RunWith,则利用注解的class来反射实例化一个Runner对象。
例如: @RunWith(SpringJUnit4ClassRunner.class) spring-test提供的Runner类
SuiteMethodBuilder
- 会去查找测试类Class,是否存在静态的static, 方法名为suite的,则直接当作测试Test执行. 如下
public static junit.framework.Test suite(){
return new junit.framework.Test(){
@Override
public int countTestCases() {
return 0;
}
@Override
public void run(TestResult result) {
System.out.println("haha");
}
};
}
JUnit3Builder
- 测试类是TestCase的子类用来执行。 是属于junit3的一种方式。基本可以忽略了
public class TestDemo extends junit.framework.TestCase{
--重写来运行的方法
}
JUnit4Builder
- 直接默认用BlockJUnit4ClassRunner来作为Runner运行测试。
也是目前junit4版本的默认实现0
public class JUnit4Builder extends RunnerBuilder {
@Override
public Runner runnerForClass(Class<?> testClass) throws Throwable {
return new BlockJUnit4ClassRunner(testClass);
}
}
Statement
- org.junit.runners.model.Statement, 当test测试类执行的时候,实际执行的就是该对象
- 最底层是直接反射调用@Test方法,然后利用装饰模式,一层层的添加各种行为
- 例如前置@BeforeClass 或者 @Before
- 后置@AfterClass或者@After
- 异常处理,@Test 中的expected属性
- 超时Timeout处理,@Test 中的timeout属性,使用FutureTask
/**
* Represents one or more actions to be taken at runtime in the course
* of running a JUnit test suite.
*
* @since 4.5
*/
public abstract class Statement {
/**
* Run the action, throwing a {@code Throwable} if anything goes wrong.
*/
public abstract void evaluate() throws Throwable;
}
Test类运行的前置/后置
- 定义在ParentRunner#classBlock中
前置RunBefores实现
- 针对@BeforeClass
在@Test方法执行之前,依次调用所有的@BeforeClass静态方法,必须是static - 针对@Before
会在@Test方法执行之前,依次调用所有的@Before实例方法
后置RunAfter
- 针对@AfterClass
在@Test方法执行之后,依次调用所有的@AfterClass静态方法,必须是static - 针对@After
会在@Test方法执行之后,依次调用所有的@After实例方法
依次顺序
@BeforeClass > (创建test实例对象) >@Before > @Test > @After > @AfterClass
ParentRunner
- 作为Runner的抽象父类,定义了run方法执行的逻辑(设计模式之模板模式)。
提供getChildren和runChild两个抽象方法供子类自己实现 - 并且使用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;
}
BlockJUnit4ClassRunner
是ParentRunner运行类的子类,也是junit4版本的默认执行类,类结构图如下:
- 下面是子类BlockJUnit4ClassRunner的相关代码:实现了父类的runChild和getChild
具体逻辑就是获取测试类Test所有的@Test注解的方法,然后运行
(当然会排除@Ignore(org.junit.Ignore)标识的方法)
@Override
protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
Description description = describeChild(method);
if (isIgnored(method)) {
notifier.fireTestIgnored(description);
} else {
在 methodBlock是一个比较重要的下面开始讲解两个抽象方法供子类自己实现methodBlock重要的流程
runLeaf(methodBlock(method), description, notifier);
}
}
@Override
protected List<FrameworkMethod> getChildren() {
return computeTestMethods();
}
// Override in subclasses
/**
* Returns the methods that run tests. Default implementation returns all
* methods annotated with {@code @Test} on this class and superclasses that
* are not overridden.
*/
protected List<FrameworkMethod> computeTestMethods() {
return getTestClass().getAnnotatedMethods(Test.class);
}
methodBlock方法
- 就是用来创建最后执行的statement对象的,然后执行statement
- 首先通过createTest()创建test实例对象
- 构造Statement对象,然后装饰为前置和后置的对象