前言
单元测试是每个程序不个获缺的部份。spring 也基于《Junit概述》进行扩展,更提供了mock集成。
快速构建
以下是快速基于spring-test开发的容器demo(缺少部分代码)
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="admin" class="entity.AdminUser">
<property name="username" value="eeeerrrr"></property>
</bean>
</beans>
@RunWith(value = SpringRunner.class)
@ContextConfiguration("classpath:spring-test.xml")
public class SpringTest {
@Autowired
private AdminUser adminUser;
@Test
public void testAdminUser(){
System.out.println(adminUser.getUsername());
}
}
public class TestMain {
public static void main(String[] args) {
//根据junit一文件介绍
// 1. 根据annotatedBuilder解析 @RunWith 实例化SpringTest. Runner
// 2. 调用SpringTest.run触发用例的运行
Result result = JUnitCore.runClasses(SpringTest.class);
for (Failure failure : result.getFailures()) { // 对于执行失败的情况打印失败信息
System.out.println(failure.toString());
}
}
}
核心类介绍
主要介绍以下类
DefaultTestContext
test上下文,用来管理spring容器和提供一些工具类
AttributeAccessor
提供容器的全局对家存储方式
public interface AttributeAccessor {
// test上下文Map中存储数据
void setAttribute(String name, @Nullable Object value);
// test上下文Map中取数据
Object getAttribute(String name);
}
TestContext
提交容器管理及运行时的一些状态管理接口
public interface TestContext {
// 获取spring容器
ApplicationContext getApplicationContext();
// 获取测试类,例SpringTest.class
Class<?> getTestClass();
// 获取当前测试类的实例
Object getTestInstance();
// 获取当前测试类运行的方法
Method getTestMethod();
// 获取当前运行的异常
Throwable getTestException();
// 根据当前运行的状态
// @see getTestInstance
// @see getTestMethod
// @see getTestException
void updateState( Object testInstance, Method testMethod, Throwable testException);
// 根据容器为可删除状态,可是理解为删除容器
// HierarchyMode @see DefaultTestContext
void markApplicationContextDirty(HierarchyMode hierarchyMode);
}
DefaultTestContext
对上面两接口的具体实现
public class DefaultTestContext {
// key-value的上下文对象存储入口
private final Map<String, Object> attributes = new ConcurrentHashMap<>(4);
// spring容器的管理接口,相当于提供 配置-容器的Map
private final CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate;
// 当前上下文的配置对象。
// 有父子配置对概念,配和上cacheAwareContextLoaderDelegate,形成相关联的容器。
// @see AbstractTestContextBootstrapper.buildMergedContextConfiguration
private final MergedContextConfiguration mergedContextConfiguration;
private final Class<?> testClass;
// 运行时三个状态
private volatile Object testInstance;
private volatile Method testMethod;
private volatile Throwable testException;
// 根据当前配置mergedContextConfiguration通过cacheAwareContextLoaderDelegate获取容器
public ApplicationContext getApplicationContext();
// 根据当前配置mergedContextConfiguration删除容器
// 如果hierarchyMode为EXHAUSTIVE,则把mergedContextConfiguration父亲配置对应的容器全删除了
// 默认只删除当前的
public void markApplicationContextDirty(HierarchyMode hierarchyMode)
}
TestContextManager
上下文管理类
public class TestContextManager {
// 测试类的上下文
private final TestContext testContext;
// 本来线程,用本地线程复制testContext,这样子线程中修改的类只影响到经程本生
// 例如:testInstance, testMethod
private final ThreadLocal<TestContext> testContextHolder = ThreadLocal.withInitial(
new Supplier<TestContext>() {
@Override
public TestContext get() {
return copyTestContext(TestContextManager.this.testContext);
}
});
// 监听类,针对测试用用命的各环节
// 主要有 class - Instance - Method - TestExecution
private final List<TestExecutionListener> testExecutionListeners = new ArrayList<>();
}
初始化流程
SpringRunner的class构造器为切入点
public SpringJUnit4ClassRunner(Class<?> clazz) throws InitializationError {
super(clazz);
// 创建 TestContextManager类
this.testContextManager = createTestContextManager(clazz);
}
public TestContextManager(Class<?> testClass) {
// 1.创建DefaultCacheAwareContextLoaderDelegate
// 2.创建DefaultBootstrapContext
// 3.根据WebAppConfiguration注册判断选用DefaultTestContextBootstrapper 还是 WebTestContextBootstrappermergedContextConfiguration
this(BootstrapUtils.resolveTestContextBootstrapper(BootstrapUtils.createBootstrapContext(testClass)));
}
public TestContextManager(TestContextBootstrapper testContextBootstrapper) {
// 创建DefaultTestContext
// 1. 通过getBootstrapContext().getTestClass()获取testClass
// 2. 通过@ContextConfiguration和ContextHierarchy创建mergedContextConfiguration
// 3. 通过getBootstrapContext().getCacheAwareContextLoaderDelegate()创建cacheAwareContextLoaderDelegate
this.testContext = testContextBootstrapper.buildTestContext();
// 注册监听器
// 1. 有@TestExecutionListeners
// 2. 默认为加载 META-INF/spring.factories
registerTestExecutionListeners(testContextBootstrapper.getTestExecutionListeners());
}
调用流程
调用SpringTest.run触发用例的运行
public void run(RunNotifier notifier) {
// 1. 使用IfProfileValue判断ProfileValueSourceConfiguration是否包含所需要的属性
// 2. ProfileValueSourceConfiguration默认为SystemProfileValueSource, 对应System.getProperty
if (!ProfileValueUtils.isTestEnabledInThisEnvironment(getTestClass().getJavaClass())) {
notifier.fireTestIgnored(getDescription());
return;
}
// 通过junit机 Statement制,触发各种listener,spring.factories中声明的如下
// DirtiesContextBeforeModesTestExecutionListener,DirtiesContextTestExecutionListener 负责方法和类等级容器清理
// DependencyInjectionTestExecutionListener 运行类注入依赖
// TransactionalTestExecutionListener 负责事物
// SqlScriptsTestExecutionListener sql脚本的执行
// ServletTestExecutionListener web相关注入
// @see TestExecutionListener
// @see XXXTestClassCallback Statement的扩展者
super.run(notifier);
}
主要参考
《spring @ProfileValueSource + @IfProfileValue 组合使用》 《spring-test官方文档》