vlambda博客
学习文章列表

Junit5 源码分析系列(二)

     上文我们介绍了junit5框架架构和对应的入口方法,主要由idea插件调用并传入LauncherDiscoveryRequest对象,根据经验,我们知道测试框架需要解析对应的测试用例文件,生成测试用例,组装参数,然后才能执行,收集结果等;我们详细看下框架是怎么解析并生成测试用例的。

       我们先看下相关重要的类数据结构:

//代表这次测试计划的抽象,里面包含所有的测试用例public class TestPlan {
//TestIdentifier ,代表一个测试用例或者容器的唯一标识 //解析的根节点集合 private final Set<TestIdentifier> roots = Collections.synchronizedSet(new LinkedHashSet<>(4));   //后代TestIdentifier集合,key为 父节点的unigueId private final Map<String, Set<TestIdentifier>> 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}. * * <p>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 {@code null} * @return a new test plan   */  @API(status = INTERNAL, since = "1.0") public static TestPlan from(Collection<TestDescriptor> 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}. * * <p>The supplied {@link UniqueId} must be used as the unique ID of the * returned root {@link TestDescriptor}. In addition, the {@code UniqueId} * 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 {@code null} * @param uniqueId the unique ID to be used for this test engine's * {@code TestDescriptor}; never {@code null} * @return the root {@code TestDescriptor} of this engine, typically an * instance of {@code EngineDescriptor} * @see org.junit.platform.engine.support.descriptor.EngineDescriptor */ TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId);
/** * Execute tests according to the supplied {@link ExecutionRequest}. * * <p>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);  ......}

       我们查看对应实现类

  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,顾名思义,测试用例描述符,一个重要的接口,测试用例本身的抽象,我们看下实现类

    

其中,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解析器去处理传输来的discoveryRequest

JupiterEngineDescriptor engineDescriptor = new JupiterEngineDescriptor(uniqueId, configuration);new DiscoverySelectorResolver().resolveSelectors(discoveryRequest, engineDescriptor);

       下节将详细介绍Selector解析器解析用例文件的流程。

       To be continued...