小架构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%;

相关推荐
oradh2 小时前
Oracle XTTS实现跨版本迁移和升级(Oracle 11g单库升级至19C RAC集群)
数据库·oracle·11g升级19c·xtts跨版本迁移和升级
z123456789863 小时前
2026最新两款AI编程工具深度对比实测
java·数据库·ai编程
程序猿DD3 小时前
一个 API Key,统一调用大模型、生图和联网搜索
数据库·网关
TlSfoward4 小时前
抓包代理链路下的 TLS 指纹变化分析 TLSFOWARD抓包工具
数据库·爬虫·网络协议·搜索引擎·php
数智化管理手记4 小时前
账龄分析手工统计易遗漏?自动账龄分析工具怎么搭建
大数据·网络·数据库·人工智能·数据挖掘
LabVIEW开发5 小时前
NI Package Manager安装LabVIEW时InternalServerError错误
网络·数据库·labview·labview知识·labview功能·labview程序
SelectDB5 小时前
PostgreSQL 实时同步到 Apache Doris:一站式 CDC 解决方案
数据库
大鱼>5 小时前
深入Real-ESRGAN架构:RRDBNet设计精髓与ONNX/TensorRT部署优化
人工智能·深度学习·架构
Database_Cool_5 小时前
AI Agent 长期记忆怎么存?阿里云 PolarDB-X 对接 Mem0 记忆框架实践
数据库·阿里云
会编程的土豆6 小时前
数据库范式
数据库·oracle