spring-test测试集成

前言

单元测试是每个程序不个获缺的部份。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官方文档

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值