暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Junit5 源码分析系列(三)

测开技术笔记 2021-04-11
563

      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<JupiterEngineDescriptor> resolver = EngineDiscoveryRequestResolver.<JupiterEngineDescriptor>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<? extends ClassBasedTestDescriptor> 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<DiscoverySelector> methods = findMethods(testClass, isTestOrTestFactoryOrTestTemplateMethod).stream()
.map(method -> selectMethod(testClasses, method));
        //查找内嵌测试class,忽略
Stream<NestedClassSelector> nestedClasses = findNestedClasses(testClass, isNestedTestClass).stream()
.map(nestedClass -> new NestedClassSelector(testClasses, nestedClass));
return Stream.concat(methods, nestedClasses).collect(toCollection((Supplier<Set<DiscoverySelector>>) 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<Match> 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<Match> 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<Method> methodPredicate;
private final String segmentType;
private final Set<String> dynamicDescendantSegmentTypes;


    // 构造方法
MethodType(Predicate<Method> methodPredicate, String segmentType, String... dynamicDescendantSegmentTypes) {
this.methodPredicate = methodPredicate;
this.segmentType = segmentType;
this.dynamicDescendantSegmentTypes = new LinkedHashSet<>(Arrays.asList(dynamicDescendantSegmentTypes));
}

    //resolve方法
private Optional<TestDescriptor> 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<TestDescriptor> engineRoot = discoverEngineRoot(testEngine, discoveryRequest);
  ......

       解析测试文件生成TestDescriptor后,后面流程就可以开始执行对应测试用例了

      To be continued...


文章转载自测开技术笔记,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论