目录
[MQ 测试目标:](#MQ 测试目标:)
[1. 数据库管理(DataBaseManagerTest)](#1. 数据库管理(DataBaseManagerTest))
[2. 消息文件管理(MessageFileManagerTest)](#2. 消息文件管理(MessageFileManagerTest))
[3. 内存数据中心(MemoryDataCenterTest)](#3. 内存数据中心(MemoryDataCenterTest))
[4. 虚拟主机(VirtualHostTest)](#4. 虚拟主机(VirtualHostTest))
[5. 客户端交互(MqClientTests)](#5. 客户端交互(MqClientTests))
一、项目背景
基于RabbitMQ这一中间件的核心设计理念,使用SpringBoot简化开发,SQLite实现轻量级持久化数据,利用所学的生产者消费者模型为用户提供一个实现解耦合、削峰填谷功能的消息中间件,为用户提供九个相关的API,进行信道的连接、断开、交换机的创建和删除操作等;对交换机、队列、绑定和消息提供实现持久化需的功能,当程序重启, 保证上述内容不丢失,为用户提供数据安全性。客户端通过channel的创建,极大程度提高TCP连接的复用,达到长连接的效果。给客户端提供消息自动应答和手动应答的功能,极大提高消息传输的可靠性,满足对于数据可靠性要求⾼的场景。
- 测试代码地址:jialixuan/幸运弹弹弹 - Gitee.com
二、RabbitMQ介绍
1.什么是RabbitMQ?
RabbitMQ:是一款基于 AMQP(Advanced Message Queuing Protocol)协议的开源消息队列中间件,由 Erlang 语言开发,以轻量级、高可靠性、灵活的路由机制和丰富的消息模式著称。它广泛应用于分布式系统中,实现服务间的异步通信、解耦和流量控制。
RabbitMQ介绍官⽹: RabbitMQ: One broker to queue them all | RabbitMQ总结:RabbitMQ是一个消息中间件, 也是一个生产者消费者模型. 它最核心的功能就是负责接收, 存储并转发消息。
2.RabbitMQ的工作流程是怎么样的?
要了解RabbitMQ的工作流程那清楚记牢一张图就知道了!

3.项目设计

三、测试概述
MQ 测试目标:
测试类别 | 测试模块 | 测试目标 |
---|---|---|
核心数据组件功能测试 | MemoryDataCenter | 1. 验证交换机、队列、绑定关系及消息的 CRUD 操作准确性(新增、查询、删除后的数据一致性) 2. 验证消息发送(sendMessage )、获取(pollMessage )及等待确认(addMessageWaitAck /removeMessageWaitAck )流程的完整性 3. 验证多线程并发操作下的数据安全性(无重复数据、数据丢失或错乱) |
DataBaseManager | 1. 验证数据库表初始化逻辑(默认交换机存在、队列和绑定表为空) 2. 验证交换机、队列、绑定关系的持久化存储与查询一致性(属性完整匹配,包括名称、类型、参数等) 3. 验证删除操作的有效性(删除后数据库中无残留数据) | |
MessageFileManager | 1. 验证队列文件创建与销毁功能(文件及目录存在性校验) 2. 验证消息持久化(sendMessage )、读取(loadAllMessageFromQueue )及删除(deleteMessage )的准确性(消息内容、状态完整匹配) 3. 验证 GC 机制的有效性(清理无效消息后文件大小缩减,有效消息保留完整) |
|
核心业务逻辑测试 | VirtualHost | 1. 验证交换机、队列的声明与删除功能(操作返回值正确性及数据状态一致性) 2. 验证队列与交换机的绑定(queueBind )和解绑(queueUnbind )逻辑(绑定关系正确建立与移除) 3. 验证消息发布(basicPublish )与消费(basicConsume )全流程(消息正确路由至目标队列,消费者能准确接收并处理) 4. 验证不同交换机类型(DIRECT 、FANOUT 、TOPIC )的路由规则有效性(消息按预期分发至匹配队列) |
Router | 1. 验证 TOPIC 类型交换机的路由键(RoutingKey )与绑定键(BindingKey )匹配规则(覆盖通配符 * 和 # 的各种组合场景,确保匹配结果符合预期) |
|
服务器与客户端交互测试 | BrokerServer | 1. 验证服务器对客户端连接的处理(TCP 连接建立、关闭及资源释放) 2. 验证服务器对不同类型请求的解析与响应(创建信道、声明交换机/队列、发布/消费消息等) 3. 验证服务器的并发处理能力(多客户端同时操作时的数据一致性) |
客户端 Channel | 1. 验证客户端与服务器的通信协议(请求格式、响应解析的正确性) 2. 验证消息发布(basicPublish )、订阅(basicConsume )、确认(Ack )等操作的完整性 |
测试用例统计:
测试类 | 测试用例数 | 测试结果 |
---|---|---|
DataBaseManagerTest | 7 | 全部通过 |
MessageFileManagerTest | 6 | 全部通过 |
MemoryDataCenterTest | 6 | 全部通过 |
RouterTest | 16 | 全部通过 |
VirtualHostTest | 11 | 全部通过 |
MqClientTests | 6 | 全部通过 |
总计 | 52 | 全部通过 |
核心模块测试详情及代码示例:
1. 数据库管理(DataBaseManagerTest)
该测试类主要验证数据库层面对交换机、队列和绑定关系的 CRUD 操作。
关键测试代码:
java
/**
* DataBaseManager测试类,用于验证数据库管理器对交换机、队列、绑定关系的CRUD操作
* 测试基于内存数据库(如H2),通过MyBatis映射文件与数据库交互
*/
@SpringBootTest
public class DataBaseManagerTest {
// 数据库管理器实例,作为测试核心操作对象(静态实例确保全局唯一)
private static DataBaseManager dataBaseManager = new DataBaseManager();
/**
* 测试前置方法,在每个测试用例执行前初始化Spring上下文并初始化数据库
* 1. 启动Spring应用上下文,用于获取MyBatis的Mapper实例
* 2. 调用DataBaseManager的init()方法,创建数据表(若不存在)并插入默认数据
*/
@BeforeEach
public void setUp() {
// 初始化Spring上下文,为DataBaseManager提供Mapper实例
MqApplication.context = SpringApplication.run(MqApplication.class);
// 初始化数据库(创建表结构和默认数据)
dataBaseManager.init();
}
/**
* 测试后置方法,在每个测试用例执行后清理资源
* 1. 关闭Spring上下文,释放数据库连接和端口资源
* 2. 删除数据库文件及目录,避免测试数据残留影响后续测试
*/
@AfterEach
public void tearDown() {
// 关闭Spring上下文,释放资源
MqApplication.context.close();
// 删除数据库文件(meta.db)及数据目录
dataBaseManager.deleteDd();
}
/**
* 测试数据库表初始化功能
* 验证初始化后的数据表状态:
* - 交换机表包含1条默认交换机(名称为空字符串)
* - 队列表和绑定表初始状态为空
*/
@Test
public void testInitTable() {
// 查询所有交换机、队列和绑定关系
List<Exchange> exchanges = dataBaseManager.selectAllExchanges();
List<MSGQueue> msgQueues = dataBaseManager.selectAllQueues();
List<Binding> bindings = dataBaseManager.selectAllBindings();
// 验证默认交换机存在(初始化时插入的空名称交换机)
Assertions.assertEquals(1, exchanges.size());
Assertions.assertEquals("", exchanges.get(0).getName());
// 验证队列表初始为空
Assertions.assertEquals(0, msgQueues.size());
// 验证绑定表初始为空
Assertions.assertEquals(0, bindings.size());
}
/**
* 测试交换机插入功能
* 验证流程:
* 1. 创建测试交换机并插入数据库
* 2. 查询所有交换机,验证总数为2(默认交换机+新插入的交换机)
* 3. 验证新插入交换机的名称和类型与预期一致
*/
@Test
public void testInsertExchange() {
// 创建测试交换机(名称为"testExchange",类型为FANOUT)
Exchange exchange = createTestExchange("testExchange");
// 插入交换机到数据库
dataBaseManager.insertExchange(exchange);
// 查询所有交换机
List<Exchange> exchangeList = dataBaseManager.selectAllExchanges();
// 验证交换机总数(默认1条+新增1条)
Assertions.assertEquals(2, exchangeList.size());
// 获取新增的交换机并验证属性
Exchange newExchange = exchangeList.get(1);
Assertions.assertEquals("testExchange", newExchange.getName());
Assertions.assertEquals(ExchangeType.FANOUT, newExchange.getType());
}
/**
* 测试队列删除功能
* 验证流程:
* 1. 创建测试队列并插入数据库
* 2. 验证队列插入成功(查询结果数量为1)
* 3. 执行删除操作后,验证队列已从数据库中移除(查询结果数量为0)
*/
@Test
public void testDeleteQueue() {
// 创建测试队列(名称为"testQueue")
MSGQueue queue = createTestQueue("testQueue");
// 插入队列到数据库
dataBaseManager.insertQueue(queue);
// 验证队列插入成功
List<MSGQueue> queueList = dataBaseManager.selectAllQueues();
Assertions.assertEquals(1, queueList.size());
// 执行删除队列操作
dataBaseManager.deleteQueue("testQueue");
// 验证队列已被删除
queueList = dataBaseManager.selectAllQueues();
Assertions.assertEquals(0, queueList.size());
}
/**
* 创建测试交换机
* @param exchangeName 交换机名称
* @return 构造好的交换机实例(FANOUT类型,持久化,非自动删除)
*/
private Exchange createTestExchange(String exchangeName) {
Exchange exchange = new Exchange();
exchange.setName(exchangeName);
exchange.setType(ExchangeType.FANOUT);
exchange.setAutoDelete(false);
exchange.setDurable(true);
exchange.setArguments("aaa", 1);
exchange.setArguments("bbb", 2);
return exchange;
}
/**
* 创建测试队列
* @param queueName 队列名称
* @return 构造好的队列实例(持久化,非排他,非自动删除)
*/
private MSGQueue createTestQueue(String queueName) {
MSGQueue queue = new MSGQueue();
queue.setName(queueName);
queue.setDurable(true);
queue.setAutoDelete(false);
queue.setExclusive(false);
queue.setArguments("aaa", 1);
queue.setArguments("bbb", 2);
return queue;
}
}
2. 消息文件管理(MessageFileManagerTest)
验证消息在文件系统中的持久化存储、读取、删除及 GC 压缩功能。
关键测试代码:
java
/**
* MessageFileManager测试类,用于验证消息文件管理器的核心功能
* 包括消息的持久化存储、读取、删除及垃圾回收(GC)等文件操作
*/
@Slf4j
@SpringBootTest
class MessageFileManagerTest {
// 消息文件管理器实例,测试操作的核心对象
private MessageFileManager messageFileManager = new MessageFileManager();
// 测试用队列名称常量,避免硬编码
private static final String queueName1 = "testQueue1";
/**
* 测试前置方法,在每个测试用例执行前初始化队列文件
* 创建测试队列对应的目录和数据文件,为后续测试提供基础环境
* @throws IOException 当创建队列文件失败时抛出
*/
@BeforeEach
public void setUp() throws IOException {
messageFileManager.createQueueFile(queueName1);
}
/**
* 测试后置方法,在每个测试用例执行后清理队列文件
* 删除测试过程中生成的队列目录及数据文件,避免影响其他测试
* @throws IOException 当删除队列文件失败时抛出
*/
@AfterEach
public void tearDown() throws IOException {
messageFileManager.destroyQueueFiles(queueName1);
}
/**
* 测试消息发送与持久化功能
* 验证流程:
* 1. 创建测试消息和队列
* 2. 发送消息到队列并持久化到文件
* 3. 验证统计信息中消息总数是否正确
* 4. 验证从文件加载的消息与发送的消息内容一致
* @throws IOException 当文件操作失败时抛出
* @throws MqException 当MQ操作异常时抛出
* @throws ClassNotFoundException 当消息反序列化失败时抛出
*/
@Test
public void testSendMessage() throws IOException, MqException, ClassNotFoundException {
// 创建测试消息(内容为"testMessage")
Message message = createTestMessage("testMessage");
// 创建测试队列(使用预定义的队列名)
MSGQueue queue = createTestQueue(queueName1);
// 发送消息到队列,触发持久化操作
messageFileManager.sendMessage(queue, message);
// 反射调用私有方法读取统计信息(验证消息总数)
MessageFileManager.Stat stat = ReflectionTestUtils.invokeMethod(messageFileManager, "readStat", queueName1);
Assertions.assertEquals(1, stat.totalCount);
// 从文件加载所有消息并验证
LinkedList<Message> messages = messageFileManager.loadAllMessageFromQueue(queueName1);
Assertions.assertEquals(1, messages.size()); // 验证消息数量
Assertions.assertArrayEquals(message.getBody(), messages.get(0).getBody()); // 验证消息内容
}
/**
* 测试消息文件的垃圾回收(GC)功能
* 验证流程:
* 1. 向队列发送100条测试消息
* 2. 记录GC前的文件大小
* 3. 删除偶数下标的消息(逻辑删除)
* 4. 执行GC操作(清理无效消息,压缩文件)
* 5. 验证GC后的文件大小小于GC前(确认压缩效果)
* @throws IOException 当文件操作失败时抛出
* @throws MqException 当MQ操作异常时抛出
* @throws ClassNotFoundException 当消息反序列化失败时抛出
*/
@Test
public void testGC() throws IOException, MqException, ClassNotFoundException {
// 创建测试队列
MSGQueue queue = createTestQueue(queueName1);
// 存储所有发送的消息,用于后续删除操作
List<Message> messages = new LinkedList<>();
// 发送100条测试消息
for (int i = 0; i < 100; i++) {
Message message = createTestMessage("testMessage" + i);
messageFileManager.sendMessage(queue, message);
messages.add(message);
}
// 记录GC前的数据文件大小
File beforeGCFile = new File("./data/" + queueName1 + "/queue_data.txt");
long beforeGCLength = beforeGCFile.length();
// 删除偶数下标的消息(逻辑删除,标记为无效)
for (int i = 0; i < 100; i += 2) {
messageFileManager.deleteMessage(queue, messages.get(i));
}
// 执行GC操作,清理无效消息并压缩文件
messageFileManager.gc(queue);
// 验证GC后的文件大小小于GC前
File afterGCFile = new File("./data/" + queueName1 + "/queue_data.txt");
Assertions.assertTrue(afterGCFile.length() < beforeGCLength);
}
/**
* 创建测试队列
* @param queueName 队列名称
* @return 构造好的测试队列实例(持久化、非自动删除)
*/
private MSGQueue createTestQueue(String queueName) {
MSGQueue queue = new MSGQueue();
queue.setName(queueName);
queue.setDurable(true);
queue.setAutoDelete(false);
queue.setExclusive(false);
return queue;
}
/**
* 创建测试消息
* @param content 消息内容
* @return 构造好的测试消息实例(包含指定内容的字节数组)
*/
private Message createTestMessage(String content) {
return Message.createMessageWithId("testRoutingKey", null, content.getBytes());
}
}
3. 内存数据中心(MemoryDataCenterTest)
测试内存中交换机、队列、消息的管理及消息收发机制。
关键测试代码:
java
/**
* MemoryDataCenter测试类,用于验证内存数据中心的核心功能
* 包括交换机管理、消息发送与接收等内存数据操作
*/
@SpringBootTest
class MemoryDataCenterTest {
// 内存数据中心实例,测试操作的核心对象
private MemoryDataCenter memoryDataCenter = null;
/**
* 测试前置方法,在每个测试用例执行前初始化内存数据中心
* 确保每个测试用例都使用全新的内存数据中心实例,避免测试间相互干扰
*/
@BeforeEach
public void setUp() {
memoryDataCenter = new MemoryDataCenter();
}
/**
* 测试交换机的增删查功能
* 验证流程:
* 1. 创建测试交换机并插入内存数据中心
* 2. 查询交换机并验证其与插入的交换机一致
* 3. 删除交换机后验证查询结果为null
*/
@Test
public void testExchange() {
// 创建测试交换机(实际代码中应有createTestExchange方法,此处假设已实现)
Exchange expectedExchange = createTestExchange("testExchange");
// 插入交换机到内存数据中心
memoryDataCenter.insertExchange(expectedExchange);
// 查询交换机并验证是否与插入的一致
Exchange actualExchange = memoryDataCenter.getExchange("testExchange");
Assertions.assertEquals(expectedExchange, actualExchange);
// 删除交换机
memoryDataCenter.deleteExchange("testExchange");
// 验证交换机已被删除
actualExchange = memoryDataCenter.getExchange("testExchange");
Assertions.assertNull(actualExchange);
}
/**
* 测试消息发送到队列及从队列接收消息的功能
* 验证流程:
* 1. 创建测试队列和10条测试消息
* 2. 将消息发送到队列并记录预期消息列表
* 3. 从队列中取出所有消息并记录实际消息列表
* 4. 验证实际消息与预期消息的数量和内容完全一致
*/
@Test
public void testSendMessage() {
// 创建测试队列(实际代码中应有createTestQueue方法,此处假设已实现)
MSGQueue queue = createTestQueue("testQueue");
// 存储预期发送的消息列表
List<Message> expectedMessages = new ArrayList<>();
// 创建10条测试消息并发送到队列
for (int i = 0; i < 10; i++) {
Message message = createTestMessage("testMessage" + i); // 假设存在创建测试消息的方法
memoryDataCenter.sendMessage(queue, message);
expectedMessages.add(message);
}
// 存储从队列中实际取出的消息列表
List<Message> actualMessages = new ArrayList<>();
// 循环从队列中获取消息直到队列为空
while (true) {
Message message = memoryDataCenter.pollMessage("testQueue");
if (message == null) break;
actualMessages.add(message);
}
// 验证消息数量一致
Assertions.assertEquals(expectedMessages.size(), actualMessages.size());
// 验证每条消息内容一致
for (int i = 0; i < expectedMessages.size(); i++) {
Assertions.assertEquals(expectedMessages.get(i), actualMessages.get(i));
}
}
/**
* 创建测试交换机(实际实现方法,用于测试)
* @param exchangeName 交换机名称
* @return 构造好的测试交换机实例
*/
private Exchange createTestExchange(String exchangeName) {
Exchange exchange = new Exchange();
exchange.setName(exchangeName);
exchange.setType(ExchangeType.DIRECT); // 示例使用DIRECT类型
exchange.setDurable(true);
exchange.setAutoDelete(false);
return exchange;
}
/**
* 创建测试队列(实际实现方法,用于测试)
* @param queueName 队列名称
* @return 构造好的测试队列实例
*/
private MSGQueue createTestQueue(String queueName) {
MSGQueue queue = new MSGQueue();
queue.setName(queueName);
queue.setDurable(true);
queue.setAutoDelete(false);
return queue;
}
/**
* 创建测试消息(实际实现方法,用于测试)
* @param content 消息内容
* @return 构造好的测试消息实例
*/
private Message createTestMessage(String content) {
return Message.createMessageWithId("testRoutingKey", null, content.getBytes());
}
}
4. 虚拟主机(VirtualHostTest)
验证虚拟主机中交换机、队列的声明 / 删除、绑定 / 解绑及消息发布消费流程。
关键测试代码:
java
/**
* VirtualHost测试类,用于验证虚拟主机(VirtualHost)的核心功能
* 包括交换机声明、队列操作、消息发布与消费等MQ核心流程
*/
class VirtualHostTest {
// 虚拟主机实例,测试用例的核心操作对象
private VirtualHost virtualHost = null;
/**
* 测试前置方法,在每个测试用例执行前初始化环境
* 1. 启动Spring应用上下文,加载必要的Bean组件
* 2. 创建名为"test"的虚拟主机实例
*/
@BeforeEach
public void setUp() {
// 初始化Spring上下文,为依赖注入提供支持
MqApplication.context = SpringApplication.run(MqApplication.class);
// 创建测试用虚拟主机
virtualHost = new VirtualHost("test");
}
/**
* 测试后置方法,在每个测试用例执行后清理环境
* 1. 关闭Spring应用上下文释放资源
* 2. 销毁虚拟主机实例
* 3. 删除测试过程中生成的data目录(消息持久化数据)
* @throws IOException 当删除目录时发生I/O错误时抛出
*/
@AfterEach
public void tearDown() throws IOException {
// 关闭Spring上下文
MqApplication.context.close();
// 置空虚拟主机引用,帮助GC回收
virtualHost = null;
// 清理测试产生的持久化文件
File file = new File("./data");
FileUtils.deleteDirectory(file);
}
/**
* 测试先订阅队列后发送消息的场景
* 验证流程:
* 1. 声明DIRECT类型交换机和队列
* 2. 先注册消费者订阅队列
* 3. 发送消息到交换机
* 4. 验证消费者能否正确接收并处理消息
* @throws InterruptedException 当线程休眠被中断时抛出
*/
@Test
public void testBasicConsume1() throws InterruptedException {
// 声明DIRECT类型持久化交换机
virtualHost.exchangeDeclare("testExchange", ExchangeType.DIRECT, true, false, null);
// 声明持久化队列
virtualHost.queueDeclare("testQueue", true, false, false, null);
// 先订阅队列,设置自动确认模式,定义消息处理回调
boolean ok = virtualHost.basicConsume("testConsumerTag", "testQueue", true, new Consumer() {
/**
* 消息处理回调方法,验证消息属性和内容
* @param consumerTag 消费者标签,标识当前消费者
* @param props 消息属性,包含路由键等元数据
* @param body 消息体内容
*/
@Override
public void handleDelivery(String consumerTag, BasicProperties props, byte[] body) {
// 验证消息路由键是否正确
Assertions.assertEquals("testQueue", props.getRoutingKey());
// 验证消息内容是否与发送内容一致
Assertions.assertArrayEquals("hello".getBytes(), body);
}
});
// 验证订阅操作是否成功
Assertions.assertTrue(ok);
// 休眠500ms等待订阅生效
Thread.sleep(500);
// 发送消息到交换机,路由键为队列名
boolean publishOk = virtualHost.basicPublish("testExchange", "testQueue", null, "hello".getBytes());
// 验证消息发送是否成功
Assertions.assertTrue(publishOk);
}
/**
* 测试FANOUT类型交换机的消息广播功能
* 验证流程:
* 1. 声明FANOUT类型交换机
* 2. 创建两个队列并绑定到交换机(FANOUT交换机忽略绑定键)
* 3. 发送消息到交换机
* 4. 验证两个队列的消费者都能收到消息
* @throws InterruptedException 当线程操作被中断时抛出
*/
@Test
public void testBasicConsumeFanout() throws InterruptedException {
// 声明FANOUT类型持久化交换机(广播模式)
virtualHost.exchangeDeclare("testExchange", ExchangeType.FANOUT, true, false, null);
// 声明第一个队列并绑定到交换机
virtualHost.queueDeclare("testQueue1", true, false, false, null);
virtualHost.queueBind("testQueue1", "testExchange", "");
// 声明第二个队列并绑定到交换机
virtualHost.queueDeclare("testQueue2", true, false, false, null);
virtualHost.queueBind("testQueue2", "testExchange", "");
// 向FANOUT交换机发送消息(路由键无效,可留空)
virtualHost.basicPublish("testExchange", "", null, "hello".getBytes());
// 第一个队列的消费者验证消息
virtualHost.basicConsume("tag1", "testQueue1", true, (tag, props, body) ->
Assertions.assertArrayEquals("hello".getBytes(), body));
// 第二个队列的消费者验证消息(FANOUT交换机应广播给所有绑定队列)
virtualHost.basicConsume("tag2", "testQueue2", true, (tag, props, body) ->
Assertions.assertArrayEquals("hello".getBytes(), body));
}
}
5. 客户端交互(MqClientTests)
测试客户端与服务器的完整交互流程,包括连接建立、通道创建、消息生产和消费。
关键测试代码:
java
/**
* MQ客户端测试类,用于验证客户端与MQ服务器之间的交互功能
* 包括连接建立、通道创建、交换机/队列操作及消息收发等核心流程
*/
public class MqClientTests {
// Broker服务器实例,用于在测试中启动和停止MQ服务
private BrokerServer brokerServer = null;
// 连接工厂,用于创建客户端与服务器的连接
private ConnectionFactory factory = null;
// 用于运行BrokerServer的线程,避免服务器启动阻塞测试流程
private Thread t = null;
/**
* 测试前置方法,在每个测试用例执行前运行
* 1. 启动Spring应用上下文
* 2. 初始化并启动Broker服务器(在独立线程中运行)
* 3. 配置连接工厂,设置服务器地址和端口
* @throws IOException 当I/O操作失败时抛出
*/
@BeforeEach
public void setUp() throws IOException {
// 启动Spring应用上下文,加载必要的Bean
MqApplication.context = SpringApplication.run(MqApplication.class);
// 创建Broker服务器实例,监听9090端口
brokerServer = new BrokerServer(9090);
// 创建线程运行Broker服务器(start()方法会进入循环,需独立线程)
t = new Thread(() -> {
try {
brokerServer.start();
} catch (IOException e) {
e.printStackTrace();
}
});
t.start();
// 初始化连接工厂,配置服务器地址和端口
factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setPort(9090);
}
/**
* 测试后置方法,在每个测试用例执行后运行
* 1. 停止Broker服务器
* 2. 关闭Spring应用上下文
* 3. 清理测试生成的data目录(消息持久化数据)
* @throws IOException 当I/O操作失败时抛出
*/
@AfterEach
public void tearDown() throws IOException {
// 停止Broker服务器
brokerServer.stop();
// 关闭Spring应用上下文
MqApplication.context.close();
// 删除测试过程中生成的data目录及文件
File file = new File("./data");
FileUtils.deleteDirectory(file);
}
/**
* 测试消息发送与消费的完整流程
* 1. 建立连接和通道
* 2. 声明交换机和队列
* 3. 发送测试消息到交换机
* 4. 订阅队列并验证消息内容
* @throws IOException 当I/O操作失败时抛出
* @throws MqException 当MQ操作失败时抛出
* @throws InterruptedException 当线程休眠被中断时抛出
*/
@Test
public void testMessage() throws IOException, MqException, InterruptedException {
// 创建与服务器的连接
Connection connection = factory.newConnection();
// 创建通道(基于连接的通信通道)
Channel channel = connection.createChannel();
// 声明一个DIRECT类型的持久化交换机
channel.exchangeDeclare("testExchange", ExchangeType.DIRECT, true, false, null);
// 声明一个持久化队列
channel.queueDeclare("testQueue", true, false, false, null);
// 准备测试消息内容
byte[] requestBody = "hello".getBytes();
// 发送消息到指定交换机,指定路由键为队列名(DIRECT交换机根据路由键匹配队列)
channel.basicPublish("testExchange", "testQueue", null, requestBody);
// 订阅队列,设置自动确认,并定义消息处理回调
channel.basicConsume("testQueue", true, new Consumer() {
/**
* 消息处理回调方法,验证消费的消息内容与发送的一致
* @param consumerTag 消费者标签,用于标识消费者
* @param basicProperties 消息属性
* @param body 消息体内容
*/
@Override
public void handleDelivery(String consumerTag, BasicProperties basicProperties, byte[] body) {
// 断言消费的消息体与发送的一致
Assertions.assertArrayEquals(requestBody, body);
}
});
// 休眠500ms,等待消息处理完成
Thread.sleep(500);
// 关闭通道和连接
channel.close();
connection.close();
}
}
结论
所有 52 个测试用例均通过验证,覆盖了 MQ 系统的核心功能点,包括交换机 / 队列管理、消息存储与传输、路由规则、客户端交互等。测试结果表明,系统各模块功能正常,模块间交互符合预期,整体表现稳定可靠,能够满足消息队列的基本功能需求。