为什么被标注 @Test 注解的方法会被执行

发表于:2021-7-05 09:23

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:磊叔_GLMapper    来源:掘金

  这里比较好理解,被打了 @Test 注解的方法,一定是 Junit 通过某种方式将其扫描到了,然后作为待执行的一个集合或者队列中。下面通过分析代码来论证下。
org.junit.runners.BlockJUnit4ClassRunner#getChildren
  @Override
  protected List<FrameworkMethod> getChildren() {
      return computeTestMethods();
  }
  通过方法 computeTestMethods 方法名其实就可以看出其目的,就是计算出所有的测试方法。
  etAnnotatedMethods 通过指定的 annotationClass 类型,将当前 TestClass 中类型为 annotationClass 类型注解标注的方法过滤出来。
  getFilteredChildren 中最后将获取得到的测试方法放在 filteredChildren 中缓存起来。这里简单汇总下 @Test 注解被识别的整个过程(其他注解如 @Before 都是一样的)。
  1、Junit 在初始化构建 Runner 的过程,内部会基于给定的 测试类创建一个 TestClass 对象模型,用于描述当前测试类在 Junit 中的表示。
  // clazz 是待测试类
  public TestClass(Class<?> clazz) {
      this.clazz = clazz;
      if (clazz != null && clazz.getConstructors().length > 1) {
          // 测试类不能有有参构造函数
          throw new IllegalArgumentException(
              "Test class can only have one constructor");
      }
      Map<Class<? extends Annotation>, List<FrameworkMethod>> methodsForAnnotations =
          new LinkedHashMap<Class<? extends Annotation>, List<FrameworkMethod>>();
      Map<Class<? extends Annotation>, List<FrameworkField>> fieldsForAnnotations =
          new LinkedHashMap<Class<? extends Annotation>, List<FrameworkField>>();
      // 扫描待测试类中所有的 Junit 注解,包括 @Test @Before @After 等等
      scanAnnotatedMembers(methodsForAnnotations, fieldsForAnnotations);
  // 过滤出打在方法上的注解,
      this.methodsForAnnotations = makeDeeplyUnmodifiable(methodsForAnnotations);
      // 过滤出打在变量上的注解
      this.fieldsForAnnotations = makeDeeplyUnmodifiable(fieldsForAnnotations);
  }
  methodsForAnnotations 和 fieldsForAnnotations 缓存了当前待测试类所有被 junit 注解标注过的方法和变量。
  2、getFilteredChildren 中,从 methodsForAnnotations 中筛选出所有 @Test 注解标注的方法。(getDescription()-> getFilteredChildren  -> computeTestMethods -> 从 methodsForAnnotations 按类型过滤)。
  3、返回所有 @Test 注解标注的方法。
  Before 和 After 执行时机
  要搞定这个问题,其实有必要了解下 Junit 中一个比较重要的概念 Statement。
  public abstract class Statement {
      /**
       * Run the action, throwing a {@code Throwable} if anything goes wrong.
       */
      public abstract void evaluate() throws Throwable;
  }
  Statement 从 junit 4.5 版本被提出,Statement 表示在运行 JUnit 测试组件的过程中要在运行时执行的一个或多个操作,简单说就是,对于被 @Before @After 注解标注的方法,在 JUnit  会被作为一种 Statement  存在,分别对应于 RunBefores 和 RunnerAfter,这些 statement 中持有了当前运行所有的 FrameworkMethod。
  FrameworkMethod 是 JUnit   中所有被 junit 注解标注方式的内部描述,@Test, @Before, @After, @BeforeClass, @AfterClass 标注的方法最终都作为 FrameworkMethod  实例存在。
  Statement  的创建有两种方式,基于 FrameworkMethod 的 methodBlock 和基于 RunNotifier 的 classBlock,这里介绍 methodBlock ,classBlock 下节讨论。
  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);
          }
          Statement statement = methodInvoker(method, test);
          statement = possiblyExpectingExceptions(method, test, statement);
          statement = withPotentialTimeout(method, test, statement);
          statement = withBefores(method, test, statement);
          statement = withAfters(method, test, statement);
          statement = withRules(method, test, statement);
          statement = withInterruptIsolation(statement);
          return statement;
      }
  withAfters、withBefores 会将 RunAfters 和 RunBefore 绑定到 statement,最后 形成一个 statement 链,这个链的执行入口时  RunAfters#evaluate。
  @Override
  public void evaluate() throws Throwable {
      List<Throwable> errors = new ArrayList<Throwable>();
      try {
          next.evaluate();
      } catch (Throwable e) {
          errors.add(e);
      } finally {
          // 在 finally 中执行 after 方法
          for (FrameworkMethod each : afters) {
              try {
                  invokeMethod(each);
              } catch (Throwable e) {
                  errors.add(e);
              }
          }
      }
      MultipleFailureException.assertEmpty(errors);
  }
  next 链中包括 before 和待执行的测试方法。
  所以我们看到的就是 before -> testMethod -> after。
  这里其实和预想的不太一样,关于 before 和 after 这种逻辑,第一想法是通过代理的方式,对测试方法进行代理拦截,类似 Spring AOP 中的 Before 和 After,其实不然。
  BeforeClass 和 AfterClass 执行时机
  前面分析了 methodBlock,了解到 junit 中通过这个方法创建 statement 并且将 before 和 after 的方法绑定给 statement,以此推断,classBlock 的作用就是将 BeforeClass 和 AfterClass 绑定给statement 。
  protected Statement classBlock(final RunNotifier notifier) {
      // childrenInvoker 这里会调用到 methodBlock
      Statement statement = childrenInvoker(notifier);
      if (!areAllChildrenIgnored()) {
          statement = withBeforeClasses(statement);
          statement = withAfterClasses(statement);
          statement = withClassRules(statement);
          statement = withInterruptIsolation(statement);
      }
      return statement;
  }
  BeforeClass  和 before  都会对应创建一个 RunnerBefores,区别在于 BeforeClass   在创建 RunnerBefores 时,不会指定目标测试方法。
  · BeforeClass  在执行 statement 之前,运行该类和超类上所有非覆盖的@BeforeClass方法;如果有抛出异常,停止执行并传递异常。
  · AfterClass 在执行 statement链最后,在该类和超类上运行所有未覆盖的 @AfterClass 方法;始终执行所有 AfterClass 方法:如有必要,将前面步骤抛出的异常与来自 AfterClass 方法的异常合并到 org.junit.runners.model.MultipleFailureException 中。

  本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号