Files
cs-note/hhs/MQ/10-监控与运维/41-MQ-测试策略.md
T
2026-05-24 20:51:06 +08:00

6.2 KiB
Raw Blame History

tags, create time
tags create time
MQ
测试
集成测试
契约测试
2026-05-24 19:52

MQ 测试策略

概述

MQ 的测试面临异步性、消息顺序、环境依赖等独特挑战。本文从单元测试到混沌工程,逐层介绍 MQ 测试策略,重点讲解 Testcontainers 集成测试、Pact 契约测试和影子流量等实践方法。

正文

MQ 测试的挑战

和同步的 HTTP API 相比,MQ 测试难在哪里?

  1. 异步性:消息发送后不立即得到结果,如何验证消息被正确处理?
  2. 消息顺序:测试时如何保证消息的顺序和幂等性?
  3. 环境依赖:没有真实的 Broker,很多行为无法验证(如 Partition 路由、Consumer Group Rebalance)
  4. 分布式一致性:跨多个服务的事件流转,如何端到端验证?

[!question] 传统 API 测试可以用 assert response.status == 200。但 MQ 中,消息发出去后"成功"意味着什么?是 Broker 收到?还是被消费者处理完?

测试金字塔在 MQ 场景的应用

graph TD
    subgraph Pyramid["MQ 测试金字塔"]
        E2E["E2E 测试 - 完整集群 + 多服务"]
        Integration["集成测试 - Testcontainers 真实 Broker"]
        Contract["契约测试 - Pact 消息契约"]
        Unit["单元测试 - Mock Producer/Consumer"]
    end
    Unit -->|"快速, 大量"| Contract
    Contract -->|"契约保证兼容"| Integration
    Integration -->|"真实环境验证"| E2E

单元测试:Mock 接口,验证逻辑

单元测试的核心是将消息处理逻辑与 Broker 解耦。在 Go 中,通过接口抽象 Producer 和 Consumer:

// 定义接口,方便 Mock
type MessageProducer interface {
	Send(ctx context.Context, topic string, key, value []byte) error
}

type OrderHandler struct {
	producer MessageProducer
}

// 处理订单创建事件的业务逻辑
func (h *OrderHandler) HandleOrderCreated(ctx context.Context, event OrderEvent) error {
	if event.Amount <= 0 {
		return fmt.Errorf("invalid amount: %d", event.Amount)
	}
	// 发送支付请求事件
	payment := PaymentEvent{OrderID: event.ID, Amount: event.Amount}
	data, _ := json.Marshal(payment)
	return h.producer.Send(ctx, "payment-requests", []byte(event.ID), data)
}

// 单元测试:Mock Producer 验证业务逻辑
func TestOrderHandler_HandleOrderCreated(t *testing.T) {
	mock := &MockProducer{} // 实现 MessageProducer 接口
	handler := &OrderHandler{producer: mock}

	event := OrderEvent{ID: "order-1", Amount: 100}
	err := handler.HandleOrderCreated(context.Background(), event)

	assert.NoError(t, err)
	assert.Equal(t, "payment-requests", mock.LastTopic())
	assert.Equal(t, "order-1", mock.LastKey())
}

单元测试快速且无外部依赖,但只能验证业务逻辑,无法保证消息格式与下游兼容。

集成测试:Testcontainers 启动真实 Broker

Mock 测试不到的问题——Partition 路由、序列化、Consumer Group 行为——需要真实 Broker。Testcontainers 在测试中启动真实的 Docker 容器:

package main

import (
	"context"
	"testing"
	"time"

	"github.com/IBM/sarama"
	"github.com/testcontainers/testcontainers-go"
	"github.com/testcontainers/testcontainers-go/modules/kafka"
)

func TestKafkaIntegration(t *testing.T) {
	ctx := context.Background()

	// 启动真实的 Kafka 容器
	kafkaContainer, err := kafka.Run(ctx,
		"confluentinc/confluent-local:7.5.0",
	)
	if err != nil {
		t.Fatal(err)
	}
	defer kafkaContainer.Terminate(ctx)

	// 获取 Broker 地址
	brokers, _ := kafkaContainer.Brokers(ctx)

	// 创建 Producer 发送消息
	config := sarama.NewConfig()
	config.Producer.Return.Successes = true
	producer, _ := sarama.NewSyncProducer(brokers, config)
	defer producer.Close()

	msg := &sarama.ProducerMessage{
		Topic: "test-topic",
		Value: sarama.StringEncoder("hello kafka"),
	}
	partition, offset, err := producer.SendMessage(msg)
	if err != nil {
		t.Fatalf("send failed: %v", err)
	}

	// 创建 Consumer 消费并验证
	config2 := sarama.NewConfig()
	config2.Consumer.Offsets.Initial = sarama.OffsetOldest
	consumer, _ := sarama.NewConsumer(brokers, config2)
	defer consumer.Close()

	pc, _ := consumer.ConsumePartition("test-topic", partition, offset)
	select {
	case msg := <-pc.Messages():
		if string(msg.Value) != "hello kafka" {
			t.Errorf("unexpected message: %s", msg.Value)
		}
	case <-time.After(10 * time.Second):
		t.Fatal("timeout waiting for message")
	}
}

这段测试用 Testcontainers 自动拉起 Kafka 容器,发送一条消息后立即消费验证。整个过程在 CI 中无需预置环境,测试结束自动清理容器。

消息契约测试

在事件驱动架构中,Producer 和 Consumer 通过消息格式(Schema)解耦。但如果 Producer 改了字段名,Consumer 就会崩溃。契约测试解决这个问题。

Pact 框架的工作流程:

  1. Consumer 端定义期望的消息格式(契约)
  2. Producer 端验证自己产生的消息满足所有 Consumer 的契约
  3. 契约存储在 Pact Broker 中,CI 中自动验证

[!question] 消息契约测试和传统的 API 测试有什么本质区别?为什么在事件驱动架构中更重要?

关键区别在于:API 测试验证的是"请求-响应"的一对一关系,而消息契约验证的是"事件-消费者"的一对多关系。一个事件可能被 5 个服务消费,任何格式变更都必须向后兼容。

影子流量(Shadow Testing)

将生产流量复制到测试环境,验证新版本的消息处理逻辑是否正确,而不影响真实业务。

实现方式:

  • Producer 端使用拦截器,将消息副本发送到 Shadow Topic
  • Shadow Consumer 消费并处理,对比结果
  • 关键:Shadow 消费者的处理结果不会写入生产数据库

故障注入

Chaos Engineering 在 MQ 场景的应用:

  • Kill Broker:随机杀掉一个 Broker,验证 Producer/Consumer 的自动故障转移
  • 网络分区:模拟 Broker 之间网络隔离,验证脑裂防护
  • 消息延迟注入:人为增加 Broker 响应延迟,验证超时和重试机制
  • 磁盘满:模拟磁盘空间不足,验证告警和降级策略

关联笔记