Junit5 源码分析系列(二)
上文我们介绍了junit5框架架构和对应的入口方法,主要由idea插件调用并传入LauncherDiscoveryRequest对象,根据经验,我们知道测试框架需要解析对应的测试用例文件,生成测试用例,组装参数,然后才能执行,收集结果等;我们详细看下框架是怎么解析并生成测试用例的。
我们先看下相关重要的类数据结构:
//代表这次测试计划的抽象,里面包含所有的测试用例public class TestPlan {//TestIdentifier ,代表一个测试用例或者容器的唯一标识//解析的根节点集合private final Set<TestIdentifier> roots = Collections.synchronizedSet(new LinkedHashSet<>(4));//后代TestIdentifier集合,key为 父节点的unigueIdprivate final Map<String, Set<TestIdentifier>> children = new ConcurrentHashMap<>(32);//所有的TestIdentifier集合,包括父子节点,key为父子节点的unigueIdprivate 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*/(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;}/**构造方法,是否包含测试用例*/(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和TestDescriptorprivate 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和对应的根节点testDescriptorprivate 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对象
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,顾名思义,测试用例描述符,一个重要的接口,测试用例本身的抽象,我们看下实现类
介绍了这么多,我们看下发现和解析测试用例的关键方法:
private Root discoverRoot(LauncherDiscoveryRequest discoveryRequest, String phase) {//初始化一个Root对象Root root = new Root(discoveryRequest.getConfigurationParameters());//遍历测试引擎,由父节点 engine->class->method 扫描对应测试类和测试方法//最后生成根节点for (TestEngine testEngine : this.testEngines) {......//发现并生成和关联各层级TestDescriptor对象,最后返回根节点engine testDescriptorOptional<TestDescriptor> engineRoot = discoverEngineRoot(testEngine, discoveryRequest);engineRoot.ifPresent(rootDescriptor -> root.add(testEngine, rootDescriptor));}//处理idea插件传入的后置过滤器,剪枝,即去掉需要过滤的descriptorroot.applyPostDiscoveryFilters(discoveryRequest);//再次剪枝,去掉不包含测试用例相关的descriptorroot.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...
