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

Junit5 源码分析系列(七)

测开技术笔记 2021-05-13
841

        我们先看看TestMethodTestDescriptor的execute方法

public JupiterEngineExecutionContext execute(JupiterEngineExecutionContext context,
DynamicTestExecutor dynamicTestExecutor) throws Exception {
ThrowableCollector throwableCollector = context.getThrowableCollector();


// @formatter:off
//执行BeforeEachCallback接口实现类,包括MockitoExtension和SpringExtension
invokeBeforeEachCallbacks(context);
if (throwableCollector.isEmpty()) {

//执行用例中的beforeEach方法
invokeBeforeEachMethods(context);
if (throwableCollector.isEmpty()) {
//执行beforeTestExecution方法
invokeBeforeTestExecutionCallbacks(context);
if (throwableCollector.isEmpty()) {
invokeTestMethod(context, dynamicTestExecutor);
}
invokeAfterTestExecutionCallbacks(context);
}
invokeAfterEachMethods(context);
}
invokeAfterEachCallbacks(context);
   // @formatter:on
   throwableCollector.assertEmpty();
return context;
}

我们详细分析下具体执行步骤:

(一)

   invokeBeforeEachCallbacks 方法执行 registry中实现了接口BeforeEachCallback的扩展类,我们主要看SpringExtension的实现;对应实现的方法为SpringExtension 中的 beforeEach方法

public void beforeEach(ExtensionContext context) throws Exception { 
//获取之前生成的testInstance,已经实例化的测试类
Object testInstance = context.getRequiredTestInstance();
//当前需要执行的测试方法
Method testMethod = context.getRequiredTestMethod();
//执行TestContextManager的beforeTestMethod方法
getTestContextManager(context).beforeTestMethod(testInstance, testMethod);
}

在执行TestContextManager的beforeTestMethod方法时,和之前执行prepareTestInstance方法类似,实际我们可以发现,TestContextManager中执行pre/post 等hook方法时,都是遍历TestExecutionListener列表,依次调用每个Listener的对应方法

public void beforeTestMethod(Object testInstance, Method testMethod) throws Exception {
String callbackName = "beforeTestMethod";
prepareForBeforeCallback(callbackName, testInstance, testMethod);


for (TestExecutionListener testExecutionListener : getTestExecutionListeners()) {
try {
testExecutionListener.beforeTestMethod(getTestContext());
}
catch (Throwable ex) {
handleBeforeException(ex, callbackName, testExecutionListener, testInstance, testMethod);
}
}
}

我们看下主要的Listener对应的beforeTestMethod方法

  • MockitoTestExecutionListener

     因为在prepareTestInstance方法中,已经初始化了mock相关bean和注入对应测试实例,所以在这里有个判断,是否有REINJECT_DEPENDENCIES_ATTRIBUTE属性,如果有重新初始化和重新注入,当前不走这个处理流程

public void beforeTestMethod(TestContext testContext) throws Exception {
//当前无此属性,不走重新初始化和注入流程
if (Boolean.TRUE.equals(
testContext.getAttribute(DependencyInjectionTestExecutionListener.REINJECT_DEPENDENCIES_ATTRIBUTE))) {
initMocks(testContext);
reinjectFields(testContext);
}
}
  • SpringBootDependencyInjectionTestExecutionListener

  因为在prepareTestInstance方法中,已经自动注入被Spring 相关注解@Resource 等修饰的bean,同样判断是否有有

REINJECT_DEPENDENCIES_ATTRIBUTE属性,当前不走这个处理流程

public void beforeTestMethod(TestContext testContext) throws Exception {
//当前无此属性,不重新注入
if (Boolean.TRUE.equals(testContext.getAttribute(REINJECT_DEPENDENCIES_ATTRIBUTE))) {
if (logger.isDebugEnabled()) {
logger.debug("Reinjecting dependencies for test context [" + testContext + "].");
}
injectDependencies(testContext);
}
}
  • ResetMocksTestExecutionListener

       重置被 MockReset标记的bean

public void beforeTestMethod(TestContext testContext) throws Exception {
if (MOCKITO_IS_PRESENT) {
//重置处理流程
resetMocks(testContext.getApplicationContext(), MockReset.BEFORE);
}
}

当前我们使用mock的bean为Server bean,MockReset属性为MockReset.AFTER,所以会在afterTestMethod方法中reset

