• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

junit和Statement源码 ,整体执行流程

武飞扬头像
々子非鱼
帮助1

简介

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
系列文章
更多 icon
同类精品
更多 icon
继续加载