单元测试的主要框架是 JUnit , 在JUnit 运行一个单元测试时,会寻找 @RunWith注解指定的 Runner类,如果没有指定@RunWith则默认会以 JUnit4来运行
以下是 @RunWith的定义
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface RunWith {
/**
* @return a Runner class (must have a constructor that takes a single Class to run)
*/
Class<? extends Runner> value();
}
在Junit4 框架里,每一个单元测试类都会创建一个 `Runner`进行对应,默认是 `Junit4`
public abstract class Runner implements Describable {
/*
* (non-Javadoc)
* @see org.junit.runner.Describable#getDescription()
*/
public abstract Description getDescription();
/**
* 调用Runner的run方法进行单元测试的执行
* 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);
/**
* @return the number of tests to be run by the receiver
*/
public int testCount() {
return getDescription().testCount();
}
}
// 主要找到run的方法实现
public final class JUnit4 extends BlockJUnit4ClassRunner {
/**
* Constructs a new instance of the default runner
*/
public JUnit4(Class<?> klass) throws InitializationError {
super(klass);
}
}
从代码继承关系里可以找到,是在 ParentRunner里实现
public abstract class ParentRunner<T> extends BlockJUnit4ClassRunner {
@Override
public void run(final RunNotifier notifier) {
EachTestNotifier testNotifier = new EachTestNotifier(notifier,
getDescription());
try {
// Statement 代表了运行时的一个或多个动作,如 BeforeClass,AfterClass,Before,After等
Statement statement = classBlock(notifier);
// 依次调用对应的方法执行
statement.evaluate();
} catch (AssumptionViolatedException e) {
testNotifier.addFailedAssumption(e);
} catch (StoppedByUserException e) {
throw e;
} catch (Throwable e) {
testNotifier.addFailure(e);
}
}
// 构建Class级别的动作,如BeforeClass和AfterClass
protected Statement classBlock(final RunNotifier notifier) {
// 因为statement是一个链表,先构建一个包含方法级的statement
// 所有标注了 @Test的方法
Statement statement = childrenInvoker(notifier);
if (!areAllChildrenIgnored()) {
// 在外层加一个类级别的BeforeClass,需要是 static方法
statement = withBeforeClasses(statement);
// 在最后加一个AfterClass
statement = withAfterClasses(statement);
statement = withClassRules(statement);
}
return statement;
}
protected Statement childrenInvoker(final RunNotifier notifier) {
return new Statement() {
@Override
public void evaluate() {
runChildren(notifier);
}
};
}
private void runChildren(final RunNotifier notifier) {
final RunnerScheduler currentScheduler = scheduler;
try {
// 获取所有有标识@Test注解的方法
for (final T each : getFilteredChildren()) {
currentScheduler.schedule(new Runnable() {
public void run() {
// 执行每个单元测试,在后面的 BlockJUnit4ClassRunner分析
ParentRunner.this.runChild(each, notifier);
}
});
}
} finally {
currentScheduler.finished();
}
}
private Collection<T> getFilteredChildren() {
if (filteredChildren == null) {
synchronized (childrenLock) {
if (filteredChildren == null) {
// 暂存所有的方法,由子类BlockJUnit4ClassRunner实现
filteredChildren = Collections.unmodifiableCollection(getChildren());
}
}
}
return filteredChildren;
}
}
public class BlockJUnit4ClassRunner extends ParentRunner<FrameworkMethod> {
@Override
protected List<FrameworkMethod> getChildren() {
return computeTestMethods();
}
// 获取所有@Test注解的方法
protected List<FrameworkMethod> computeTestMethods() {
return getTestClass().getAnnotatedMethods(Test.class);
}
@Override
protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
Description description = describeChild(method);
// 如果方法标注了 @Ignore,则不会执行
if (isIgnored(method)) {
notifier.fireTestIgnored(description);
} else {
// 执行每个方法
// 这里会
runLeaf(methodBlock(method), description, notifier);
}
}
protected Statement methodBlock(FrameworkMethod method) {
Object test;
// 1. 创建当前类的实例
try {
test = new ReflectiveCallable() {
@Override
protected Object runReflectiveCall() throws Throwable {
return createTest();
}
}.run();
} catch (Throwable e) {
return new Fail(e);
}
// 创建方法的Statement
Statement statement = methodInvoker(method, test);
statement = possiblyExpectingExceptions(method, test, statement);
//封装一个设置了超时线程,以@Test上设置的timeout值
statement = withPotentialTimeout(method, test, statement);
// 封装一层方法级 @Before的方法
statement = withBefores(method, test, statement);
// 封装一层方法级 @After的方法
statement = withAfters(method, test, statement);
statement = withRules(method, test, statement);
return statement;
}
}
以上是 JUnit4框架的对应各个时机点的流程方式
Spring框架的单元测试同样也是基于 BlockJUnit4ClassRunner,但因为要处理 Spring的Context,需要进行初始化,是如何处理的呢?
入口为 SpringRunner
public final class SpringRunner extends SpringJUnit4ClassRunner {
/**
* Construct a new {@code SpringRunner} and initialize a
* {@link org.springframework.test.context.TestContextManager TestContextManager}
* to provide Spring testing functionality to standard JUnit 4 tests.
* @param clazz the test class to be run
* @see #createTestContextManager(Class)
*/
public SpringRunner(Class<?> clazz) throws InitializationError {
super(clazz);
}
}
public SpringJUnit4ClassRunner(Class<?> clazz) throws InitializationError {
super(clazz);
if (logger.isDebugEnabled()) {
logger.debug("SpringJUnit4ClassRunner constructor called with [" + clazz + "]");
}
ensureSpringRulesAreNotPresent(clazz);
// 初始化Spring的TestContextManager
this.testContextManager = createTestContextManager(clazz);
}
/**
* Create a new {@link TestContextManager} for the supplied test class.
* <p>Can be overridden by subclasses.
* @param clazz the test class to be managed
*/
protected TestContextManager createTestContextManager(Class<?> clazz) {
return new TestContextManager(clazz);
}
public TestContextManager(Class<?> testClass) {
// 1. 创建ClassLoader DefaultCacheAwareContextLoaderDelegate,该类调用的是无参构造函数,默认会初始会一个静态的DefaultContextCache
// 进行缓存下spring ApplicationContext,这里是 Spring Test framework ApplicationContext 缓存的逻辑,即相同的配置下,ApplicationContext是相同的
// 2. 创建 DefaultBootstrapContext -> 加载springContext
// 3. 创建 TestContextBootstrapper -> WebTestContextBootstrapper
this(BootstrapUtils.resolveTestContextBootstrapper(BootstrapUtils.createBootstrapContext(testClass)));
}
/**
* Construct a new {@code TestContextManager} using the supplied {@link TestContextBootstrapper}
* and {@linkplain #registerTestExecutionListeners register} the necessary
* {@link TestExecutionListener TestExecutionListeners}.
* <p>Delegates to the supplied {@code TestContextBootstrapper} for building
* the {@code TestContext} and retrieving the {@code TestExecutionListeners}.
* @param testContextBootstrapper the bootstrapper to use
* @see TestContextBootstrapper#buildTestContext
* @see TestContextBootstrapper#getTestExecutionListeners
* @see #registerTestExecutionListeners
*/
public TestContextManager(TestContextBootstrapper testContextBootstrapper) {
// 这里默认的是 DefaultTestContext
this.testContext = testContextBootstrapper.buildTestContext();
// 注册所有的 TestExecutionListener
// 如果类上有标注 @TestExecutionListeners ,则为类上指定的为主
// 如果没有标注,则会获取 spring.factories 文件定义的所有类进行排序
// 而这Listerner 是Spring的context 初始化的时机点
registerTestExecutionListeners(testContextBootstrapper.getTestExecutionListeners());
}
/**
* Get the {@link TestContext} managed by this {@code TestContextManager}.
*/
public final TestContext getTestContext() {
return this.testContextHolder.get();
}
/**
* Register the supplied list of {@link TestExecutionListener TestExecutionListeners}
* by appending them to the list of listeners used by this {@code TestContextManager}.
* @see #registerTestExecutionListeners(TestExecutionListener...)
*/
public void registerTestExecutionListeners(List<TestExecutionListener> testExecutionListeners) {
registerTestExecutionListeners(testExecutionListeners.toArray(new TestExecutionListener[0]));
}
/**
* Register the supplied array of {@link TestExecutionListener TestExecutionListeners}
* by appending them to the list of listeners used by this {@code TestContextManager}.
*/
public void registerTestExecutionListeners(TestExecutionListener... testExecutionListeners) {
for (TestExecutionListener listener : testExecutionListeners) {
if (logger.isTraceEnabled()) {
logger.trace("Registering TestExecutionListener: " + listener);
}
this.testExecutionListeners.add(listener);
}
}
上面提到 Spring Context是在 TestExecutionListener中进行初始化,具体是怎么做的呢?
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
以上的几个是针对不同场景的处理方式,分析下 DependencyInjectionTestExecutionListener
public class DependencyInjectionTestExecutionListener extends AbstractTestExecutionListener {
/**
* Attribute name for a {@link TestContext} attribute which indicates
* whether or not the dependencies of a test instance should be
* <em>reinjected</em> in
* {@link #beforeTestMethod(TestContext) beforeTestMethod()}. Note that
* dependencies will be injected in
* {@link #prepareTestInstance(TestContext) prepareTestInstance()} in any
* case.
* <p>Clients of a {@link TestContext} (e.g., other
* {@link org.springframework.test.context.TestExecutionListener TestExecutionListeners})
* may therefore choose to set this attribute to signal that dependencies
* should be reinjected <em>between</em> execution of individual test
* methods.
* <p>Permissible values include {@link Boolean#TRUE} and {@link Boolean#FALSE}.
*/
public static final String REINJECT_DEPENDENCIES_ATTRIBUTE = Conventions.getQualifiedAttributeName(
DependencyInjectionTestExecutionListener.class, "reinjectDependencies");
private static final Log logger = LogFactory.getLog(DependencyInjectionTestExecutionListener.class);
/**
* Returns {@code 2000}.
*/
@Override
public final int getOrder() {
return 2000;
}
/**
* Performs dependency injection on the
* {@link TestContext#getTestInstance() test instance} of the supplied
* {@link TestContext test context} by
* {@link AutowireCapableBeanFactory#autowireBeanProperties(Object, int, boolean) autowiring}
* and
* {@link AutowireCapableBeanFactory#initializeBean(Object, String) initializing}
* the test instance via its own
* {@link TestContext#getApplicationContext() application context} (without
* checking dependencies).
* <p>The {@link #REINJECT_DEPENDENCIES_ATTRIBUTE} will be subsequently removed
* from the test context, regardless of its value.
*/
@Override
public void prepareTestInstance(TestContext testContext) throws Exception {
if (logger.isDebugEnabled()) {
logger.debug("Performing dependency injection for test context [" + testContext + "].");
}
injectDependencies(testContext);
}
/**
* If the {@link #REINJECT_DEPENDENCIES_ATTRIBUTE} in the supplied
* {@link TestContext test context} has a value of {@link Boolean#TRUE},
* this method will have the same effect as
* {@link #prepareTestInstance(TestContext) prepareTestInstance()};
* otherwise, this method will have no effect.
*/
@Override
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);
}
}
// 当前的类进行注册到Spring Bean中,并进行初始化
protected void injectDependencies(TestContext testContext) throws Exception {
Object bean = testContext.getTestInstance();
// 会进行Spring的容器初始化
AutowireCapableBeanFactory beanFactory = testContext.getApplicationContext().getAutowireCapableBeanFactory();
beanFactory.autowireBeanProperties(bean, AutowireCapableBeanFactory.AUTOWIRE_NO, false);
beanFactory.initializeBean(bean, testContext.getTestClass().getName());
testContext.removeAttribute(REINJECT_DEPENDENCIES_ATTRIBUTE);
}
}
那这些 TestExecutionListener 是在哪里被调用呢?
在SpringJUnit4ClassRunner中有重写 BlockJUnit4ClassRunner的内容
//创建好对象后
@Override
protected Object createTest() throws Exception {
Object testInstance = super.createTest();
// 调用 TestExecutionListener.prepareTestInstance
getTestContextManager().prepareTestInstance(testInstance);
return testInstance;
}
/**
* Perform the same logic as
* {@link BlockJUnit4ClassRunner#runChild(FrameworkMethod, RunNotifier)},
* except that tests are determined to be <em>ignored</em> by
* {@link #isTestMethodIgnored(FrameworkMethod)}.
*/
@Override
protected void runChild(FrameworkMethod frameworkMethod, RunNotifier notifier) {
Description description = describeChild(frameworkMethod);
if (isTestMethodIgnored(frameworkMethod)) {
notifier.fireTestIgnored(description);
}
else {
Statement statement;
try {
statement = methodBlock(frameworkMethod);
}
catch (Throwable ex) {
statement = new Fail(ex);
}
runLeaf(statement, description, notifier);
}
}
// 重写了基类的 methodBlock
// 1. 创建对象,调用 prepareTestInstance方法
// 2. 调用
@Override
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);
// 调用 listener的 beforeTestExecution
statement = withBeforeTestExecutionCallbacks(frameworkMethod, testInstance, statement);
// listener的 beforeTestExecution注册
statement = withAfterTestExecutionCallbacks(frameworkMethod, testInstance, statement);
// 注册异常的处理方式
statement = possiblyExpectingExceptions(frameworkMethod, testInstance, statement);
// 方法 beforeTestMethod, @Before的处理方式
statement = withBefores(frameworkMethod, testInstance, statement);
// 方法 afterTestMethod, @After的处理方式
statement = withAfters(frameworkMethod, testInstance, statement);
statement = withRulesReflectively(frameworkMethod, testInstance, statement);
statement = withPotentialRepeat(frameworkMethod, testInstance, statement);
statement = withPotentialTimeout(frameworkMethod, testInstance, statement);
return statement;
}
// 注册 listener的 beforeTestClass
@Override
protected Statement withBeforeClasses(Statement statement) {
Statement junitBeforeClasses = super.withBeforeClasses(statement);
return new RunBeforeTestClassCallbacks(junitBeforeClasses, getTestContextManager());
}
// 注册 listener的 afterTestClass
@Override
protected Statement withAfterClasses(Statement statement) {
Statement junitAfterClasses = super.withAfterClasses(statement);
return new RunAfterTestClassCallbacks(junitAfterClasses, getTestContextManager());
}
在JUnit4的基础上,调用对应的Listener,同时进行Spring context的初始化包括Bean的创建 注意下DefaultTestContext ,这个类是和testClass 具体的类关联的,在加载Spring ApplicationContext的逻辑如下
public ApplicationContext getApplicationContext() {
// 从cacheAwareContextLoaderDelegate中加载数据
// 从上面知道是 DefaultCacheAwareContextLoaderDelegate
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 [" + 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;
}
如下:
@Override
public ApplicationContext loadContext(MergedContextConfiguration mergedContextConfiguration) {
synchronized (this.contextCache) {
// this.contextCache 默认是一个静态的数据
// 从这里可以看到,缓存的key是MergedContextConfiguration
ApplicationContext context = this.contextCache.get(mergedContextConfiguration);
if (context == null) {
try {
context = loadContextInternal(mergedContextConfiguration);
if (logger.isDebugEnabled()) {
logger.debug(String.format("Storing ApplicationContext in cache under key [%s]",
mergedContextConfiguration));
}
this.contextCache.put(mergedContextConfiguration, context);
}
catch (Exception ex) {
throw new IllegalStateException("Failed to load ApplicationContext", ex);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Retrieved ApplicationContext from cache with key [%s]",
mergedContextConfiguration));
}
}
this.contextCache.logStatistics();
return context;
}
}
从上面分析可知,如果要使用同一个Spring ApplicationContext,需要 MergedContextConfiguration对应的数据也是相同的,如果在运行中,使用的 ApplicationContext 非同一个,则需要考虑MergedContextConfiguration是否相同