spring junit源码简要分析

685 阅读3分钟

idea中junit运行的入口

com.intellij.junit4.JUnit4IdeaTestRunner

单个类运行时,会首先调用

org.junit.runners.ParentRunner#getChildren()

该方法生成每个@Test注解标注的TestCase

junit 关键的抽象

RunnerBuilder 为被测试的类构建runner,默认的runner有2种,默认JUnit4Builder,AnnotatedBuilder即我们在测试用例中@Runwith注解标注的runner

RunnerBuilder {
    /**
     * Override to calculate the correct runner for a test class at runtime.
     *
     * @param testClass class to be run
     * @return a Runner
     * @throws Throwable if a runner cannot be constructed
     */
    public abstract Runner runnerForClass(Class<?> testClass) throws Throwable;
}

Runner负责运行我们的测试类,常见的Runner有
BlockJUnit4ClassRunner-junit默认的测试运行runner SpringJUnit4ClassRunner,-带spring容器启动的runner
MockitoJUnitRunner-与mockito集成的runner

/**
* 自定义的runner必须提供一个带class类型参数的构造函数,
*/
public abstract class Runner implements Describable {

    /**
     * Run the tests for this runner.
     *
     * @param notifier will be notified of events while tests are being run--tests being
     * started, finishing, and failing
     */
    public abstract void run(RunNotifier notifier);
}

public abstract class ParentRunner<T> extends Runner implements Filterable,
        Sortable {


        }

// 执行每个@Test注解的方法
// 该方法在SpringJUnit4ClassRunner中被重写,增加了前置和后置的处理类
// junit为每个@Test方法执行封装成Statement语句(装饰模式)
protected abstract void runChild(T child, RunNotifier notifier);

junit 运行testCase的流程

  • 为每个@TestCase注解的方法生成TestClass对象
  • 调用@Before注解方法
  • 调用@Test注解方法
  • 调用@After注解方法

spring与junit结合点

SpringJUnit4ClassRunner在创建时,会持有testContextManager,testContextManager会持有一组 List testExecutionListeners。

SpringJUnit4ClassRunner通过在每个@Test注解标志的测试用例执行语句增加前置处理的statement, 增加钩子方法,实现对spring容器的初始化,同时springJUnit4ClassRunner还重载了createTest()方法,增加prepareTestInstance()钩子函数调用实现spring容器初始化

protected Statement methodBlock(FrameworkMethod frameworkMethod) {
		Object testInstance;
		try {
			testInstance = new ReflectiveCallable() {
				@Override
				protected Object runReflectiveCall() throws Throwable {
					return createTest();
				}
			}.run();
		}
		catch (Throwable ex) {
			return new Fail(ex);
		}

		Statement statement = methodInvoker(frameworkMethod, testInstance);
		statement = withBeforeTestExecutionCallbacks(frameworkMethod, testInstance, statement);
		statement = withAfterTestExecutionCallbacks(frameworkMethod, testInstance, statement);
		statement = possiblyExpectingExceptions(frameworkMethod, testInstance, statement);
		statement = withBefores(frameworkMethod, testInstance, statement);
		statement = withAfters(frameworkMethod, testInstance, statement);
		statement = withRulesReflectively(frameworkMethod, testInstance, statement);
		statement = withPotentialRepeat(frameworkMethod, testInstance, statement);
		statement = withPotentialTimeout(frameworkMethod, testInstance, statement);
		return statement;
	}
  1. RunBeforeTestClassCallbacks - 在类级别的执行

  2. RunBeforeTestExecutionCallbacks - beforeTestExecution 在test执行前执行

  3. RunBeforeTestMethodCallbacks - beforeTestMethod 在方法执行之前执行

  4. prepareTestInstance - 创建测试用例的类实例时执行

  • 创建类实例时,重载如下方法

    org.junit.runners.BlockJUnit4ClassRunner#createTest
    
    org.springframework.test.context.junit4.SpringJUnit4ClassRunner#createTest
    

    此时Spring会循环调用TestExecutionListener.prepareTestInstance(TestContext context)

    org.springframework.test.context.TestExecutionListener#prepareTestInstance 
    

    TestExecutionListener由 TestContextManager管理和初始化

    spring默认的TestExecutionListener

    ServletTestExecutionListener
    DirtiesContextBeforeModesTestExecutionListener
    // 执行核心的依赖准入
    DependencyInjectionTestExecutionListener 
    DirtiesContextTestExecutionListener
    TransactionalTestExecutionListener
    SqlScriptsTestExecutionListener
    

    spring boot 新增如下TestExecutionListener

    // mockiot注入
    MockitoTestExecutionListener
    ResetMocksTestExecutionListener
    

TestExecutionListener加载方式

  • 放置在META-INF目录下spring.factories方式加载 参考spring-test配置
org.springframework.test.context.TestExecutionListener = \
	org.springframework.test.context.web.ServletTestExecutionListener,\
	org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener,\
	org.springframework.test.context.support.DependencyInjectionTestExecutionListener,\
	org.springframework.test.context.support.DirtiesContextTestExecutionListener,\
	org.springframework.test.context.transaction.TransactionalTestExecutionListener,\
	org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener
  • 在测试类中配置,这样会覆盖默认加载方式,即上面提到的方式

junit中spring容器启动方式

junit在为每个@Test方法执行前创建被测对象的类实例,此时Spring容器会通过TestExecutionListener.prepareTestInstance() 完成spring容器的创建

同时spring为性能考虑针对同一个类的测试用例会缓存spring容器,仅创建一次

spring-test启动上下文自定义customizeContext

spring-test通过MergedContextConfiguration中的自定义指定的ContextCustomizer实现spring容器的自定义 ContextCustomizer通过META-INFO/spring.factoring 加载

spring 默认定义的自定义

org.springframework.test.context.ContextCustomizerFactory = \
	org.springframework.test.context.web.socket.MockServerContainerContextCustomizerFactory

spring boot 扩充定义

org.springframework.test.context.ContextCustomizerFactory=\
org.springframework.boot.test.context.ImportsContextCustomizerFactory,\
org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizerFactory,\
org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory,\
org.springframework.boot.test.mock.mockito.MockitoContextCustomizerFactory,\
org.springframework.boot.test.web.client.TestRestTemplateContextCustomizerFactory,\
org.springframework.boot.test.web.reactive.server.WebTestClientContextCustomizerFactory 

其中MockitoContextCustomizerFactory 实现MockitoPostProcessor后者处理器向spring容器注入,该后置处理器向spring容器注入所有配置的mock bean, 通过此后者处理器创建的spring bean均由该类代理创建实例(ObjectFactory)