3. Testing框架入门:Testing框架简介、安装与配置、基础测试用例编写(assert断言)
3.1 Testing框架概述
Testing框架是消息队列任务契约测试的核心执行引擎。它提供了一套标准化的测试定义、执行与报告机制,使得开发者能够以声明式的方式描述消息队列的“契约”——即生产者与消费者之间的数据格式、顺序、超时与异常约定。
与传统的单元测试框架不同,Testing框架专为分布式消息场景设计,具备以下核心能力:
- 契约驱动:测试用例直接映射为消息队列的Topic/Partition契约。
- 异步编排:原生支持消息的异步发送、消费与超时断言。
- 版本感知:每个测试用例可绑定消息Schema版本,实现契约的版本管理。
- 轻量嵌入:可嵌入到CI/CD流水线中,无需独立部署测试服务。
适用场景:Testing框架特别适合微服务架构中,多个服务通过消息队列进行异步通信时的接口兼容性验证。它帮助团队在消息Schema升级、消费者逻辑变更时,快速发现契约断裂点。
3.2 安装与配置
Testing框架支持主流编程语言与消息中间件。以下以Java环境为例,展示安装与基础配置步骤。
3.2.1 环境要求
| 组件 | 版本要求 | 说明 |
|---|---|---|
| JDK | 11+ | 推荐使用JDK 17 LTS |
| Maven/Gradle | Maven 3.8+ / Gradle 7+ | 构建工具 |
| 消息中间件 | Kafka 3.0+ / RabbitMQ 3.9+ | 测试目标环境 |
| Testing框架 | 2.1.0+ | 核心依赖 |
3.2.2 Maven依赖配置
在 pom.xml 中添加Testing框架核心依赖及消息中间件适配器:
<dependency>
<groupId>io.testing.mq</groupId>
<artifactId>testing-framework-core</artifactId>
<version>2.1.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.testing.mq</groupId>
<artifactId>testing-adapter-kafka</artifactId>
<version>2.1.0</version>
<scope>test</scope>
</dependency>
3.2.3 基础配置文件
在 src/test/resources 目录下创建 testing-config.yml:
testing:
mq:
type: kafka
bootstrap-servers: localhost:9092
consumer:
group-id: testing-group
auto-offset-reset: earliest
producer:
acks: all
contract:
schema-registry: http://localhost:8081
default-version: 1.0.0
注意:
schema-registry 地址必须与消息队列的Schema Registry服务地址一致。如果未部署Schema Registry,可将 default-version 设置为 none,但此时版本管理功能将受限。
3.3 基础测试用例编写:assert断言
Testing框架的断言体系围绕消息契约展开,核心是 assertThat 方法链。以下通过一个订单消息的契约测试案例,展示基础断言的使用。
3.3.1 测试用例结构
import io.testing.mq.core.MqTest;
import io.testing.mq.core.annotation.MqTest;
import io.testing.mq.core.annotation.MqContract;
import io.testing.mq.core.assertion.MqAssertions;
@MqTest
public class OrderContractTest {
@MqContract(topic = "order.created", version = "1.0.0")
public void shouldValidateOrderCreatedMessage() {
// 1. 构造预期消息
String expectedPayload = "{\"orderId\":\"12345\",\"amount\":99.9}";
// 2. 发送消息到队列
MqAssertions.given()
.topic("order.created")
.message(expectedPayload)
.send();
// 3. 消费并断言
MqAssertions.then()
.expectMessage()
.withTopic("order.created")
.withPayloadContaining("orderId")
.withPayloadContaining("amount")
.assertAll();
}
}
3.3.2 常用断言方法
| 断言方法 | 用途 | 示例 |
|---|---|---|
withPayloadContaining(String) |
断言消息体包含指定字符串 | .withPayloadContaining("orderId") |
withPayloadJsonPath(String, Object) |
断言JSON路径的值符合预期 | .withPayloadJsonPath("$.amount", 99.9) |
withHeader(String, String) |
断言消息头包含指定键值对 | .withHeader("version", "1.0.0") |
withinTimeout(Duration) |
断言消息在指定时间内到达 | .withinTimeout(Duration.ofSeconds(5)) |
assertAll() |
执行所有累积的断言 | 必须作为链式调用的终点 |
3.3.3 断言执行流程
下图展示了Testing框架中一个完整契约测试用例的执行流程:
3.4 版本管理在断言中的体现
Testing框架的断言天然支持版本管理。通过 @MqContract 注解的 version 属性,每个测试用例都与特定的消息Schema版本绑定。当消息Schema升级时,只需新增对应版本的测试用例,旧版本用例继续保留,形成版本兼容性测试矩阵。
版本断言最佳实践:
- 每个消息版本对应一个独立的测试方法,使用
@MqContract(version = "x.y.z")显式声明。 - 在断言中使用
withHeader("version", "x.y.z")验证消息头中的版本号。 - 对于向后兼容的字段,使用
withPayloadJsonPath断言新字段的默认值或缺失处理。
3.5 常见断言模式与避坑
| 模式 | 代码示例 | 注意事项 |
|---|---|---|
| 精确匹配 | .withPayload(expectedJson) |
要求消息体完全一致,适用于固定契约 |
| 部分匹配 | .withPayloadContaining("key") |
适用于验证关键字段存在性 |
| JSON路径匹配 | .withPayloadJsonPath("$.user.name", "Alice") |
路径表达式需与JSON结构一致,避免空指针 |
| 超时断言 | .withinTimeout(Duration.ofSeconds(3)) |
超时时间不宜过短,建议根据实际延迟设置 |
| 顺序断言 | .expectMessages().inOrder() |
仅用于严格顺序的场景,如事件溯源 |
常见错误:在异步消息测试中,忘记调用
assertAll() 是导致断言不生效的最常见原因。所有链式断言必须由 assertAll() 触发执行,否则测试将始终通过(假阳性)。