这里先粗滤的概括下运行单测时发生了什么。首先,@RunWith注解了该测试类,所以Junit框架会先用SpringRunnerTest.class作为参数创建SpringRunner的实例,然后调用SpringRunner的run方法运行测试,该方法中会启动Spring容器,加载@ContextConfiguration注解指定的Bean配置文件,同时也会处理@Autowired注解为SpringRunnerTest的实例注入myTestBean,最后运行test()测试用例。
简言之就是先通过SpringRunner启动Spring容器,然后运行测试方法。接下来探究一下SpringRunner启动Spring容器的过程。
public final class SpringRunner extends SpringJUnit4ClassRunner {
public SpringRunner(Class<?> clazz) throws InitializationError {
super(clazz);
}
}
public class SpringJUnit4ClassRunner extends BlockJUnit4ClassRunner {
...
}
SpringRunner和SpringJUnit4ClassRunner实际是等价的,可以认为SpringRunner是SpringJUnit4ClassRunner的一个别名,这里着重看下SpringJUnit4ClassRunner类的实现。
SpringJUnit4ClassRunner继承了BlockJUnit4ClassRunner,前面着重分析过BlockJUnit4ClassRunner,它运行的是一个标准的JUnit4测试模型,SpringJUnit4ClassRunner则是在此基础上做了一些扩展,扩展的内容主要包括: ·扩展了构造函数,多创建了一个TestContextManager实例。 · 扩展了createTest()方法,会额外调用TestContextManager的prepareTestInstance方法。 · 扩展了beforeClass,在执行@BeforeClass注解的方法前,会先调用TestContextManager的beforeTestClass方法。 · 扩展了before,在执行@Before注解的方法前,会先调用TestContextManager的beforeTestMethod方法。 · 扩展了afterClass,在执行@AfterClass注解的方法之后,会再调用TestContextManager的afterTestClass方法。 · 扩展了after,在执行@After注解的方法之后,会再调用TestContextManager的after方法。
TestContextManager是Spring测试框架的核心类,官方的解释是:TestContextManager is the main entry point into the Spring TestContext Framework. Specifically, a TestContextManager is responsible for managing a single TestContext.
TestContextManager管理着TestContext,而TestContext则是对ApplicationContext的一个再封装,可以把TestContext理解为增加了测试相关功能的Spring容器。 TestContextManager同时也管理着TestExecutionListeners,这里使用观察者模式提供了对测试运行过程中的关键节点(如beforeClass, afterClass等)的监听能力。
所以通过研究TestContextManager,TestContext和TestExecutionListeners的相关实现类的代码,就不难发现测试时Spring容器的启动秘密了。关键代码如下:
public class DefaultTestContext implements TestContext {
...
public ApplicationContext getApplicationContext() {
ApplicationContext context = this.cacheAwareContextLoaderDelegate.loadContext(this.mergedContextConfiguration);
if (context instanceof ConfigurableApplicationContext) {
@SuppressWarnings("resource")
ConfigurableApplicationContext cac = (ConfigurableApplicationContext) context;
Assert.state(cac.isActive(), () ->
"The ApplicationContext loaded for [" + this.mergedContextConfiguration +
"] is not active. This may be due to one of the following reasons: " +
"1) the context was closed programmatically by user code; " +
"2) the context was closed during parallel test execution either " +
"according to @DirtiesContext semantics or due to automatic eviction " +
"from the ContextCache due to a maximum cache size policy.");
}
return context;
}
...
}