NingG +

Spring 源码:Testing

0. 测试代码的重要作用

整理一下:

  1. 避免低级 bug:数据库字段缺失、数据库CRUD代码不完整、JSON 字段是否完整;
  2. 提升开发、调试效率:部署应用很耗时,避免因为小bug,反复打包部署;
  3. 工程重构、新人交接时,测试代码提供很大便利;

1. 开卷前的思考

几点:

Spring Testing 具体包括两个框架:

针对 Spring TestContext框架,具体:

思考:摆脱具体测试框架(JUnit4、TestNG),能够直接使用 Spring Test 吗?

Re:Spring Test 是一种抽象接口,需要底层具体的测试框架来承载,因此,只使用 Spring Test 无法正常工作。

2. 单元测试 & 集成测试

Unit Testing:单元测试,针对POJO上单个方法,进行测试,Spring中提供一系列的mock手段;

Integration Testing:集成测试,基于业务流程,进行测试;Spring Testing 在集成测试时,提供几点:

3. Spring TestContext 源码剖析

简单理一下,测试一个方法所需的基本条件:

3.1. Spring TestContext 框架概要

具体到Spring Test 框架,其核心是 org.springframework.test.context 包下:

Spring 提供了几个 TestExecutionListener 接口实现类,分别说明如下:

Spring 通过 AOP hook 了测试类的实例创建、beforeClass、before、after、afterClass 等事件入口,执行顺序主要如下:

JUnit 4 中可以通过 @RunWith 注解指定测试用例的运行器,Spring Test 框架提供了扩展于 org.junit.internal.runners.JUnit4ClassRunner 的 SpringJUnit4ClassRunner 运行器,它负责总装 Spring Test 测试框架并将其统一到 JUnit 4 框架中。

3.2. TestContext 提供的抽象测试用例类

Spring TestContext 为基于 JUnit 4.4 测试框架提供了两个抽象测试用例类,分别是 AbstractJUnit4SpringContextTests 和 AbstractTransactionalJUnit4SpringContextTests,而后者扩展于前者提供事务支持。

说明:

  1. 编写的测试用例类,可以不继承上述抽象类,同时也不用添加@TextExecutionListener注解?思考:这些Listener是如何生效的?
  2. AbstractJUnit4SpringContextTests 和 AbstractTransactionalJUnit4SpringContextTests,两个抽象基类,目标:获取applicationContext

(TODO)

4. 最佳实践

4.1. 在工程中配置 Spring Test + JUnit4

添加如下依赖即可:

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>${junitVersion}</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>${org.springframework-version}</version>
    <scope>test</scope>
</dependency>

4.2. 常用规范

几点:

(todo)

注:JUnit 4 中编写单元测试,注意事项:

4.3. 对接外部系统

场景:

Service 层代码,会调用外部系统,如何测试Service?Mockito 与 Spring AOP 之间兼容有问题。

5. Spring 工程,搭建测试环境

Spring工程,包含普通Spring工程、Spring Web工程,测试方案有哪些?如何搭建?这才是本文的重点。

5.1. 测试方案调研

现有Spring工程采用的测试方案:

特别说明:

Mockito 跟 Spring 结合时,存在潜在问题:Spring IoC容器管理的beanA,使用Mockito来mock beanA 内部beanB时,如果beanA实际是代理类,则mock的BeanB,只存在于beanA代理类中,并不存在于beanA的业务类里;实际上 Spring AOP机制,产生beanA代理类之后,具体执行单个方法时,先到达beanA代理类,然后转发beanA业务类。

更多细节,参考:

解决办法:

ReflectionTestUtils.setField(unwrapProxy(magicCardLocalService), "magicCardService", magicCardService);
...
public static Object unwrapProxy(Object bean) {
if (AopUtils.isAopProxy(bean) && bean instanceof Advised) {
        Advised advised = (Advised) bean;
try {
            bean = advised.getTargetSource().getTarget();
        } catch (Exception e) {
            System.out.println("Exception unwrapping proxy object" + e);
        }
    }
return bean;
}

补充:官方可能会提供新的解决办法,https://github.com/mockito/mockito/pull/277/files

5.2. 初步选定测试方案

几个典型场景:

初步选定:

5.3. 搭建测试环境

5.3.1. 添加依赖

(todo)

5.3.2. 实例代码

详细阅读:

6. 参考来源

Top