首页 > 编程语言 >Junit5源码分析

Junit5源码分析

时间:2024-02-18 22:56:30浏览次数:45  
标签:分析 execute return private selector 源码 context new Junit5

近期使用junit和springtest 做公司的一个灰盒自动化项目,即非白盒单测和黑盒接口方式的自动化方式,验证代码中复杂的业务逻辑(金融相关),使用过程中遇到过一些使用问题,业余时间学习了下框架源码,略有收获,遂记录之。
创建一个简单测试DEMO如下:
新建一个TestApplication和一个server
新建测试类如下:

image
image

这样,一个简单的测试程序就可以跑了。
我们可以在test用例中随便设置一个断点,选中两个测试类DEBUG启动程序后,查看调用栈如下:
image

我们先看左边的调用栈,DefaultLauncher中的discover方法, 这个是junit代码入口,之前的流程是idea本身的插件源码,主要构造了监听器,处理一些idea工具本身需要统计的用例测试时间、用例执行状态等;执行器,处理测试用例名称,用户在idea不同操作对应的不同的Selector等参数对接junit的Launcher接口。感兴趣的同学可以查看IDEA插件源码 (最新版本地址:https://github.com/JetBrains/intellij-community/tree/61fb94acd0e337972338618b58c38a4509aefcff/plugins/junit5_rt/src/com/intellij/junit5 需要阅读和IDEA版本配套的版本代码)。

借用外网的一张图,阐述junit5本身架构和ide工具的关系如下  

image

     简单描述下Junit5的相关架构
     (1)junit-jupiter-api
        开发者用于撰写测试的 API,包括需要使用的注解、断言等。
    (2)junit-platgorm-engine
      包含了一套所有测试引擎都必须实现的 API。这样,不同的测试引擎之间可以通过统一的接口被调用。引擎可以跑正常的 JUnit 测试,但也可以实现不同的引擎用以执行其他框架写成的测试,如 TestNG、Spock、Cucumber 等。
   ( 3)junit-jupiter-engine
      junit-platform-engine API 的一个实现,专门用于执行 JUnit 5 撰写的测试。
   (4)junit-vintage-engine
   junit-platform-engine API 的一个实现,用来运行JUnit 4 撰写的测试。可以认为是低版本的 JUnit 3/4 与 JUnit 5 之间的一个适配器。
  ( 5)junit-platform-launcher
   这部分使用了一个服务加载器 ServiceLoader 来发现测试引擎,并协调不同实现之间的执行。它提供了一个 API 给 IDE 和构建工具,使得它们能够与测试执行过程交互,比如,运行单个的测试、搜集测试结果并展示等。
 我们再看上述截图右边的变量,可以看到idea插件本身构造了一个       LauncherDiscoveryRequest 接口的默认实现类DefaultDiscoveryRequest对象discoveryRequest传入,。

`/**

  • {@code DefaultDiscoveryRequest} is the default implementation of the
  • {@link EngineDiscoveryRequest} and {@link LauncherDiscoveryRequest} APIs.
  • @since 1.0
    */
    final class DefaultDiscoveryRequest implements LauncherDiscoveryRequest {

// Selectors provided to the engines to be used for discovering tests
private final List selectors;

.....

// Discovery filters are handed through to all engines to be applied during discovery.
private final List<DiscoveryFilter<?>> discoveryFilters;

.......
}`
我们只关注有值的部分,DiscoverySelector接口实现类对象和 DiscoveryFilter 接口实现类对象
image

可以看到selectors 变量是  ClasspathRootSelector对象,存储的是业务类和测试类代码URI路径。discoverFilters变量为idea插件传入的值,顾名思义,filter的作用是为后面流程用来过滤测试类做处理(include和exclude).

上文我们介绍了junit5框架架构和对应的入口方法,主要由idea插件调用并传入LauncherDiscoveryRequest对象,根据经验,我们知道测试框架需要解析对应的测试用例文件,生成测试用例,组装参数,然后才能执行,收集结果等;我们详细看下框架是怎么解析并生成测试用例的。
我们先看下相关重要的类数据结构:
`//代表这次测试计划的抽象,里面包含所有的测试用例
public class TestPlan {

//TestIdentifier ,代表一个测试用例或者容器的唯一标识
//解析的根节点集合
private final Set roots = Collections.synchronizedSet(new LinkedHashSet<>(4));

//后代TestIdentifier集合,key为 父节点的unigueId
private final Map<String, Set> children = new ConcurrentHashMap<>(32);

//所有的TestIdentifier集合,包括父子节点,key为父子节点的unigueId
private final Map<String, TestIdentifier> allIdentifiers = new ConcurrentHashMap<>(32);

private final boolean containsTests;

/**

  • Construct a new {@code TestPlan} from the supplied collection of
  • {@link TestDescriptor TestDescriptors}.
  • Each supplied {@code TestDescriptor} is expected to be a descriptor

  • for a {@link org.junit.platform.engine.TestEngine TestEngine}.
  • @param engineDescriptors the engine test descriptors from which the test
  • plan should be created; never
  • @return a new test plan
    */
    @API(status = INTERNAL, since = "1.0")
    public static TestPlan from(Collection engineDescriptors) {
    Preconditions.notNull(engineDescriptors, "Cannot create TestPlan from a null collection of TestDescriptors");
    TestPlan testPlan = new TestPlan(engineDescriptors.stream().anyMatch(TestDescriptor::containsTests));
    //Visitor function,为了执行testPlan.add 方法,下一行调用
    Visitor visitor = descriptor -> testPlan.add(TestIdentifier.from(descriptor));
    engineDescriptors.forEach(engineDescriptor -> engineDescriptor.accept(visitor));
    return testPlan;
    }

/**
构造方法,是否包含测试用例
*/
@API(status = INTERNAL, since = "1.4")
protected TestPlan(boolean containsTests) {
this.containsTests = containsTests;
}

....
} InternalTestPlan, TestPlan的子类,使用了委托模式,扩展了属性,具体如下:
class InternalTestPlan extends TestPlan {

private static final Logger logger = LoggerFactory.getLogger(InternalTestPlan.class);

private final AtomicBoolean warningEmitted = new AtomicBoolean(false);
//这里由个Root对象,代表所有发现的TestEngines和TestDescriptor
private final Root root;
private final TestPlan delegate;

static InternalTestPlan from(Root root) {
TestPlan delegate = TestPlan.from(root.getEngineDescriptors());
return new InternalTestPlan(root, delegate);
}
......
} Root 类详细信息如下:class Root {

//TestEngine和TestDescriptor的关联集合,存放每个engin和对应的根节点testDescriptor
private final Map<TestEngine, TestDescriptor> testEngineDescriptors = new LinkedHashMap<>(4);

//插件传递过来的参数,从LauncherDiscoveryRequest对象中获取
private final ConfigurationParameters configurationParameters;
......

} 我们可以看到Root对象中涉及到了TestEngine和 TestDescriptor, 由上节介绍框架model所知,junit抽象了一个执行引擎,即TestEngine接口,统一由testEngine发现和执行测试用例,部分结构信息如下:public interface TestEngine {
......
/**

  • Discover tests according to the supplied {@link EngineDiscoveryRequest}.
  • The supplied {@link UniqueId} must be used as the unique ID of the

  • returned root {@link TestDescriptor}. In addition, the
  • must be used to create unique IDs for children of the root's descriptor
  • by calling {@link UniqueId#append}.
  • @param discoveryRequest the discovery request; never
  • @param uniqueId the unique ID to be used for this test engine's
  • {@code TestDescriptor}; never
  • @return the root {@code TestDescriptor} of this engine, typically an
  • instance of
  • @see org.junit.platform.engine.support.descriptor.EngineDescriptor
    */
    TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId);

/**

  • Execute tests according to the supplied {@link ExecutionRequest}.
  • The {@code request} passed to this method contains the root

  • {@link TestDescriptor} that was previously returned by {@link #discover},
  • the {@link EngineExecutionListener} to be notified of test execution
  • events, and {@link ConfigurationParameters} that may influence test execution.
  • @param request the request to execute tests for; never {@code null}
    */
    void execute(ExecutionRequest request);
    ......
    }`
    我们查看对应实现类
    image

HierarchicalTestEngine为抽象类,主要实现了execute(ExecutionRequest request) 方法,后面执行测试用例的时候会涉及;JupiterTestEngine,一个官方的实现类,Junit5的Jupiter 测试引擎,即默认的testEngine,其中主要方法实现了TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) 方法,返回JupiterEngineDescriptor对象

@Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { JupiterConfiguration configuration = new CachingJupiterConfiguration( new DefaultJupiterConfiguration(discoveryRequest.getConfigurationParameters())); JupiterEngineDescriptor engineDescriptor = new JupiterEngineDescriptor(uniqueId, configuration); new DiscoverySelectorResolver().resolveSelectors(discoveryRequest, engineDescriptor); return engineDescriptor; }
TestDescriptor,顾名思义,测试用例描述符,一个重要的接口,测试用例本身的抽象,我们看下实现类
image

其中,JupiterEngineDescriptor,代表engine本身的抽象;ClassTestDescriptor,通常情况下测试类的抽象(代表没有嵌套测试类的情况下);TestMethodTestDescriptor ,通常情况下测试方法的抽象(其他还有测试模板,测试工厂等方式)

  介绍了这么多,我们看下发现和解析测试用例的关键方法:

`private Root discoverRoot(LauncherDiscoveryRequest discoveryRequest, String phase) {
//初始化一个Root对象
Root root = new Root(discoveryRequest.getConfigurationParameters());
//遍历测试引擎,由父节点 engine->class->method 扫描对应测试类和测试方法
//最后生成根节点
for (TestEngine testEngine : this.testEngines) {

  ......
  //发现并生成和关联各层级TestDescriptor对象,最后返回根节点engine testDescriptor
  Optional<TestDescriptor> engineRoot = discoverEngineRoot(testEngine, discoveryRequest);
  engineRoot.ifPresent(rootDescriptor -> root.add(testEngine, rootDescriptor));
}

//处理idea插件传入的后置过滤器,剪枝,即去掉需要过滤的descriptor
root.applyPostDiscoveryFilters(discoveryRequest);

//再次剪枝,去掉不包含测试用例相关的descriptor
root.prune();
return root;

} discoverEngineRoot(testEngine, discoveryRequest)方法会调用刚才说的JupiterTestEngine中的discover方法,可以看到其中直接new了一个Selector解析器去处理传输来的discoveryRequestJupiterEngineDescriptor engineDescriptor = new JupiterEngineDescriptor(uniqueId, configuration);
new DiscoverySelectorResolver().resolveSelectors(discoveryRequest, engineDescriptor);`
下节将详细介绍Selector解析器解析用例文件的流程。

