小架构step系列15:白盒集成测试

1 概述

白盒集成测试是spring-test提供的一种不需要启动Web Server就可以触发Controller里的http方法执行的一种方式,由于是从Controller就开始测试,相当于把对应方法后面的一整个流程都进行了测试,这个流程里面可能包含数据库的操作,可能包含文件操作,可能包含中间操作等,如果这些都需要进行配套,比如准备好文件存储的OSS、准备好类似kafka等消息中间件,那这个测试会相当难做,即使能够做也很难多做,成本非常高。本文就列一下这些场景的一种做法,仅供参考。

2 常用方式

2.1 数据库访问

2.1.1 内置数据库

开发一个业务系统,几乎都是需要数据库的,所以代码需要访问数据库,设计到这个场景的测试方法有两种。

一种是使用Spring提供的内置数据库,如HSQL、H2、Derby等,参考官方文档:https://docs.spring.io/spring-framework/docs/5.3.39/reference/html/data-access.html#jdbc-embedded-database-support

java 复制代码
public class DataAccessIntegrationTestTemplate {
    private EmbeddedDatabase db;

    @BeforeEach
    public void setUp() {
        // creates an HSQL in-memory database populated from default scripts
        // classpath:schema.sql and classpath:data.sql
        db = new EmbeddedDatabaseBuilder()
                .generateUniqueName(true)
                .addDefaultScripts()
                .build();
    }

    @Test
    public void testDataAccess() {
        JdbcTemplate template = new JdbcTemplate(db);
        template.query( /* ... */ );
    }

    @AfterEach
    public void tearDown() {
        db.shutdown();
    }
}

此方式的好处是不需要安装数据库,也不需要清理数据,就像每个测试用例都用一个新的数据库一样。

2.1.2 外部数据库

第二种就是使用外部数据库,此时测试用例用上事务注解@Transactional,该注解由spring-tx包提供,需要引入spring-tx包的依赖:

XML 复制代码
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
</dependency>

@SpringBootTest
@AutoConfigureMockMvc
@Transactional
public class HelloControllerTest {
}

从之前的原理来看,执行测试用例就相当于一个普通的方法调用,加上这个注解之后,相当于整个测试用例的执行都是在一个事务中执行,测试用例执行完成之后,测试框架会自动回滚,就相当于没有真正把数据写到数据库里,这样就不需要清理数据。

注意:此方式需要准备一个外部的数据库,并正常配置数据库连接参数。好处是真正能够测试到实际使用的数据库。

2.2 文件访问

当需要读取或者写出文件时,JDK提供了临时文件这种方式可以用到测试当中:

java 复制代码
File tempFile = File.createTempFile("fileName", ".tmp");

在测试用例中生成一个临时文件,然后传到Service中代替业务实际使用的文件。

注意:要设计好如何把临时文件传到Service中,这可以倒逼把代码写得可测。

2.3 其它

其它类型,如使用消息中间件收发消息,使用ElasticSearch存取数据,使用OSS存取文件,使用HttpClient访问外部链接等,这些都需要封装出一个Repository或者Facade接口,通过mock的方式使用mock对象进行代替。

java 复制代码
// 正常的业务代码
@Service
public class GroupMemberCreatorImpl implements GroupMemberCreator {
    private GroupMemberRepository repository;

    @Autowired
    public GroupMemberCreatorImpl(GroupMemberRepository repository) {
        this.repository = repository;
    }

    @Override
    public GroupMember create(Long groupId, String memberName) {
        GroupMember member = new GroupMember(groupId, memberName);
        return repository.save(member); // 在测试中,调此接口会触发mock对象的行为
    }
}

// 测试代码
@SpringBootTest
@AutoConfigureMockMvc
public class HelloControllerTest {
    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private GroupMemberRepository groupMemberRepository;

    @Test
    public void create_normal_group_member() throws Exception {
        Long groupId = 1L;
        String memberName = "zhangsan";
        GroupMember member = new GroupMember(groupId, memberName);
        // 当调Repository.save()时,返回值使用mock对象代替,不真正执行业务代码Repository.save()的逻辑
        Mockito.when(groupMemberRepository.save(Mockito.any())).thenReturn(member);

        MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post("/createGroupMember")
                        .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                        .content("groupId=" + groupId + "&memberName=" + memberName))
                .andExpect(status().isOk())
                .andReturn();
    }
}

上面举的使用消息中间件收发消息,使用ElasticSearch存取数据,使用OSS存取文件,使用HttpClient访问外部链接等都是不太好测的代码,需要封装起来,封装的都是不带业务的技术操作,体现在尽量少的类中,以便能够排除掉,从而满足编写的代码可以达到100%的测试覆盖率。
比如使用消息中间件收发消息,消息的组装和拆解都属于业务操作,只有调消息中间件的SDK方法并传参和返回值属于技术操作,Repository或者Facade接口封装的是这类操作,这类操作主要是隔离一下,没有任何逻辑,所以也不用测试。
对于一个业务系统,这类场景还挺多的:

  • NoSQL数据库读写数据,如Redis、MongoDB等;
  • 消息中间件的收发消息,比如Kafka、RabbitMQ、RocketMQ等;
  • 搜索引擎搜索和存储数据,比如ElasticSearch;
  • 任务调度,如XXL-Job;
  • 图片/文件存取,如OSS;
  • 访问外部链接,如HttpClient;
  • 导入导出Excel数据,如EasyExcel、POI等;
  • 调用RPC接口;
  • 其它第三方SDK等;

3 架构一小步

1、对于数据库,准备一个有空表的数据库,使用@Transactional注解帮助测试;
2、涉及到文件的,则使用临时文件测试;
3、涉及到其它外部资源的(中间件、ElasticSearch等),把技术操作封装成接口,使用mock的方式测试,并测试配置中排除实际的实现类,使得测试覆盖率仍然可以100%;

相关推荐
鼠鼠我捏,要死了捏19 分钟前
MongoDB性能优化实战指南:原理、实践与案例
数据库·mongodb·性能优化
古井无波 202441 分钟前
ARMv8.1原子操作指令(ll_sc/lse)
数据库
__只是为了好玩__1 小时前
MongoDB 数据库 启用访问控制
数据库·mongodb
JeffreyGu.1 小时前
【Oracle】centos7静默安装oracle19c
数据库·oracle
brzhang2 小时前
OpenAI 7周发布Codex,我们的数据库迁移为何要花一年?
前端·后端·架构
boyedu3 小时前
Hyperledger Fabric深入解读:企业级区块链的架构、应用与未来
架构·区块链·fabric·企业级区块链
Databend3 小时前
使用 SeaTunnel 建立从 MySQL 到 Databend 的数据同步管道
数据库
老纪的技术唠嗑局3 小时前
度小满列举五大技术场景拆解数据库选型方案,降本、性能、效率均翻倍
数据库·百度
一眼万里*e4 小时前
Python 字典 (Dictionary) 详解
前端·数据库·python
泉城老铁4 小时前
Gitee上开源主流的springboot框架一探究竟
spring boot·后端·架构