junit和Statement源码 ,整体执行流程
简介
junit
基于java语言的单元测试类框架
Statement
对一个单元运行的封装,每个Statement都只是执行它本身所表达的逻辑,而将其他逻辑交给下一个Statement处理,而且基本上的Statement都存在对下一个节点的引用,从设计模式上讲 是一个职责连模式。JUnit中对@BeforeClass、@AfterClass、@Before、@After、@ClassRule、@Rule等逻辑就是通过Statement来实现的
【可以简单理解为:Statement代表了所有测试方法case集合,相当于我们需要执行的一系列任务,通过next指向下一个任务,实际就以链表形式存在】
junit和Statement的关系
junit是大的执行环境,框架启动以后调用parentRuner的run方法 ,其中>本质是Runner.run()表示了整个执行入口 ,接着通过Runner执行器中的classBlock方法>创建出statement对象,statement对象封装的evaluate()方法中执行以Test注解修饰的方法集合 ,然后对通过获取BeforeClass注解的方法集合、AfterClass注解的方法集合、statement对象进行排序最终返回一个statment对象,并调用statement的evaluate方法启动执行case的一系列动作也就是statement的链表
具体源码实现分析
源码执行case流程概览
上图,假设存在8条用例,其中包含2条以@BeforeClass修饰的用例、3条以@AfterClass修饰的用例、3条以@Test修饰的用例,具体执行流程如上图;
源码解读
首先程序执行入口run()方法,决定了整个测试的执行流程;
【junit封装的,BlockJUnit4ClassRunner类(执行器的一种)作为JUnit4的默认Runner实现 继承了 ParentRunner类;
ParentRunner类 继承了 Runner类,重写了 Runner类中的run()方法,作为程序执行入口】
注释:一般常见会在junit单测类上出现@RunWith(XXXX.class)的注解
它是junit框架支持的一个类级别的注释;参数是一个执行器,是Runner类的子类,是用来指定运行测试用例的工具。使用@RunWith()注解为这个类指定一个特定的Runner。当我们没有指定@RunWith()的时候,会自动使用Junit的默认Runner——BlockJunit4ClassRunner。
常用的Runner: Suite:测试套件 、Category:按种类区分的套件、Parameterized:参数化测试、
Theories:排列组合。 例如:@RunWith(Suite.class)。
/**
* 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);
@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);
}
}
相关调用方法:
/**
*classBlock()负责构造这样一个Statement:
*1、通过注解校验和过滤筛选出需要执行的测试方法
*2、对这些测试方法使用装饰器模式进行 @BeforeClass 和 @AfterClass和@ClassRule的包装
*/
/**
* Constructs a {@code Statement} to run all of the tests in the test class.
* Override to add pre-/post-processing. Here is an outline of the
* implementation:
* <ol>
* <li>Determine the children to be run using {@link #getChildren()}
* (subject to any imposed filter and sort).</li>
* <li>If there are any children remaining after filtering and ignoring,
* construct a statement that will:
* <ol>
* <li>Apply all {@code ClassRule}s on the test-class and superclasses.</li>
* <li>Run all non-overridden {@code @BeforeClass} methods on the test-class
* and superclasses; if any throws an Exception, stop execution and pass the
* exception on.</li>
* <li>Run all remaining tests on the test-class.</li>
* <li>Run all non-overridden {@code @AfterClass} methods on the test-class
* and superclasses: exceptions thrown by previous steps are combined, if
* necessary, with exceptions from AfterClass methods into a
* {@link org.junit.runners.model.MultipleFailureException}.</li>
* </ol>
* </li>
* </ol>
* @return {@code Statement}
*/
protected Statement classBlock(final RunNotifier notifier) {
Statement statement = childrenInvoker(notifier);
if (!areAllChildrenIgnored()) {
statement = withBeforeClasses(statement);
statement = withAfterClasses(statement);
statement = withClassRules(statement);
}
return statement;
}
//childrenInvoker(notifier)构造了一个匿名内部类,直接调用runChildren()
/**
* Returns a {@link Statement}: Call {@link #runChild(Object, RunNotifier)}
* on each object returned by {@link #getChildren()} (subject to any imposed
* filter and sort)
*/
protected Statement childrenInvoker(final RunNotifier notifier) {
return new Statement() {
@Override
public void evaluate() {
runChildren(notifier);
}
};
}
//runChildren()则调用getFilteredChildren()方法(会调用子类的getChildren()扫描JUnit注解,比如@Test),之后通过RunnerScheduler直接调用子类实现的抽象方法runChild(),拿到所有的@Test注解的方法
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();
}
}
private Collection<T> getFilteredChildren() {
if (filteredChildren == null) {
synchronized (childrenLock) {
if (filteredChildren == null) {
filteredChildren = Collections.unmodifiableCollection(getChildren());
}
}
}
return filteredChildren;
}
//返回以@Test注解修饰的方法集合
@Override
protected List<FrameworkMethod> getChildren() {
return computeTestMethods();
}
/**
* 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);
}
//判断子类孩子是否被忽略,默认返回false ,检查方法上是否存在@Ignore注解,有该注释的方法在执行时会被忽略,不被执行
private boolean areAllChildrenIgnored() {
for (T child : getFilteredChildren()) {
if (!isIgnored(child)) {
return false;
}
}
return true;
}
/**
* Evaluates whether a child is ignored. The default implementation always
* returns <code>false</code>.
*
* <p>{@link BlockJUnit4ClassRunner}, for example, overrides this method to
* filter tests based on the {@link Ignore} annotation.
*/
protected boolean isIgnored(T child) {
return false;
}
至此可以看到构建好Statement链表数据源(这里的statment对象是进行排序的)之后,执行对应的evalute()方法: statement.evaluate();
扩展:若自己进行自动化封装框架,就可以通过自定义类,继承Statement类,从而重写evaluate()方法,控制整个用例执行顺序;
假如我们有多个Statement对象(比如下面自定义了两个类都继承于Statement),当然都可以重写evaluate()方法,如示:
public class RunAfterTestClass extends Statement { //见名知意: 类后执行,目的想要在每个测试类执行完后,也就是所有的测试脚本执行完成之后,做一些自定义逻辑(比如:关闭app 杀掉进程)
public void evaluate() throws Throwable {
this.next.evaluate(); //这里会优先执行下一个statement对象,也可以理解下一个case
.....
this.testContextManager.afterTestClass(); //后再执行我们自定义的逻辑
....
}
....
}
public class RunBeforeTestClass extends Statement { //见名知意: 类前执行,目的想要在每个测试类执行之前,也就是所有的测试脚本执行之前,做一些自定义逻辑(比如:打开app等)
@Override
public void evaluate() throws Throwable {
this.testContextManager.beforeTestClass(); //这里优先执行我们自定义的逻辑
this.next.evaluate(); //后再执执行下一个statement对象,也可以理解下一个case
}
....
}
}
##通过这样就可以达到控制执行顺序的一个效果,可以保证在我们的脚本 单测类之前/类执行后,单测方法之前/方法执行之后,添加一些自定义的实现逻辑,可以是测试结果收集、报告生成等;
而这里多个Statement 对象的执行顺序,可以由执行器决定,重写执行器的withBeforeClasses、withBefores、withAfters、withAfterClasses等方法;
以上这几个方法都是有BlockJUnit4ClassRunner该父类执行器定义的;
如:
@Override
protected Statement withBeforeClasses(Statement statement) {
Statement junitBeforeClasses = super.withBeforeClasses(statement);
return new RunBeforeTestClass(junitBeforeClasses, getTestContextManager()); //自定义类,包含了该构造方法
}
@Override
protected Statement withBefores(FrameworkMethod frameworkMethod, Object testInstance, Statement statement) {
Statement junitBefores = super.withBefores(frameworkMethod, testInstance, statement);
return new RunBeforeTestMethod(junitBefores, testInstance, frameworkMethod.getMethod(),
getTestContextManager());
}
具体举例如下:
// 举例,这里自定义的RunAfterTestClass类继承了Statement,重写了evaluate()方法,保证了在Statement执行后,去走 this.testContextManager.afterTestClass() ---自行封装的类,从而在其中实现一些想要完成的操作,比如:自动化执行完成关闭driver、关闭被测app等行为等;
public class RunAfterTestClass extends Statement {
private final Statement next;
private final TestContextManager testContextManager;
public RunAfterTestClass(Statement next, TestContextManager testContextManager) {
this.next = next;
this.testContextManager = testContextManager;
}
@Override
public void evaluate() throws Throwable {
List<Throwable> errors = new ArrayList<>();
try {
this.next.evaluate();
} catch (Throwable e) {
e.printStackTrace();
errors.add(e);
}
try {
this.testContextManager.afterTestClass();
//TestContextManager此处是我们自定义的上下文管理器,主要完成:管理注册监听器、管理测试上下文数据;【我们这里自定义了多个监听器,每个监听器负责不同的职责,每个监听器可以根据需要去封装自己对应的前后置方法等,再由此处的evaluate()分别调用,达到封装效果】
//TestContextManager里面存在for循环,检测到多个监听器类,执行到这一步会依次将每个监听器类中的afterTestClass()都执行
} catch (Exception e) {
errors.add(e);
}
if (errors.isEmpty()) {
return;
}
if (errors.size() == 1) {
throw errors.get(0);
}
throw new MultipleFailureException(errors);
}
}
RunBefores和RunAfters介绍
上面源码中可以看到下面几句:
本质:将测试需要执行的单测方法封装为statement对象,且控制其执行顺序;
statement = withBeforeClasses(statement);
statement = withAfterClasses(statement);
statement = withClassRules(statement);
//查找测试类中方法上包含@BeforeClass注解的
//其中@BeforeClass是在测试类运行时,所有测试方法运行之前运行,并且对每个测试类只运行一次
//将 @BeforeClass注解的方法抽象成一个Statement叫RunBefores ,测试类中其他要运行的测试方法的运行过程是另一个Statement叫next
/**
* Returns a {@link Statement}: run all non-overridden {@code @BeforeClass} methods on this class
* and superclasses before executing {@code statement}; if any throws an
* Exception, stop execution and pass the exception on.
*/
protected Statement withBeforeClasses(Statement statement) {
List<FrameworkMethod> befores = testClass
.getAnnotatedMethods(BeforeClass.class);
return befores.isEmpty() ? statement :
new RunBefores(statement, befores, null);
}
从源码中可以看到,构造RunBefores时传入下一个Statement;
Statement抽象类中 的 evaluate()函数代表着在测试中将被执行的方法;
/junit封装,拿到注解为@AfterClass的方法,保证各方法在之后执行对应方法
/**
* Returns a {@link Statement}: run all non-overridden {@code @AfterClass} methods on this class
* and superclasses before executing {@code statement}; all AfterClass methods are
* always executed: exceptions thrown by previous steps are combined, if
* necessary, with exceptions from AfterClass methods into a
* {@link org.junit.runners.model.MultipleFailureException}.
*/
protected Statement withAfterClasses(Statement statement) {
List<FrameworkMethod> afters = testClass
.getAnnotatedMethods(AfterClass.class);
return afters.isEmpty() ? statement :
new RunAfters(statement, afters, null);
}
/**
* Returns a {@link Statement}: apply all
* static fields assignable to {@link TestRule}
* annotated with {@link ClassRule}.
*
* @param statement the base statement
* @return a RunRules statement if any class-level {@link Rule}s are
* found, or the base statement
*/
private Statement withClassRules(Statement statement) {
List<TestRule> classRules = classRules();
return classRules.isEmpty() ? statement :
new RunRules(statement, classRules, getDescription());
}
持续更新学习中~
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanhgcbcck
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
excel下划线不显示怎么办
PHP中文网 06-23 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
TikTok加速器哪个好免费的TK加速器推荐
TK小达人 10-01 -
怎样阻止微信小程序自动打开
PHP中文网 06-13