SelectorResolver是一个接口,官方注释如下:
A resolver that supports resolving one or multiple types of {@link DiscoverySelector DiscoverySelectors}.An implementation of a {@code resolve()} method is typically comprised of the following steps: Check whether the selector is applicable for the current {@link org.junit.platform.engine.TestEngine} and the current {@link org.junit.platform.engine.EngineDiscoveryRequest} (e.g. for a test class: is it relevant for the current engine and does it pass all filters in the request?). If so, use the supplied {@link Context Context}, to add one or multiple {@link TestDescriptor TestDescriptors} to the designated parent (see {@link Context Context} for details) and return a {@linkplain Resolution#match(Match) match} or multiple {@linkplain Resolution#matches(Set) matches}. Alternatively, convert the supplied selector into one or multiple other {@linkplain Resolution#selectors(Set) selectors} (e.g. a {@link PackageSelector} into a set of {@link ClassSelector ClassSelectors}). Otherwise, return {@link Resolution#unresolved() unresolved()}.
可以看到,一个解析器根据传入的不同DiscoverySelector类型,分别解析出对应的Resolution, 结果存储在Match对象SET集合中
`public interface SelectorResolver {

//处理ClasspathResourceSelector 选择器
/**

  • Resolve the supplied {@link ClasspathResourceSelector} using the supplied
  • {@link Context Context}.
    */
    default Resolution resolve(ClasspathResourceSelector selector, Context context) {
    return resolve((DiscoverySelector) selector, context);
    }

//处理ClasspathRootSelector 选择器
/**

  • Resolve the supplied {@link ClasspathRootSelector} using the supplied
  • {@link Context Context}.

*/
default Resolution resolve(ClasspathRootSelector selector, Context context) {
return resolve((DiscoverySelector) selector, context);
}

//处理ClassSelector 选择器
/**

  • Resolve the supplied {@link ClassSelector} using the supplied
  • {@link Context Context}.

*/
default Resolution resolve(ClassSelector selector, Context context) {
return resolve((DiscoverySelector) selector, context);
}
......
}

class Resolution {

private static final Resolution UNRESOLVED = new Resolution(emptySet(), emptySet());
//解析后的Match结果集合
private final Set<Match> matches;
private final Set<? extends DiscoverySelector> selectors;
......

}

class Match {
//match中存放匹配的TestDescriptor
private final TestDescriptor testDescriptor;
//子选择器,从当前父DiscoverySelector到子DiscoverySelector 循环解析
private final Supplier<Set<? extends DiscoverySelector>> childSelectorsSupplier;
private final Type type;
......
} 在前面说到的discover方法里面,会调用DiscoverySelectorResolver的resolveSelectors方法解析 • new DiscoverySelectorResolver().resolveSelectors(discoveryRequest, engineDescriptor); DiscoverySelectorResolver中初始化了常用的部分解析器,其中包括ClassContainerSelectorResolver,ClassSelectorResolver和MethodSelectorResolver三个SelectorResolver和 MethodOrderingVisitor方法排序TestDescriptor.Visitor、 TestDescriptor::prune剪枝TestDescriptor.Visitor, vistor方法主要用来做后置的一些处理public class DiscoverySelectorResolver {
// @formatter:off
private static final EngineDiscoveryRequestResolver resolver = EngineDiscoveryRequestResolver.builder()
.addClassContainerSelectorResolver(new IsTestClassWithTests())
.addSelectorResolver(context -> new ClassSelectorResolver(context.getClassNameFilter(), context.getEngineDescriptor().getConfiguration()))
.addSelectorResolver(context -> new MethodSelectorResolver(context.getEngineDescriptor().getConfiguration()))
.addTestDescriptorVisitor(context -> new MethodOrderingVisitor(context.getEngineDescriptor().getConfiguration()))
.addTestDescriptorVisitor(context -> TestDescriptor::prune)
.build();
// @formatter:on

public void resolveSelectors(EngineDiscoveryRequest request, JupiterEngineDescriptor engineDescriptor) {
resolver.resolve(request, engineDescriptor);
}
}`
resolver.resolve方法中,直接初始化EngineDiscoveryRequestResolution对象,并执行run方法,在run方法中循环解析传入的DiscoverySelector对象和子DiscoverySelector对象,并生成Resolution结果

点击查看代码
void run() {
    //获取request对象中selector选择器,即前文所描述的EngineDiscoveryRequest 对象,并加入d
    remainingSelectors.addAll(request.getSelectorsByType(DiscoverySelector.class));
    //循环处理selector队列,依次解析生成Resolution对象
    while (!remainingSelectors.isEmpty()) {
      resolveCompletely(remainingSelectors.poll());
    }
    visitors.forEach(engineDescriptor::accept);
  }
private void resolveCompletely(DiscoverySelector selector) {
    try {
      //解析DiscoverySelector 对象
      Optional<Resolution> result = resolve(selector);
      if (result.isPresent()) {
        //里面会处理当前selector中包含的子selector,并放入remainingSelectors队列
        enqueueAdditionalSelectors(result.get());
      }
      else {
        //记录解析失败的selector和原因
        logUnresolvedSelector(selector, null);
      }
    }
    catch (Throwable t) {
       ......
    }
  }  
   其中,调用resolve方法解析selector是关键流程,从初始化的resolvers中依次调用reslove方法,即上文所述的ClassContainerSelectorResolver,ClassSelectorResolver,MethodSelectorResolver依次处理selector,过滤isResolved的Resolution结果,把对应结果放入Map<DiscoverySelector, Resolution> resolvedSelectors中,把match结果存入Map<UniqueId, Match> resolvedUniqueIds中
    在resolve(DiscoverySelector selector)方法中,会依次判断selector类型,调用对应类型的resolver去处理
点击查看代码
private Optional<Resolution> resolve(DiscoverySelector selector,
      Function<SelectorResolver, Resolution> resolutionFunction) {
    // @formatter:off
    return resolvers.stream()
        .map(resolutionFunction)
        .filter(Resolution::isResolved)
        .findFirst()
        .map(resolution -> {
          contextBySelector.remove(selector);
          resolvedSelectors.put(selector, resolution);
          resolution.getMatches()
              .forEach(match -> resolvedUniqueIds.put(match.getTestDescriptor().getUniqueId(), match));
          return resolution;
        });
    // @formatter:on
  }

private Optional<Resolution> resolve(DiscoverySelector selector) {
    if (resolvedSelectors.containsKey(selector)) {
      return Optional.of(resolvedSelectors.get(selector));
    }
    if (selector instanceof UniqueIdSelector) {
      return resolveUniqueId((UniqueIdSelector) selector);
    }
    return resolve(selector, resolver -> {
      Context context = getContext(selector);
      if (selector instanceof ClasspathResourceSelector) {
        return resolver.resolve((ClasspathResourceSelector) selector, context);
      }
      //selector为ClasspathRootSelector的处理
      if (selector instanceof ClasspathRootSelector) {
        return resolver.resolve((ClasspathRootSelector) selector, context);
      }
      //selector为ClassSelector的处理
      if (selector instanceof ClassSelector) {
        return resolver.resolve((ClassSelector) selector, context);
      }
      ......
      //selector为MethodSelector的处理
      if (selector instanceof MethodSelector) {
        return resolver.resolve((MethodSelector) selector, context);
      }
      ......
      return resolver.resolve(selector, context);
    });
  }

从前文知道传入的selector为ClasspathRootSelector,对应的解析器为ClassContainerSelectorResolver,resolve方法主要为处理其中的测试类,并生成对应的ClassSelector,通过前文描述的 enqueueAdditionalSelectors方法加入到selector队列








public Resolution resolve(ClasspathRootSelector selector, Context context) { return classSelectors(findAllClassesInClasspathRoot(selector.getClasspathRoot(), classFilter, classNameFilter)); } ...... //解析classPathRoot中的class即测试类,生成ClassSelector private Resolution classSelectors(List<Class> classes) { return selectors(classes.stream().map(DiscoverySelectors::selectClass).collect(toSet())); } 循环解析selector过程中,解析到当前seletor为ClassSelector,对应的解析器为ClassSelectorResolver, • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • public Resolution resolve(ClassSelector selector, Context context) { Class testClass = selector.getJavaClass(); //校验当前testClass是否是测试类 if (isTestClassWithTests.test(testClass)) { // Nested tests are never filtered out if (classNameFilter.test(testClass.getName())) { //生成resolution return toResolution( //生成ClassTestDescriptor,同时关联父子关系,当前parent为JupiterEngineDescriptor context.addToParent(parent -> Optional.of(newClassTestDescriptor(parent, testClass)))); } } else if (isNestedTestClass.test(testClass)) { ...... } return unresolved(); } private Resolution toResolution(Optional testDescriptor) { return testDescriptor.map(it -> { Class testClass = it.getTestClass(); //默认testClasses 为空 List<Class<?>> testClasses = new ArrayList<>(it.getEnclosingTestClasses()); testClasses.add(testClass); // @formatter:off return Resolution.match( //构造一个包含子类selector的match对象 Match.exact(it, () -> { //查找测试类中测试方法,生成子selector,即MethodSelector Stream methods = findMethods(testClass, isTestOrTestFactoryOrTestTemplateMethod).stream() .map(method -> selectMethod(testClasses, method)); //查找内嵌测试class,忽略 Stream nestedClasses = findNestedClasses(testClass, isNestedTestClass).stream() .map(nestedClass -> new NestedClassSelector(testClasses, nestedClass)); return Stream.concat(methods, nestedClasses).collect(toCollection((Supplier<Set>) LinkedHashSet::new)); })); // @formatter:on }).orElse(unresolved()); } public static Match exact(TestDescriptor testDescriptor, Supplier<Set<? extends DiscoverySelector>> childSelectorsSupplier) { return new Match(testDescriptor, childSelectorsSupplier, Type.EXACT); }
现在再循环解析selector,就存在MethodSelector了,对应解析器为MethodSelectorResolver,




































































private Resolution resolve(Context context, List<Class> enclosingClasses, Class testClass, Method method) { // @formatter:off //MethodType是个枚举,遍历MethodType转为stream,依次调用methodType的resolve方法 Set matches = Arrays.stream(MethodType.values()) .map(methodType -> methodType.resolve(enclosingClasses, testClass, method, context, configuration)) .filter(Optional::isPresent) .map(Optional::get) //expansionCallback 获取Filterable类型的 TestDescriptor,忽略 .map(testDescriptor -> Match.exact(testDescriptor, expansionCallback(testDescriptor))) .collect(toSet()); // @formatter:on if (matches.size() > 1) { ...... } //通过 Resolution matches(Set matches) 方法生成 resolution return matches.isEmpty() ? unresolved() : matches(matches); } private enum MethodType { //最常用的test类型 TEST(new IsTestMethod(), TestMethodTestDescriptor.SEGMENT_TYPE) { @Override protected TestDescriptor createTestDescriptor(UniqueId uniqueId, Class testClass, Method method, JupiterConfiguration configuration) { return new TestMethodTestDescriptor(uniqueId, testClass, method, configuration); } }, TEST_FACTORY(new IsTestFactoryMethod(), TestFactoryTestDescriptor.SEGMENT_TYPE, TestFactoryTestDescriptor.DYNAMIC_CONTAINER_SEGMENT_TYPE, TestFactoryTestDescriptor.DYNAMIC_TEST_SEGMENT_TYPE) { @Override protected TestDescriptor createTestDescriptor(UniqueId uniqueId, Class testClass, Method method, JupiterConfiguration configuration) { return new TestFactoryTestDescriptor(uniqueId, testClass, method, configuration); } }, ...... private final Predicate methodPredicate; private final String segmentType; private final Set dynamicDescendantSegmentTypes;
// 构造方法 MethodType(Predicate methodPredicate, String segmentType, String... dynamicDescendantSegmentTypes) { this.methodPredicate = methodPredicate; this.segmentType = segmentType; this.dynamicDescendantSegmentTypes = new LinkedHashSet<>(Arrays.asList(dynamicDescendantSegmentTypes)); } //resolve方法 private Optional resolve(List<Class> enclosingClasses, Class testClass, Method method, Context context, JupiterConfiguration configuration) { // 调用Predicate方法校验,Test枚举对应new IsTestMethod() if (!methodPredicate.test(method)) { return Optional.empty(); } // 关联parent ClassTestDescriptor和当前 MethodType中各枚举值对应的 createTestDescriptor创建的 TestDescriptor //当前为 子TestDescriptor 为 TestMethodTestDescriptor return context.addToParent(() -> selectClass(enclosingClasses, testClass), // parent -> Optional.of( createTestDescriptor(createUniqueId(method, parent), testClass, method, configuration))); } ......}
至此,从通过idea传入request对象,到循环解析其中的父selector和子selector,通过addToParent方法构建父子TestDescriptor关联关系,完成了测试类到测试方法等解析工作,最后返回了根节点TestDescriptor即JupiterEngineDescriptor对象,和上篇描述的discoverRoot方法中处理流程一致




...... //发现并生成和关联各层级TestDescriptor对象,最后返回根节点engine testDescriptor Optional engineRoot = discoverEngineRoot(testEngine, discoveryRequest); ......
解析测试文件生成TestDescriptor后,后面流程就可以开始执行对应测试用例了
To be continued...

前面讲了测试用例相关的数据结构是怎么生成的,当框架解析了测试用例后,后面就是执行了。执行方法的入口在DefaultLauncher.execute()方法































@Override public void execute(LauncherDiscoveryRequest discoveryRequest, TestExecutionListener... listeners) { ...... execute(InternalTestPlan.from(discoverRoot(discoveryRequest, "execution")), listeners); }
@Override public void execute(TestPlan testPlan, TestExecutionListener... listeners) { ...... execute((InternalTestPlan) testPlan, listeners); } private void execute(InternalTestPlan internalTestPlan, TestExecutionListener[] listeners) { Root root = internalTestPlan.getRoot(); ConfigurationParameters configurationParameters = root.getConfigurationParameters(); TestExecutionListenerRegistry listenerRegistry = buildListenerRegistryForExecution(listeners); //构造lambda表达式,遍历testEngine,分别执行其中的testDescriptor withInterceptedStreams(configurationParameters, listenerRegistry, testExecutionListener -> { ...... ExecutionListenerAdapter engineExecutionListener = new ExecutionListenerAdapter(internalTestPlan, testExecutionListener); //循环遍历testEngine 中的estDescriptor ,依次执行 for (TestEngine testEngine : root.getTestEngines()) { TestDescriptor testDescriptor = root.getTestDescriptorFor(testEngine); execute(testEngine, //构造ExecutionRequest对象 new ExecutionRequest(testDescriptor, engineExecutionListener, configurationParameters)); } ...... }); }
实际依次调用执行了三个execute方法,最后execute执行的参数是构造的InternalTestPlan对象,使用InternalTestPlan.from()方法生成















static InternalTestPlan from(Root root) { //生成委托对象,传入前面解析完成的根节点TestDescriptor即JupiterEngineDescriptor TestPlan delegate = TestPlan.from(root.getEngineDescriptors()); return new InternalTestPlan(root, delegate); } public static TestPlan from(Collection engineDescriptors) { Preconditions.notNull(engineDescriptors, "Cannot create TestPlan from a null collection of TestDescriptors"); TestPlan testPlan = new TestPlan(engineDescriptors.stream().anyMatch(TestDescriptor::containsTests)); //添加到testPlan中的allIdentifiers map中 Visitor visitor = descriptor -> testPlan.add(TestIdentifier.from(descriptor)); engineDescriptors.forEach(engineDescriptor -> engineDescriptor.accept(visitor)); return testPlan; }
在withInterceptedStreams方法中,主要就是执行lambda表达式,所以重点还是execute(InternalTestPlan internalTestPlan, TestExecutionListener[] listeners)方法中的lambda表达式,如上述中循环遍历testEngine根节点,调用第四个execute方法 execute(TestEngine testEngine, ExecutionRequest executionRequest)执行,在其中调用testEngine的execute方法执行,入参ExecutionRequest 是一个包装类,主要包含TestDescriptor对象和EngineExecutionListener监听器对象


























private void withInterceptedStreams(ConfigurationParameters configurationParameters, TestExecutionListenerRegistry listenerRegistry, Consumer action) { //获取CompositeTestExecutionListener,实际是个组合类,listener的组合, //其中主要调用notifyEach 方法依次执行所有的listener对应的方法,如executionStarted,executionFinished等 TestExecutionListener testExecutionListener = listenerRegistry.getCompositeTestExecutionListener(); ...... try { //执行lambda表达式 action.accept(testExecutionListener); } finally { ...... } } private void execute(TestEngine testEngine, ExecutionRequest executionRequest) { try { //调用engine的excute方法 testEngine.execute(executionRequest); } catch (Throwable throwable) { handleThrowable(testEngine, "execute", throwable); } }

  说到testEngine的execute方法,我们先看看JupiterTestEngine的继承结构,如下图所示:

  由于JupiterTestEngine中没有实现execute方法,会直接调用父类HierarchicalTestEngine中的execute方法



































@Overridepublic final void execute(ExecutionRequest request) { //创建执行器service,如果没有并非执行相关配置,默认直接返回单线程执行器service //这里返回默认执行器service - SameThreadHierarchicalTestExecutorService try (HierarchicalTestExecutorService executorService = createExecutorService(request)) { //创建,这里返回JupiterEngineExecutionContext对象 C executionContext = createExecutionContext(request); //返回包含 TestAbortedException(依赖于opentest4j库) Predicate<? super Throwable> 的 OpenTest4JAwareThrowableCollector ThrowableCollector.Factory throwableCollectorFactory = createThrowableCollectorFactory(request); //调用HierarchicalTestExecutor的execute方法,并get取得结果 new HierarchicalTestExecutor<>(request, executionContext, executorService, throwableCollectorFactory).execute().get(); } catch (Exception exception) { throw new JUnitException("Error executing tests for engine " + getId(), exception); } } //HierarchicalTestExecutor 的execute方法 Future execute() { TestDescriptor rootTestDescriptor = this.request.getRootTestDescriptor(); EngineExecutionListener executionListener = this.request.getEngineExecutionListener(); //walk方法解决资源竞争的问题,当前不涉及 NodeExecutionAdvisor executionAdvisor = new NodeTreeWalker().walk(rootTestDescriptor); //通过listener,executionService,throwableCollectorFactory,executionAdvisor构造taskContext NodeTestTaskContext taskContext = new NodeTestTaskContext(executionListener, this.executorService, this.throwableCollectorFactory, executionAdvisor); //通过taskContext和 根testDescriptor构造NodeTestTask对象,这个对象是后面执行测试用例的关键 NodeTestTask rootTestTask = new NodeTestTask<>(taskContext, rootTestDescriptor);
rootTestTask.setParentContext(this.rootContext); //提交给executorService执行NodeTestTask return this.executorService.submit(rootTestTask); }
NodeTestTask 是执行测试用例的关键,下节将详细讲解其执行流程。
To be continued...
执行一个测试用例构造了NodeTestTask对象,提交给ExecutorService后调用了NodeTestTask的execute方法


























@Override public void execute() { try { throwableCollector = taskContext.getThrowableCollectorFactory().create(); //第一步, 准备执行的 context prepare(); if (throwableCollector.isEmpty()) { checkWhetherSkipped(); } if (throwableCollector.isEmpty() && !skipResult.isSkipped()) { //第二步,递归执行,即由根节点engine- class - method 顺序执行用例 executeRecursively(); } if (context != null) { //第三步,清理资源 cleanUp(); } //最后,用例执行完成后的收尾工作,调用 node.nodeFinished 方法 reportCompletion(); } finally { ...... } ...... context = null; }
我们可以看到,执行测试用例可以分为三步走,分别为准备需要执行的context, 依照顺序递归执行,最后清理context资源。讲解具体流程,我们先看下NodeTestTask的数据结构:







































class NodeTestTask implements TestTask {
private static final Logger logger = LoggerFactory.getLogger(NodeTestTask.class);
private final NodeTestTaskContext taskContext; private final TestDescriptor testDescriptor; private final Node node;
private C parentContext; private C context;
private SkipResult skipResult; private boolean started; private ThrowableCollector throwableCollector;
NodeTestTask(NodeTestTaskContext taskContext, TestDescriptor testDescriptor) { this.taskContext = taskContext; this.testDescriptor = testDescriptor; this.node = NodeUtils.asNode(testDescriptor); } ......}
interface TestTask { /** * Get the {@linkplain ExecutionMode execution mode} of this task. / ExecutionMode getExecutionMode();
/
* * Get the {@linkplain ResourceLock resource lock} of this task. / ResourceLock getResourceLock();
/
* * Execute this task. */ void execute(); }
我们可以看到,NodeTestTask中主要属性有父context parentContext和当前执行的context, 根节点TestDescriptor对象和NodeTestTaskContext对象, NodeTestTaskContext 主要包含EngineExecutionListener对象,用于在用例执行的各阶段调用listener的对应的通知方法,如executionStarted、executionFinished方法等。
这里有个Node对象,Node是个接口,抽象了执行的一个单元,如engine, class, method等,可以看到各个TestDescriptor都继承并实现了Node部分方法




















































































































public interface Node {
/** * Prepare the supplied {@code context} prior to execution. *

The default implementation returns the supplied {@code context} unmodified. * @see #cleanUp(EngineExecutionContext) / default C prepare(C context) throws Exception { return context; }
/
* * Clean up the supplied {@code context} after execution. *

The default implementation does nothing. * / default void cleanUp(C context) throws Exception { }
/
* * Determine if the execution of the supplied {@code context} should be
/ default SkipResult shouldBeSkipped(C context) throws Exception { return SkipResult.doNotSkip(); }
/
* * Execute the before behavior of this node. *

This method will be called once before {@linkplain #execute * execution} of this node. *

The default implementation returns the supplied {@code context} unmodified. * @param context the context to execute in * @return the new context to be used for children of this node; never * {@code null} / default C before(C context) throws Exception { return context; }
/
* * Execute the behavior of this node. * *

Containers typically do not implement this method since the * {@link HierarchicalTestEngine} handles execution of their children. * *

The supplied {@code dynamicTestExecutor} may be used to submit * additional dynamic tests for immediate execution. *

The default implementation returns the supplied {@code context} unmodified.

  • @param context the context to execute in * @param dynamicTestExecutor the executor to submit dynamic tests to * @return the new context to be used for children of this node and for the * after behavior of the parent of this node, if any / default C execute(C context, DynamicTestExecutor dynamicTestExecutor) throws Exception { return context; }
    /
    * * Execute the after behavior of this node.

  • This method will be called once after {@linkplain #execute * execution} of this node.

  • The default implementation does nothing.

  • @param context the context to execute in / default void after(C context) throws Exception { }
    /
    * * Wraps around the invocation of {@link #before(EngineExecutionContext)}, * {@link #execute(EngineExecutionContext, DynamicTestExecutor)}, and * {@link #after(EngineExecutionContext)}. * * @param context context the context to execute in * @param invocation the wrapped invocation (must be invoked exactly once) * @since 1.4 / @API(status = EXPERIMENTAL, since = "1.4") default void around(C context, Invocation invocation) throws Exception { invocation.invoke(context); }
    /
    * * Callback invoked when the execution of this node has been skipped. * *

    The default implementation does nothing. * * @param context the execution context * @param testDescriptor the test descriptor that was skipped * @param result the result of skipped execution * @since 1.4 / @API(status = EXPERIMENTAL, since = "1.4", consumers = "org.junit.platform.engine.support.hierarchical") default void nodeSkipped(C context, TestDescriptor testDescriptor, SkipResult result) { }
    /
    * * Callback invoked when the execution of this node has finished. * *

    The default implementation does nothing. * * @param context the execution context * @param testDescriptor the test descriptor that was executed * @param result the result of the execution * @since 1.4 */ @API(status = EXPERIMENTAL, since = "1.4", consumers = "org.junit.platform.engine.support.hierarchical") default void nodeFinished(C context, TestDescriptor testDescriptor, TestExecutionResult result) { } ......}
    我们可以看到Node接口中有prepare,before, after等方法,各个层级的TestDescriptor会根据自己的需要,选择实现对应的方法。NodeTestTask中的prepare方法,实际就是调用了node的prepare方法






    private void prepare() { throwableCollector.execute(() -> context = node.prepare(parentContext)); // Clear reference to parent context to allow it to be garbage collected. // See https://github.com/junit-team/junit5/issues/1578 parentContext = null; }
    我们查看对应实现prepare方法的TestDescriptor, 其中有EngineDescriptor, ClassBasedTestDescripor, TestMethodTestDescriptor等,即从engine -> class -> method各个层级都实现了prepare方法。由此可知,上层级的TestDescriptor 调用prepare方法都是为了子节点执行做好相关准备

    查看JupiterEngineDescriptor的prepare方法,实际重新生成了JupiterEngineExecutionContext对象,为class执行做好准备      
    

















public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) { //重新生成extensionRegistry, 当前还未有springExtension关键扩展类 MutableExtensionRegistry extensionRegistry = MutableExtensionRegistry.createRegistryWithDefaultExtensions( context.getConfiguration()); //执行监听器 EngineExecutionListener executionListener = context.getExecutionListener(); //当前容器或者测试用例执行的extensionContext ExtensionContext extensionContext = new JupiterEngineExtensionContext(executionListener, this, context.getConfiguration());
// 重新生成EngineExecutionContext return context.extend() .withExtensionRegistry(extensionRegistry) .withExtensionContext(extensionContext) .build(); }
接下来执行第二步,执行NodeTestTask中的executeRecursively方法































private void executeRecursively() { ..... throwableCollector.execute(() -> { node.around(context, ctx -> { context = ctx; throwableCollector.execute(() -> { //这里直接通过查找出的子testDescriptor 直接生成子NodeTestTask List<NodeTestTask> children = testDescriptor.getChildren().stream() .map(descriptor -> new NodeTestTask(taskContext, descriptor)) .collect(toCollection(ArrayList::new));
//执行对应node节点即testDescripitor的before方法,没有略过 context = node.before(context); //生成DynamicTestExecutor对象 final DynamicTestExecutor dynamicTestExecutor = new DefaultDynamicTestExecutor(); //执行execute方法,没有略过 context = node.execute(context, dynamicTestExecutor);
if (!children.isEmpty()) { children.forEach(child -> child.setParentContext(context)); //通过taskConext中d的executorService执行子节点转化的 NodeTestTask taskContext.getExecutorService().invokeAll(children); }
throwableCollector.execute(dynamicTestExecutor::awaitFinished); });
throwableCollector.execute(() -> node.after(context)); }); }); }
我们可以查看到JupiterEngineDescriptor中,没有execute方法,故没有执行逻辑,直接返回context,在执行到taskContext.getExecutorService().invokeAll(children)方法时候,执行TestTask::execute接口方法,继续回调NodeTestTask的execute方法,回到了本节开始的地方,从新开始执行,只是这个时候node已经变成了传过来的children即ClassTestDescriptor了,这个执行流程大致如下

这个调用循环一直到当前子NodeTestTask为空为止,即当前testDescriptor.getChildren()获取的Set为空,当前为methodTestDescirptor即可满足条件。
下节将详细分析classTestDescriptor 和methodTestDescriptor所构成的NodeTestTask所执行的重要方法。
To be continued...
我们先看下ClassTestDescriptor所构成的NodeTestTask的执行流程,执行before方法,跳转到父类ClassBasedTestDescriptor 里面执行





















































public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) { //查找新的Extension, 这里才注册SpringExtension MutableExtensionRegistry registry = populateNewExtensionRegistryFromExtendWithAnnotation( context.getExtensionRegistry(), this.testClass);
// Register extensions from static fields here, at the class level but // after extensions registered via @ExtendWith. //查找filed 域的扩展,如被注解 @RegisterExtension 修饰的属性 registerExtensionsFromFields(registry, this.testClass, null);
// Resolve the TestInstanceFactory at the class level in order to fail // the entire class in case of configuration errors (e.g., more than // one factory registered per class). //当前为null, 跳过 this.testInstanceFactory = resolveTestInstanceFactory(registry);
//注册 被注解BeforeEach 修饰的方法,在我们当前举得例子中,是基类BaseTest中的init方 //这里注册的是 BeforeEachMethodAdapter Extensio registerBeforeEachMethodAdapters(registry); //注册 被注解AfterEach 修饰的方法 registerAfterEachMethodAdapters(registry);
ThrowableCollector throwableCollector = createThrowableCollector(); ClassExtensionContext extensionContext = new ClassExtensionContext(context.getExtensionContext(), context.getExecutionListener(), this, this.lifecycle, context.getConfiguration(), throwableCollector); //通过注解查找beforeAll方法 this.beforeAllMethods = findBeforeAllMethods(this.testClass, this.lifecycle == Lifecycle.PER_METHOD); //通过注解查找afterAll方法 this.afterAllMethods = findAfterAllMethods(this.testClass, this.lifecycle == Lifecycle.PER_METHOD);
//刷新context return context.extend() //这里是个关键流程,注册了TestInstancesProvider,为后面初始化testInstance做准备 .withTestInstancesProvider(testInstancesProvider(context, extensionContext)) .withExtensionRegistry(registry) .withExtensionContext(extensionContext) .withThrowableCollector(throwableCollector) .build(); // @formatter:on } //lambda初始化TestInstancesProvider接口实现类 private TestInstancesProvider testInstancesProvider(JupiterEngineExecutionContext parentExecutionContext, ClassExtensionContext extensionContext) {
return (registry, registrar) -> extensionContext.getTestInstances().orElseGet( () -> instantiateAndPostProcessTestInstance(parentExecutionContext, extensionContext, registry, registrar)); } private TestInstances instantiateAndPostProcessTestInstance(JupiterEngineExecutionContext parentExecutionContext, ExtensionContext extensionContext, ExtensionRegistry registry, ExtensionRegistrar registrar) { ...... }
执行完prepare方法后,在executeRecursively方法中继续执行node.before方法




































public JupiterEngineExecutionContext before(JupiterEngineExecutionContext context) { ThrowableCollector throwableCollector = context.getThrowableCollector(); ...... if (throwableCollector.isEmpty()) { context.beforeAllCallbacksExecuted(true); //执行beforeAllCallBack方法,在这里初始化TestContextManager invokeBeforeAllCallbacks(context);
if (throwableCollector.isEmpty()) { context.beforeAllMethodsExecuted(true); //执行beforeAll 方法 invokeBeforeAllMethods(context); } } ...... return context; } ...... private void invokeBeforeAllCallbacks(JupiterEngineExecutionContext context) { ...... for (BeforeAllCallback callback : registry.getExtensions(BeforeAllCallback.class)) { //实现BeforeAllCallback的实现类主要有TimeOUtExtension,SpringExtension, TempDirectory, OutputCaptureExtension //这里我们主要关注SpringExtension 相关的处理方法 throwableCollector.execute(() -> callback.beforeAll(extensionContext)); if (throwableCollector.isNotEmpty()) { break; } } } //SpringExtension的beforeAll方法 public void beforeAll(ExtensionContext context) throws Exception { getTestContextManager(context).beforeTestClass(); }
在SpringExtension的beforeAll方法中,初始化了TestContextManager对象,循环遍历TestExecutionListener,执行listener的beforeTestClass方法
这里涉及到了Spring TestContext Framework机制,详细机制讲解可见官方文档
https://docs.spring.io/spring-framework/docs/current/reference/html/testing.html#testcontext-framework


































/** * Get the {@link TestContextManager} associated with the supplied {@code ExtensionContext}. * @return the {@code TestContextManager} (never {@code null}) */private static TestContextManager getTestContextManager(ExtensionContext context) { Assert.notNull(context, "ExtensionContext must not be null"); Class testClass = context.getRequiredTestClass(); //获取当前的ExtensionContext中的ExtensionValuesStore对象valuesStore Store store = getStore(context); //存在则获取Supplier

标签:分析,execute,return,private,selector,源码,context,new,Junit5
From: https://www.cnblogs.com/bobo503/p/18020112

相关文章

  • docker启动mysql失败原因分析
    dockerlogsmysql 发现问题Can'treaddirof'/etc/mysql/conf.d/修改原因:原来的命令:dockerrun-p3306:3306--namemysql-v/mydata/mysql/log:/var/log/mysql-v/mydata/mysql/data:/var/lib/mysql -v/mydata/mysql/conf:/etc/mysql-eMYSQL_ROOT_PASSWORD=roo......
  • 数学分析中间断点的类型
    在数学分析中,函数的间断点是指函数在该点附近的行为表现出不一致或者极端性的点。间断点的类型主要有两种:第一类间断点和第二类间断点。第一类间断点:可去间断点和跳跃间断点。可去间断点(RemovableDiscontinuity):如果函数在某点的左极限和右极限都存在且相等,但函数在该点要么没有......
  • 跨界协作:借助gRPC实现Python数据分析能力的共享
    gRPC是一个高性能、开源、通用的远程过程调用(RPC)框架,由Google推出。它基于HTTP/2协议标准设计开发,默认采用ProtocolBuffers数据序列化协议,支持多种开发语言。在gRPC中,客户端可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法,使得您能够更容易地创建分布式应用......
  • Swoole 源码分析之 Http Server 模块
    首发原文链接:Swoole源码分析之HttpServer模块Swoole源码分析之HttpServer模块Http模块的注册初始化这次我们分析的就是Swoole官网的这段代码,看似简单,实则不简单。在Swoole源码文件swoole_http_server.c中有这样一个函数php_swoole_http_server_minit。这个......
  • POLIR-Economics-Microeconomics: 经济模型{静态分析+比较静态分析+动态分析}}@<<西方
    经济理论经济理论是在对现实的经济事物的主要特征和内在联系进行概括和抽象的基础上,对现实的经济事务进行的系统描述;西方经济学家认为由于现实的经济事务是错综复杂的,所以在研究每一个经济事物时,往往要舍弃一些非基本的因素,只就经济事物的基本因素及其相互之间的......
  • Lex 生成一个词法分析器
     lex通过输入一个.l文件生成一个lex.yy.c文件,然后通过c编译器编译成一个可执行的词法分析器。该词法分析器扫描输入源文件,生成一个token符号流给后面语法分析器使用。 .l文件的结构,分成三个部分,声明,转换规则,自定义规则。三个部分由%%分割declarations%%transl......
  • 大数分析(5)——BMS(两行)
    前言在稍微过了一遍反射和最基本的稳定之后,我们终于可以着手分析当今最前沿的两个记号了不过BMS和Y序列其实都和PrSS和Hydra模式有关,进而也就是和树型模式有关首先是BashicuMatrixSystem,简称BMS单行BMS请参考PrSS,顺便可以复习一下,每一项少一即可两行BMS标准型是矩阵形式,......
  • 25个常见的python系统设计源码(python+mysql+vue)
    收集整理了25个常见的python系统设计源码。可以用于课程作业或者毕业设计。所有系统都带源码和文档。1.网上商城系统这是一个基于python+vue开发的商城网站,平台采用B/S结构,后端采用主流的Python语言进行开发,前端采用主流的Vue.js进行开发。整个平台包括前台和后台两个部分。......
  • 100 行代码实现用户登录注册与 RESTful 接口 - 手把手教程附 Python 源码
    在开发大多数应用时,用户系统都是必不可少的部分,而我们总是需要开发围绕用户的登录,注册,获取,更新等接口。在这篇文章将带你用一百多行代码简洁地实现一套这样的用户鉴权与RESTful接口,并使用Session来处理用户的登录登出我们将使用UtilMeta框架完成接口开发,这是一个开源的Py......
  • NLP-情感分析 Prompting
    **NLP-情感分析Prompting**注:本文是Transformers快速入门Prompting章节的学习笔记,更详细的分析请参见原文。写在前面Github地址:https://github.com/Lockegogo/NLP_Tasks/tree/main/text_cls_prompt_senti本项目使用Prompting方法完成情感分析任务。Prompting方法......