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框架中一个完整契约测试用例的执行流程:

Testing框架契约测试执行流程 1. 构造预期消息 2. 发送消息到队列 3. 消费消息 4. 执行断言链 5. 生成测试报告 关键说明: • 步骤1-3由框架自动编排,开发者只需关注断言逻辑。 • 断言链支持组合多个条件,如同时验证payload、header和超时。 • 测试报告会详细列出每个断言的成功/失败信息,便于定位问题。

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() 触发执行,否则测试将始终通过(假阳性)。

3.6 小结