public enum MockReset {
/**
* Reset the mock before the test method runs.
*/
   BEFORE,
/**
* Reset the mock after the test method runs.
*/
   AFTER,
/**
* Don't reset the mock.
*/
NONE;
......
}
.....
private void resetMocks(ConfigurableApplicationContext applicationContext, MockReset reset) {
ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();
//取得容器中beanDefinition名称
String[] names = beanFactory.getBeanDefinitionNames();
Set<String> instantiatedSingletons = new HashSet<>(Arrays.asList(beanFactory.getSingletonNames()));
for (String name : names) {
BeanDefinition definition = beanFactory.getBeanDefinition(name);
if (definition.isSingleton() && instantiatedSingletons.contains(name)) {
Object bean = beanFactory.getSingleton(name);
//获取bean的MockReset属性
if (reset.equals(MockReset.get(bean))) {
//重置,mockbean失效
Mockito.reset(bean);
}
}
}
try {
MockitoBeans mockedBeans = beanFactory.getBean(MockitoBeans.class);
for (Object mockedBean : mockedBeans) {
if (reset.equals(MockReset.get(mockedBean))) {
Mockito.reset(mockedBean);
}
}
}
catch (NoSuchBeanDefinitionException ex) {
// Continue
}
if (applicationContext.getParent() != null) {
//如果存在父容器,循环处理父容器中的mock bean
resetMocks(applicationContext.getParent(), reset);
}
}

(二)

   接下来执行invokeBeforeEachMethods方法,即执行我们的beforeEach方法init();

private void invokeBeforeEachMethods(JupiterEngineExecutionContext context) {
ExtensionRegistry registry = context.getExtensionRegistry();
invokeBeforeMethodsOrCallbacksUntilExceptionOccurs(BeforeEachMethodAdapter.class, context,
(adapter, extensionContext) -> {
try {
//执行adapter的invokeBeforeEachMethod方法
adapter.invokeBeforeEachMethod(extensionContext, registry);
}
catch (Throwable throwable) {
invokeBeforeEachExecutionExceptionHandlers(extensionContext, registry, throwable);
}
});
}
......
private <T extends Extension> void invokeBeforeMethodsOrCallbacksUntilExceptionOccurs(Class<T> type,
JupiterEngineExecutionContext context, CallbackInvoker<T> callbackInvoker) {


ExtensionRegistry registry = context.getExtensionRegistry();
ExtensionContext extensionContext = context.getExtensionContext();
ThrowableCollector throwableCollector = context.getThrowableCollector();

//获取type类型的extension,即BeforeEachMethodAdapter接口类型
for (T callback : registry.getExtensions(type)) {
//执行lambda 表达式callbackInvoker,回调
throwableCollector.execute(() -> callbackInvoker.invoke(callback, extensionContext));
if (throwableCollector.isNotEmpty()) {
break;
}
}
}

执行before or after method方法会调用ClassBasedTestDescriptor中对应注册的adapter方法

private BeforeEachMethodAdapter synthesizeBeforeEachMethodAdapter(Method method) {
return (extensionContext, registry) -> invokeMethodInExtensionContext(method, extensionContext, registry,
InvocationInterceptor::interceptBeforeEachMethod);
}


private AfterEachMethodAdapter synthesizeAfterEachMethodAdapter(Method method) {
return (extensionContext, registry) -> invokeMethodInExtensionContext(method, extensionContext, registry,
InvocationInterceptor::interceptAfterEachMethod);
}


private void invokeMethodInExtensionContext(Method method, ExtensionContext context, ExtensionRegistry registry,
VoidMethodInterceptorCall interceptorCall) {
TestInstances testInstances = context.getRequiredTestInstances();
Object target = testInstances.findInstance(method.getDeclaringClass()).orElseThrow(
() -> new JUnitException("Failed to find instance for method: " + method.toGenericString()));
//调用ExecutableInvoker 的invoke方法
executableInvoker.invoke(method, target, context, registry,
ReflectiveInterceptorCall.ofVoidMethod(interceptorCall));
}

ExecutableInvoker类中涉及调用拦截链,会查找InvocationInterceptor扩展依次执行;当前存在一个默认实现是TimoeoutExtension,其中interceptBeforeEachMethod方法中获取执行timeOut相关配置,如果方法上面有注解@Timeout,读取对应的值,否则获取默认配置,包括

"junit.jupiter.execution.timeout.beforeeach.method.default"

或者"junit.jupiter.execution.timeout.default"

配置对应的值,如果都没有,则超时时间为null;我们当前举的例子中,没有对应配置,所以为null

private <E extends Executable, T> T invoke(Invocation<T> originalInvocation,
ReflectiveInvocationContext<E> invocationContext, ExtensionContext extensionContext,
ExtensionRegistry extensionRegistry, ReflectiveInterceptorCall<E, T> call) {
//调用interceptorChain的invoke
return interceptorChain.invoke(originalInvocation, extensionRegistry, (interceptor,
wrappedInvocation) -> call.apply(interceptor, wrappedInvocation, invocationContext, extensionContext));
}


