问题背景

事情起因是这样的,最近在公司里面做一个很大型的微服务(巨服务)的业务,业务复杂的服务难免涉及到给一张表增加几个字段之类的,这个时候就要同时去改 dao 层的实体类和 Mapper,而公司的 Mapper.xml 又是代码平台自动生成的模板,涉及到各种各样的增删改查方法,牵一发而动全身,每次加几个字段都得到处修修改改,头晕目眩。

改完 mapper.xml 之后也不确定有没有漏掉什么逗号分好之类的,找 AI 帮忙找 bug 也不一定百分百正确,还是得单测一次才能踏实放心,但是这个 Spring 项目真的太大了,如果采用 Spring 默认的测试方案,本地完全无法启动,并且还会有各种注册中心的配置中心的服务极度拖慢启动时间,如果云端单测,不仅编译发包部署容器调度十分缓慢,Mapper 无法直接接口调用调试,得配置复杂的单测流水线才能完成,更是折磨。

痛定思痛之后,到处搜集了一些资料,最后简化了这样一个大型复杂 Spring 项目快速对 Mapper 进行测试的方案。

方案实现

这个方案的核心是利用 AnnotationConfigApplicationContext 指定加载特定的 Bean,避免启动整个 Spring 项目启动带来的大量时间空间消耗。这个时候 Spring 的 ICO 容器里面就只有我们需要的特定的 Bean,其他的 Bean 和服务都不会启动(一些 Spring 启动时跟着启动的特殊的中间件除外,比如日志系统),从而节省大量时间和空间。

配置 Configuration

import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

@Configuration
@MapperScan(basePackageClasses = TestMapper.class)
public class DenoTestMapperConfig {

    @Bean(name = "myTestDatasource")
    @Qualifier("myTestDatasource")
    public DataSource dataSource(){
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://xx.xx.xx.xx/table?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8");
        dataSource.setUsername("username");
        dataSource.setPassword("*********");
        return dataSource;
    }

    @Bean(name = "myTestSqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("myTestDatasource") @Lazy DataSource dataSource) throws Exception{ // @Lazy 避免循环依赖
        String path = " path to TestMapper.xml";
        Resource resource = new PathMatchingResourcePatternResolver().getResource(path);
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new Resource[]{resource});
        return bean.getObject();
    }
}

测试类

import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MapperTest {
  
    private static final Logger logger = LogManager.getLogger(MapperTest.class); // 这个建议加上,提前初始化日志系统加快启动速度

    private TestMapper mapper;

    @Before
    public void doBefore(){
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(DenoTestMapperConfig.class);
        mapper = ac.getBean(TestMapper.class);
    }

    @Test
    public void simpleTest(){
       // TEST
    }

}

方案效果

一个私有云编译构造部署需要 20 分钟的 Spring 服务,采取本方案,测试只需要 2sec

题外话:

后续会写一篇如何更好地对 Spring 单测的博客