近期使用junit和springtest 做公司的一个灰盒自动化项目,即非白盒单测和黑盒接口方式的自动化方式,验证代码中复杂的业务逻辑(金融相关),使用过程中遇到过一些使用问题,业余时间学习了下框架源码,略有收获,遂记录之。
创建一个简单测试DEMO如下:
新建一个TestApplication和一个server
新建测试类如下:
这样,一个简单的测试程序就可以跑了。
我们可以在test用例中随便设置一个断点,选中两个测试类DEBUG启动程序后,查看调用栈如下:
我们先看左边的调用栈,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工具的关系如下
简单描述下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
.....
// Discovery filters are handed through to all engines to be applied during discovery.
private final List<DiscoveryFilter<?>> discoveryFilters;
.......
}`
我们只关注有值的部分,DiscoverySelector接口实现类对象和 DiscoveryFilter 接口实现类对象
可以看到selectors 变量是 ClasspathRootSelector对象,存储的是业务类和测试类代码URI路径。discoverFilters变量为idea插件传入的值,顾名思义,filter的作用是为后面流程用来过滤测试类做处理(include和exclude).
上文我们介绍了junit5框架架构和对应的入口方法,主要由idea插件调用并传入LauncherDiscoveryRequest对象,根据经验,我们知道测试框架需要解析对应的测试用例文件,生成测试用例,组装参数,然后才能执行,收集结果等;我们详细看下框架是怎么解析并生成测试用例的。
我们先看下相关重要的类数据结构:
`//代表这次测试计划的抽象,里面包含所有的测试用例
public class TestPlan {
//TestIdentifier ,代表一个测试用例或者容器的唯一标识
//解析的根节点集合
private final Set
//后代TestIdentifier集合,key为 父节点的unigueId
private final Map<String, Set
//所有的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(CollectionengineDescriptors) {
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);
......
}`
我们查看对应实现类
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解析器解析用例文件的流程。
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
.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
现在再循环解析selector,就存在MethodSelector了,对应解析器为MethodSelectorResolver,
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
private Resolution resolve(Context context, List<Class> enclosingClasses, Class testClass, Method method) { // @formatter:off //MethodType是个枚举,遍历MethodType转为stream,依次调用methodType的resolve方法 Set
// 构造方法 MethodType(Predicate
至此,从通过idea传入request对象,到循环解析其中的父selector和子selector,通过addToParent方法构建父子TestDescriptor关联关系,完成了测试类到测试方法等解析工作,最后返回了根节点TestDescriptor即JupiterEngineDescriptor对象,和上篇描述的discoverRoot方法中处理流程一致
•
•
•
•
...... //发现并生成和关联各层级TestDescriptor对象,最后返回根节点engine testDescriptor Optional
解析测试文件生成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
在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
说到testEngine的execute方法,我们先看看JupiterTestEngine的继承结构,如下图所示:
由于JupiterTestEngine中没有实现execute方法,会直接调用父类HierarchicalTestEngine中的execute方法
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
@Overridepublic final void execute(ExecutionRequest request) { //创建执行器service,如果没有并非执行相关配置,默认直接返回单线程执行器service //这里返回默认执行器service - SameThreadHierarchicalTestExecutorService try (HierarchicalTestExecutorService executorService = createExecutorService(request)) { //创建
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
我们可以看到,执行测试用例可以分为三步走,分别为准备需要执行的context, 依照顺序递归执行,最后清理context资源。讲解具体流程,我们先看下NodeTestTask的数据结构:
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
class NodeTestTask
private static final Logger logger = LoggerFactory.getLogger(NodeTestTask.class);
private final NodeTestTaskContext taskContext; private final TestDescriptor testDescriptor; private final 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, Invocationinvocation) 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
//执行对应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
下节将详细分析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