// class InvocationInterceptorChain 中的拦截链方法
public <T> T invoke(Invocation<T> invocation, ExtensionRegistry extensionRegistry, InterceptorCall<T> call) {
List<InvocationInterceptor> interceptors = extensionRegistry.getExtensions(InvocationInterceptor.class);
if (interceptors.isEmpty()) {
return proceed(invocation);
}
return chainAndInvoke(invocation, call, interceptors);
}

最终调用MethodInvocation类中的proceed方法执行before方法

public T proceed() {
//调用ReflectionUtils中的invokeMethod 执行方法
return (T) ReflectionUtils.invokeMethod(method, target.orElse(null), arguments);
}
......
//执行demo中的beforeEach方法
@BeforeEach
public void init(){
System.out.println("Init base test");
}

(三)

      接着执行invokeBeforeTestExecutionCallbacks方法,过滤帅选的extension type是 BeforeTestExecutionCallback.class,该接口的实现类还是我们熟悉的SpringExtension

private void invokeBeforeTestExecutionCallbacks(JupiterEngineExecutionContext context) {
invokeBeforeMethodsOrCallbacksUntilExceptionOccurs(BeforeTestExecutionCallback.class, context,
(callback, extensionContext) -> callback.beforeTestExecution(extensionContext));
}

我们看到SpringExtension中还是调用类TestContextManager的beforeTestExecution方法

getTestContextManager(context).beforeTestExecution(testInstance, testMethod);

在TestContextManager的beforeTestExecution方法中,和之前讲述的执行其他方法一样,依次遍历TestExecutionListener并执行对应方法

testExecutionListener.beforeTestExecution(getTestContext());

当前系统默认实现只有EventPublishingTestExecutionListener,用于发布BeforeTestExecutionEvent事件

(四)

       接下来到了执行我们的测试方法,执行invokeTestMethod

protected void invokeTestMethod(JupiterEngineExecutionContext context, DynamicTestExecutor dynamicTestExecutor) {
ExtensionContext extensionContext = context.getExtensionContext();
ThrowableCollector throwableCollector = context.getThrowableCollector();


throwableCollector.execute(() -> {
try {
Method testMethod = getTestMethod();
Object instance = extensionContext.getRequiredTestInstance();
//调用ExecutableInvoker 的invoke方法, 和执行beforeEach方法类似,这里就不在讲解
executableInvoker.invoke(testMethod, instance, extensionContext, context.getExtensionRegistry(),
interceptorCall);
}
catch (Throwable throwable) {
         //捕捉测试方法中的异常,比如断言
BlacklistedExceptions.rethrowIfBlacklisted(throwable);
//TestExecutionExceptionHandler扩展机制,可以实现自己的异常handle
//可以参考官方文档 https://junit.org/junit5/docs/current/user-guide/#extensions-exception-handling
invokeTestExecutionExceptionHandlers(context.getExtensionRegistry(), extensionContext, throwable);
}
});
}


其余的执行流程包括invokeAfterTestExecutionCallbacks,invokeAfterEachMethods和invokeAfterEachCallbacks,和对应before方法执行流程基本一致; 注意在invokeAfterEachCallbacks中,调用TestContextManager的afterTestMethod依次遍历

TestExecutionListener做一些后置处理工作,包括reset mockbean, 清理testContext属性等

(五)

    剩下的流程为清理相关资源,由于在executeRecursively方法中,依次调用子节点循环执行,清理资源相关方法会依次相反执行,由method->class->engine层次执行级别依次清理相关资源

1)  调用cleanUp方法关闭相应node context对象

private void cleanUp() {
throwableCollector.execute(() -> node.cleanUp(context));
}
//close context资源
public void cleanUp(JupiterEngineExecutionContext context) throws Exception {
context.close();
}

2)通知EngineExecutionListener执行结果

taskContext.getListener().executionFinished(testDescriptor, throwableCollector.toTestExecutionResult())

3)当node节点为调用SpringExtension通知TestExecutionListener执行afterTestClass方法

public void afterAll(ExtensionContext context) throws Exception {
try {
getTestContextManager(context).afterTestClass();
}
finally {
getStore(context).remove(context.getRequiredTestClass());
}
}


       至此,我们使用Junit5 从加载测试用例到执行测试用例流程基本流程分享完成;当然,其中很多细节,比如异常处理流程,执行链流程,Junit5强大的扩展机制等等,需要持续的深入学习,望大家读后有所收获。


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

评论