1.项目概况

2.单元测试
运用 JUnit 5 作为测试框架,Spring Boot 测试来管理上下文。
2.1 DataBaseManager(数据库管理)
在test目录中,创建DataBaseManagerTests类。


java
package com.example.mq;
import com.example.mq.mqserver.core.Binding;
import com.example.mq.mqserver.core.Exchange;
import com.example.mq.mqserver.core.ExchangeType;
import com.example.mq.mqserver.core.MSGQueue;
import com.example.mq.mqserver.datacenter.DataBaseManager;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
// 加上这个注解之后, 改类就会被识别为单元测试类.
@SpringBootTest
public class DataBaseManagerTests {
// 成员变量声明与初始,创建 DataBaseManager 类的新实例并赋值给该字段
private DataBaseManager dataBaseManager = new DataBaseManager();
// 接下来下面这里需要编写多个 方法 . 每个方法都是一个/一组单元测试用例.
// 还需要做一个准备工作. 需要写两个方法, 分别用于进行 "准备工作" 和 "收尾工作"
// 使用这个方法, 来执行准备工作. 每个用例执行前, 都要调用这个方法. setUp--安装、程序
@BeforeEach //Each--每一个
public void setUp(){
// 由于在 init 中, 需要通过 context 对象拿到 metaMapper 实例的.
// 所以就需要先把 context 对象给搞出来.
MqApplication.context = SpringApplication.run(MqApplication.class);
dataBaseManager.init();
}
// 使用这个方法, 来执行收尾工作. 每个用例执行后, 都要调用这个方法. tearDown--拆卸
@AfterEach
public void tearDown(){
// 这里要进行的操作, 就是把数据库给清空~~ (把数据库文件, meta.db 直接删了就行了)
// 注意, 此处不能直接就删除, 而需要先关闭上述 context 对象!!
// 此处的 context 对象, 持有了 MetaMapper 的实例, MetaMapper 实例又打开了 meta.db 数据库文件.
// 如果 meta.db 被别人打开了, 此时的删除文件操作是不会成功的 (Windows 系统的限制, Linux 则没这个问题).
// 另一方面, 获取 context 操作, 会占用 8080 端口. 此处的 close 也是释放 8080.
MqApplication.context.close();
dataBaseManager.deleteDB();
}
@Test
//通过查询操作验证上述 dataBaseManager.init() 初始化是否成功
public void testInitTable(){
// 由于 init 方法, 已经在上面 setUp 中调用过了. 直接在测试用例代码中, 检查当前的数据库状态即可.
// 直接从数据库中查询. 看数据是否符合预期.
// 查交换机表, 里面应该有一个数据(匿名的 exchange); 查队列表, 没有数据; 查绑定表, 没有数据
List<Exchange> exchangeList = dataBaseManager.selectAllExchanges();
List<MSGQueue> queueList = dataBaseManager.selectAllQueues();
List<Binding> bindingList = dataBaseManager.selectAllBindings();
// 验证结果是否正确 System.out.println(exchangeList.size());
// 直接打印结果, 通过肉眼来检查结果, 固然也可以. 但是不优雅, 不方便.
// 更好的办法是使用断言. Assertions 工具类 assertEquals 判定结果是不是相等.
// 注意这俩参数的顺序. 虽然比较相等, 谁在前谁在后, 无所谓.
// 但是 assertEquals 的形参, 第一个形参叫做 expected (预期的), 第二个形参叫做 actual (实际的)
Assertions.assertEquals(1, exchangeList.size());
Assertions.assertEquals("", exchangeList.get(0).getName());
Assertions.assertEquals(ExchangeType.DIREXT, exchangeList.get(0).getType());
Assertions.assertEquals(0, queueList.size());
Assertions.assertEquals(0, bindingList.size());
}
// 构造一个 Exchange 对象, 创建一个用来测试的交换机
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); //用key value方法
exchange.setArguments("bbb", 2);
return exchange;
}
// 测试交换机插入
// 构造一个 Exchange 对象, 插入到数据库中. 再查询出来, 看结果是否符合预期
@Test
public void testInsertExchange(){
Exchange exchange = createTestExchange("testExchange");
dataBaseManager.insertExchange(exchange); //插入数据库
// 插入完毕之后, 查询结果
// 使用断言. Assertions 工具类 assertEquals 判定结果是不是相等.
List<Exchange> exchangeList = dataBaseManager.selectAllExchanges();
Assertions.assertEquals(2, exchangeList.size()); //默认的和刚插入的共2个
Exchange newExchange = exchangeList.get(1); //把新插入的对象取出来
Assertions.assertEquals("testExchange", newExchange.getName());
Assertions.assertEquals(ExchangeType.FANOUT, newExchange.getType());
Assertions.assertEquals(false, newExchange.isAutoDelete());
Assertions.assertEquals(true, newExchange.isDurable());
Assertions.assertEquals(1, newExchange.getArguments("aaa"));
Assertions.assertEquals(2, newExchange.getArguments("bbb"));
}
// 测试交换机删除
// 先构造一个交换机, 插入数据库,简单查询俩; 再按照名字删除,再查询。
@Test
public void testDeleteExchange(){
Exchange exchange = createTestExchange("testExchange");
dataBaseManager.insertExchange(exchange); //插入数据库
List<Exchange> exchangeList = dataBaseManager.selectAllExchanges(); //查询结果
Assertions.assertEquals(2, exchangeList.size());
Assertions.assertEquals("testExchange", exchangeList.get(1).getName());
//查询俩即可 下面进行删除操作
dataBaseManager.deleteExchange("testExchange");
// 删除完再次查询
exchangeList = dataBaseManager.selectAllExchanges();
Assertions.assertEquals(1, exchangeList.size());
Assertions.assertEquals("", exchangeList.get(0).getName()); //默认为匿名
}
// 构造一个 MSGQueue 对象, 创建一个用来测试的队列
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;
}
//测试队列插入
//构造一个 MSGQueue 对象, 插入到数据库中. 再查询出来, 看结果是否符合预期
@Test
public void testInsertQueue(){
MSGQueue queue = createTestQueue("testQueue");
dataBaseManager.insertQueue(queue); //插入到数据库中
//查询
List<MSGQueue> queueList = dataBaseManager.selectAllQueues();
Assertions.assertEquals(1, queueList.size());
MSGQueue newQueue = queueList.get(0);
Assertions.assertEquals("testQueue", newQueue.getName());
Assertions.assertEquals(true, newQueue.isDurable());
Assertions.assertEquals(false, newQueue.isAutoDelete());
Assertions.assertEquals(false, newQueue.isExclusive());
Assertions.assertEquals(1, newQueue.getArguments("aaa"));
Assertions.assertEquals(2, newQueue.getArguments("bbb"));
}
// 测试队列删除
// 先构造一个队列, 插入数据库,简单查询俩; 再按照名字删除,再查询。
@Test
public void testDeleteQueue() {
MSGQueue queue = createTestQueue("testQueue");
dataBaseManager.insertQueue(queue); //插入数据库
List<MSGQueue> queueList = dataBaseManager.selectAllQueues(); //查询结果
Assertions.assertEquals(1, queueList.size());
Assertions.assertEquals("testQueue", queueList.get(0).getName());
//查询俩即可 下面进行删除操作
dataBaseManager.deleteQueue("testQueue");
// 删除完再次查询
queueList = dataBaseManager.selectAllQueues();
Assertions.assertEquals(0, queueList.size());
}
// 构造一个 Binding 对象, 创建一个用来测试的绑定
private Binding createTestBinding(String exchangeName, String queueName){
Binding binding = new Binding();
binding.setExchangeName(exchangeName);
binding.setQueueName(queueName);
binding.setBindingKey("testBindingKey");
return binding;
}
//测试绑定插入
//构造一个 Binding 对象, 插入到数据库中. 再查询出来, 看结果是否符合预期
@Test
public void testInsertBinding(){
Binding binding = createTestBinding("testExchange", "testQueue");
dataBaseManager.insertBinding(binding);
List<Binding> bindingList = dataBaseManager.selectAllBindings();
Binding newBinding = bindingList.get(0);
Assertions.assertEquals(1, bindingList.size());
Assertions.assertEquals("testExchange", newBinding.getExchangeName());
Assertions.assertEquals("testQueue", newBinding.getQueueName());
Assertions.assertEquals("testBindingKey", newBinding.getBindingKey());
}
// 测试绑定删除
// 先构造一个绑定, 插入数据库,简单查询俩; 再按照名字删除,再查询。
@Test
public void testDeleteBinding(){
Binding binding = createTestBinding
("testExchange", "testQueue");
dataBaseManager.insertBinding(binding);
List<Binding> bindingList = dataBaseManager.selectAllBindings();
Assertions.assertEquals(1, bindingList.size());
// 删除 使用新创建的、属性相同的对象进行删除
// 使用不同的对象进行删除(测试查找逻辑而非对象引用)
Binding toDeleteBinding = createTestBinding
("testExchange", "testQueue");
dataBaseManager.deleteBinding(toDeleteBinding);
bindingList = dataBaseManager.selectAllBindings();
Assertions.assertEquals(0, bindingList.size());
}
}
没有关于断言的信息 就说明没问题
Assertions 工具类 assertEquals 判定结果是不是相等。
也可以修改测试用例中的数据,看一看错误信息,如下。



2.2 MessageFileManager(消息文件管理)
在test目录中,创建MessageFileManagerTest 类。

java
package com.example.mq;
import com.example.mq.common.MqException;
import com.example.mq.mqserver.core.MSGQueue;
import com.example.mq.mqserver.core.Message;
import com.example.mq.mqserver.datacenter.MessageFileManager;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.util.ReflectionTestUtils;
import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;
@SpringBootTest
public class MessageFileManagerTest {
// 创建要测试的实例
private MessageFileManager messageFileManager = new MessageFileManager();
private static final String queueName1 = "testQueue1";
private static final String queueName2 = "testQueue2";
// 这个方法是每个用例执行之前的准备工作
@BeforeEach
public void setUp() throws IOException {
// 准备阶段, 创建出两个队列, 以备后用
messageFileManager.createQueueFile(queueName1);
messageFileManager.createQueueFile(queueName2);
}
// 这个方法就是每个用例执行完毕之后的收尾工作
@AfterEach
public void tearDown() throws IOException {
// 收尾阶段, 就把刚才的队列给干掉.
messageFileManager.destroyQueueFiles(queueName1);
messageFileManager.destroyQueueFiles(queueName2);
}
//创建队列文件已经在上面 setUp 阶段执行过了. 此处主要是验证看看文件是否存在.
//因为有收尾工作,把创建的队列删除了,所以在data文件是空的。
//可以先把收尾工作注释掉 看看目录,测试完记得把收尾工作的注释去掉。
@Test
public void testCreatFiles() {
File queueDataFile1 = new File("./data/" + queueName1 + "/queue_data.txt");
// 使用断言. Assertions 工具类 assertEquals 判定结果是不是相等.
// 第一个形参叫做 expected (预期的), 第二个形参叫做 actual (实际的)
// 调用isFile()方法检查路径是否是文件 如果路径存在且是一个普通文件返回true
Assertions.assertEquals(true, queueDataFile1.isFile());
File queueStatFile1 = new File("./data/" + queueName1 + "/queue_stat.txt");
Assertions.assertEquals(true, queueStatFile1.isFile());
File queueDataFile2 = new File("./data/" + queueName2 + "/queue_data.txt");
Assertions.assertEquals(true, queueDataFile2.isFile());
File queueStatFile2 = new File("./data/" + queueName2 + "/queue_stat.txt");
Assertions.assertEquals(true, queueStatFile2.isFile());
}
// 测试读出/写入消息统计文件
@Test
public void testReadWriteStat() {
// 创建对象 并设置值
MessageFileManager.Stat stat = new MessageFileManager.Stat();
stat.totalCount = 100;
stat.validCount = 50;
// 写入文件中, 调用 writeStat 和 readStat, 但其方法是private, 调用不了。
// 此处需要使用反射的方式调用, Java 原生的反射 API 其实非常难用~~
// 此处使用 Spring 帮我们封装好的 反射 的工具类. ReflectionTestUtils
// invokeMethod--调用方法 参数(实例,调用的方法名,传给方法的参数)
ReflectionTestUtils.invokeMethod(messageFileManager, "writeStat", queueName1, stat);
// 写入完毕之后, 再调用一下读取, 验证读取的结果和写入的数据是一致的.
MessageFileManager.Stat newStat = ReflectionTestUtils.invokeMethod
(messageFileManager, "readStat", queueName1);
Assertions.assertEquals(100, newStat.totalCount);
Assertions.assertEquals(50, newStat.validCount);
System.out.println("测试 readStat 和 writsStat 完成!");
}
//创建一个测试队列对象
private MSGQueue createTestQueue(String queueName) {
MSGQueue queue = new MSGQueue();
queue.setName(queueName);
queue.setDurable(true);
queue.setAutoDelete(false);
queue.setExclusive(false);
return queue;
}
//创建一个测试消息对象 content--内容
private Message createTestMessage(String content) {
Message message = Message.creatMessageWithId
("testRoutingKey", null, content.getBytes());
return message;
}
// 测试发送信息--把新消息放到队列对应的文件
// sendMessage 俩参数(队列,消息) 所以上面要先构造封装出这俩对象
@Test
public void testSendMessage() throws IOException, MqException, ClassNotFoundException {
// 构造出队列和消息
MSGQueue queue = createTestQueue(queueName1);
Message message = createTestMessage("testMessage");
// 此处创建的 queue 对象的 name, 不能随便写,只能用 queueName1 和 queueName2.
// 需要保证这个队列对象对应的目录和文件啥的都存在才行.
// 调用发送消息方法
messageFileManager.sendMessage(queue, message);
// 检查 stat 文件.
// 此处使用 Spring 帮我们封装好的 反射 的工具类. ReflectionTestUtils
// invokeMethod--调用方法 参数(实例,调用的方法名,传给方法的参数)
MessageFileManager.Stat stat = ReflectionTestUtils.invokeMethod
(messageFileManager, "readStat", queueName1);
Assertions.assertEquals(1, stat.totalCount);
Assertions.assertEquals(1, stat.validCount);
// 检查 data 文件
// 先把消息都拿出来, loadAllMessageFromQueue--从文件中, 读取出所有的消息内容, 加载到内存中(链表)
LinkedList<Message> messages = messageFileManager.loadAllMessageFromQueue(queueName1);
// 对比结果是否正确
Assertions.assertEquals(1, messages.size()); //断言:期望只有一个元素
Message curMessage = messages.get(0); //从消息链表中获取第一个(索引为0)消息对象
// message --新创建、准备发送的消息 curMessage--这是从文件系统重新加载的消息 对比一下
Assertions.assertEquals(message.getMessageId(), curMessage.getMessageId());
Assertions.assertEquals(message.getRoutingKey(), curMessage.getRoutingKey());
Assertions.assertEquals(message.getDeliverMode(), curMessage.getDeliverMode());
// 比较两个字节数组的内容是否相同, 不能直接使用 assertEquals 了.
Assertions.assertArrayEquals(message.getBody(), curMessage.getBody());
System.out.println("message: " + curMessage);
}
// 测试loadAllMessageFromQueue--从文件中, 读取出所有的消息内容, 加载到内存中(链表)
// 往队列中插入 100 条消息, 然后验证看看这 100 条消息从文件中读取之后, 是否和最初是一致的.
@Test
public void testLoadAllMessageFromQueue() throws IOException, MqException, ClassNotFoundException {
// 创建队列 需要保证这个队列对象对应的目录和文件啥的都存在才行
MSGQueue queue = createTestQueue(queueName1);
// 为了和后续读取的消息进行对比,需要记录最初的消息 expected--预期的
List<Message> expectedMessages = new LinkedList<>();
for (int i = 0; i < 100; i++) {
// 使每个消息体名字有区别
Message message = createTestMessage("testMessage" + i);
// 发送消息体
messageFileManager.sendMessage(queue, message);
expectedMessages.add(message); //记录最初的消息
}
// 读取所有消息,用链表接收,此时读取的消息是实际的消息 actual--实际
LinkedList<Message> actualMessages = messageFileManager.loadAllMessageFromQueue(queueName1);
// 进行对比 先验证总消息长度是否相同 再通过循环验证每一条消息的具体内容
Assertions.assertEquals(expectedMessages.size(), actualMessages.size());
for (int i = 0; i < expectedMessages.size(); i++) {
Message expectedMessage = expectedMessages.get(i); //取出预期的第i条消息
Message actualMessage = actualMessages.get(i); //取出实际的第i条消息
System.out.println("[" + i + "] actualMessage=" + actualMessage); //打印下标
// 依次对比各个属性
Assertions.assertEquals(expectedMessage.getMessageId(), actualMessage.getMessageId());
Assertions.assertEquals(expectedMessage.getRoutingKey(), actualMessage.getRoutingKey());
Assertions.assertEquals(expectedMessage.getDeliverMode(), actualMessage.getDeliverMode());
Assertions.assertArrayEquals(expectedMessage.getBody(), actualMessage.getBody());
Assertions.assertEquals(0x1, actualMessage.getIsValid()); //有效消息
}
}
// 测试删除消息
// 创建队列, 写入 10 个消息. 删除其中的几个消息. 再把所有消息读取出来, 判定是否符合预期.
@Test
public void testDeleteMessage() throws IOException, MqException, ClassNotFoundException {
MSGQueue queue = createTestQueue(queueName1); //创建队列
List<Message> expectedMessages = new LinkedList<>(); //记录最初预期消息
for (int i = 0; i < 10; i++) {
Message message = createTestMessage("testMessage" + i);
messageFileManager.sendMessage(queue, message); //写入消息
expectedMessages.add(message);
}
// 删除其中的三个消息
messageFileManager.deleteMessage(queue, expectedMessages.get(7));
messageFileManager.deleteMessage(queue, expectedMessages.get(8));
messageFileManager.deleteMessage(queue, expectedMessages.get(9));
//读取所有消息 用链表接收 此处为实际消息
LinkedList<Message> actualMessages = messageFileManager.loadAllMessageFromQueue(queueName1);
// 进行对比 先验证总消息长度是否相同 再通过循环验证每一条消息的具体内容
Assertions.assertEquals(7, actualMessages.size());
for (int i = 0; i < actualMessages.size(); i++) {
Message expectedMessage = expectedMessages.get(i); //取出预期的第i条消息
Message actualMessage = actualMessages.get(i); //取出实际的第i条消息
System.out.println("[" + i + "] actualMessage=" + actualMessage); //打印下标
// 依次对比各个属性
Assertions.assertEquals(expectedMessage.getMessageId(), actualMessage.getMessageId());
Assertions.assertEquals(expectedMessage.getRoutingKey(), actualMessage.getRoutingKey());
Assertions.assertEquals(expectedMessage.getDeliverMode(), actualMessage.getDeliverMode());
Assertions.assertArrayEquals(expectedMessage.getBody(), actualMessage.getBody());
Assertions.assertEquals(0x1, actualMessage.getIsValid()); //有效消息
}
}
// 测试垃圾回收GC
// 先往队列中写 100 个消息. 获取到文件大小.
// 再把 100 个消息中的一半, 都给删除掉(比如把下标为偶数的消息都删除)
// 再手动调用 gc 方法, 检测得到的新的文件的大小是否比之前缩小了.
@Test
public void testGC() throws IOException, MqException, ClassNotFoundException {
MSGQueue queue = createTestQueue(queueName1);
List<Message> expectedMessages = new LinkedList<>();
for (int i = 0; i < 100; i++) {
Message message = createTestMessage("testMessage" + i);
messageFileManager.sendMessage(queue, message);
expectedMessages.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, expectedMessages.get(i));
}
// 手动调用 gc
messageFileManager.gc(queue);
// 重新读取文件, 验证新的文件的内容是不是和之前的内容匹配
LinkedList<Message> actualMessages = messageFileManager.loadAllMessageFromQueue(queueName1);
Assertions.assertEquals(50, actualMessages.size());
for (int i = 0; i < actualMessages.size(); i++) {
// 把之前消息偶数下标的删了, 剩下的就是奇数下标的元素了.
// actual 中的 0 对应 expected 的 1
// actual 中的 1 对应 expected 的 3
// actual 中的 2 对应 expected 的 5
// actual 中的 i 对应 expected 的 2 * i + 1
Message expectedMessage = expectedMessages.get(2 * i + 1);
Message actualMessage = actualMessages.get(i);
Assertions.assertEquals(expectedMessage.getMessageId(), actualMessage.getMessageId());
Assertions.assertEquals(expectedMessage.getRoutingKey(), actualMessage.getRoutingKey());
Assertions.assertEquals(expectedMessage.getDeliverMode(), actualMessage.getDeliverMode());
Assertions.assertArrayEquals(expectedMessage.getBody(), actualMessage.getBody());
Assertions.assertEquals(0x1, actualMessage.getIsValid());
}
// 获取新的文件的大小
File afterGCFile = new File("./data/" + queueName1 + "/queue_data.txt");
long afterGCLength = afterGCFile.length();
System.out.println("before: " + beforeGCLength);
System.out.println("after: " + afterGCLength);
Assertions.assertTrue(beforeGCLength > afterGCLength);
}
}


2.3 MemoryDataCenter(内存数据中心)
在test目录中,创建MemoryDataCenterTests类。

java
package com.example.mq;
import com.example.mq.common.MqException;
import com.example.mq.mqserver.core.*;
import com.example.mq.mqserver.datacenter.DiskDataCenter;
import com.example.mq.mqserver.datacenter.MemoryDataCenter;
import org.apache.tomcat.util.http.fileupload.FileUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
@SpringBootTest
public class MemoryDataCenterTests{
private MemoryDataCenter memoryDataCenter = null;
// 准备工作 setUp--安装、程序 Each--每一个
@BeforeEach
public void setUp(){
//每一次都要创建实例 保证每个测试用例的独立性和隔离性
memoryDataCenter = new MemoryDataCenter();
}
// 收尾工作 tearDown--拆卸
@AfterEach
public void tearDown(){
//销毁实例 保证每个测试用例的独立性和隔离性
memoryDataCenter = null;
}
// 创建一个测试交换机
private Exchange createTestExchange(String exchangeName){
Exchange exchange = new Exchange();
exchange.setName(exchangeName);
exchange.setType(ExchangeType.DIREXT);
exchange.setAutoDelete(false);
exchange.setDurable(true);
return exchange;
}
// 创建一个测试队列
private MSGQueue createTestQueue(String queueName){
MSGQueue queue = new MSGQueue();
queue.setName(queueName);
queue.setDurable(true);
queue.setExclusive(false);
queue.setAutoDelete(false);
return queue;
}
// 针对交换机进行 增 删 查 测试
@Test
public void testExchange(){
// 1. 先构造一个交换机并插入.
Exchange expectedExchange = createTestExchange("testExchange");
memoryDataCenter.insertExchange(expectedExchange);
// 2. 查询出这个交换机, 比较结果是否一致. 此处直接比较这俩引用指向同一个对象.
Exchange actualExchange = memoryDataCenter.getExchange("testExchange");
Assertions.assertEquals(expectedExchange, actualExchange);
// 3. 删除这个交换机
memoryDataCenter.deleteExchange("testExchange");
// 4. 再查一次, 看是否就查不到了
actualExchange = memoryDataCenter.getExchange("testExchange");
Assertions.assertNull(actualExchange); //assertNull 判定是否为空
}
// 针对队列进行 增 删 查 测试
@Test
public void testQueue(){
// 1. 构造一个队列, 并插入
MSGQueue expectedQueue = createTestQueue("testQueue");
memoryDataCenter.insertQueue(expectedQueue);
// 2. 查询这个队列, 并比较
MSGQueue actualQueue = memoryDataCenter.getQueue("testQueue");
Assertions.assertEquals(expectedQueue, actualQueue);
// 3. 删除这个队列
memoryDataCenter.deleteQueue("testQueue");
// 4. 再次查询队列, 看是否能查到
actualQueue = memoryDataCenter.getQueue("teatQueue");
Assertions.assertNull(actualQueue);
}
// 针对绑定进行 增 删 查 测试
@Test
public void testBinding() throws MqException {
// 1. 构造一个绑定, 并插入
Binding expectedBinding = new Binding();
expectedBinding.setQueueName("testQueue");
expectedBinding.setExchangeName("testExchange");
expectedBinding.setBindingKey("testBindingKey");
memoryDataCenter.insertBinding(expectedBinding);
// 2. 查询这个绑定, 并比较
// (1)getBinding
Binding actualBinding = memoryDataCenter.getBinding
("testExchange", "testQueue");
Assertions.assertEquals(expectedBinding, actualBinding);
// (2)getBindings
ConcurrentHashMap<String, Binding> bindingMap = memoryDataCenter.
getBindings("testExchange");
Assertions.assertEquals(1, bindingMap.size());
Assertions.assertEquals(expectedBinding, bindingMap.get("testQueue"));
// 3. 删除这个绑定
memoryDataCenter.deleteBinding(expectedBinding);
// 4. 再次查询绑定, 看是否能查到
actualBinding = memoryDataCenter.getBinding
("testExchange", "testQueue");
Assertions.assertNull(actualBinding);
}
// 创建测试消息 content--内容 信息
private Message createTestMessage(String content){
Message message = Message.creatMessageWithId
("testRoutingKey", null, content.getBytes());
return message;
}
// 针对消息进行 增 删 查 测试
@Test
public void testMessage(){
// 1. 构造一个消息, 并插入
Message exceptedMessage = createTestMessage("testMessage");
memoryDataCenter.addMessage(exceptedMessage);
// 2. 查询这个消息, 并比较
Message actualMessage = memoryDataCenter.getMessage(exceptedMessage.getMessageId());
Assertions.assertEquals(exceptedMessage, actualMessage);
// 3. 删除这个消息
memoryDataCenter.removeMessage(exceptedMessage.getMessageId());
// 4. 再次查询消息, 看是否能查到
actualMessage = memoryDataCenter.getMessage(exceptedMessage.getMessageId());
Assertions.assertNull(actualMessage);
}
// 针对消息发送进行测试
@Test
public void testSendMessage(){
// 1. 创建一个队列, 创建 10 条消息, 把这些消息都插入队列中
MSGQueue queue = createTestQueue("testQueue");
List<Message> expectedMessages = new ArrayList<>(); //记录初始消息
for (int i = 0; i < 10; i++){
Message message = createTestMessage("testMessage" + i);
memoryDataCenter.sendMessage(queue, message);
expectedMessages.add(message);
}
// 2. 从队列中取出这些消息.
List<Message> actualMessages =new ArrayList<>();
while (true) {
Message message = memoryDataCenter.pollMessage("testQueue");
if (message == null) { break; }
actualMessages.add(message);
}
// 3. 比较取出的消息和之前的消息是否一致.
Assertions.assertEquals(expectedMessages.size(), actualMessages.size());
for(int i = 0; i < expectedMessages.size(); i++){
Assertions.assertEquals(expectedMessages.get(i), actualMessages.get(i));
}
}
// 针对未被确认的消息进行测试
@Test
public void testMessageWaitAck(){
// 1. 构造一个消息, 并插入
Message expectedMessage = createTestMessage("expectedMessage");
memoryDataCenter.addMessageWaitAck("testQueue", expectedMessage);
// 2. 查询这个消息, 并比较
Message actualMessage = memoryDataCenter.getMessageWaitAck
("testQueue", expectedMessage.getMessageId());
Assertions.assertEquals(expectedMessage, actualMessage);
// 3. 删除这个消息
memoryDataCenter.removeMessageWaitAck("testQueue",
expectedMessage.getMessageId());
// 4. 再次查询消息, 看是否能查到
actualMessage = memoryDataCenter.getMessageWaitAck
("testQueue", expectedMessage.getMessageId());
Assertions.assertNull(actualMessage);
}
// 针对从硬盘上读取数据进行测试
// [从硬盘上读取数据--把硬盘中之前持久化存储的各个维度的数据都恢复到内存中.]
@Test
public void testRecovery() throws IOException, MqException, ClassNotFoundException {
// 由于后续需要进行数据库操作, 依赖 MyBatis.
// 就需要先启动 SpringApplication, 这样才能进行后续的数据库操作.
MqApplication.context = SpringApplication.run(MqApplication.class);
// 1. 在硬盘上构造好数据
DiskDataCenter diskDataCenter = new DiskDataCenter();
diskDataCenter.init();
// 构造交换机
Exchange expectedExchange = createTestExchange("testExchange");
diskDataCenter.insertExchange(expectedExchange);
// 构造队列
MSGQueue expectedQueue = createTestQueue("testQueue");
diskDataCenter.insertQueue(expectedQueue);
// 构造绑定
Binding expectedBinding = new Binding();
expectedBinding.setExchangeName("testExchange");
expectedBinding.setQueueName("testQueue");
expectedBinding.setBindingKey("testBindingKey");
diskDataCenter.insertBinding(expectedBinding);
// 构造消息
Message expectedMessage = createTestMessage("testContent");
diskDataCenter.sendMessage(expectedQueue, expectedMessage);
// 2. 执行恢复操作--把硬盘中之前持久化存储的各个维度的数据都恢复到内存中.
memoryDataCenter.recovery(diskDataCenter);
// 3. 从内存中查询数据 对比结果
Exchange actualExchange = memoryDataCenter.getExchange("testExchange");
Assertions.assertEquals(expectedExchange.getName(), actualExchange.getName());
Assertions.assertEquals(expectedExchange.getType(), actualExchange.getType());
Assertions.assertEquals(expectedExchange.isDurable(), actualExchange.isDurable());
Assertions.assertEquals(expectedExchange.isAutoDelete(), actualExchange.isAutoDelete());
MSGQueue actualQueue = memoryDataCenter.getQueue("testQueue");
Assertions.assertEquals(expectedQueue.getName(), actualQueue.getName());
Assertions.assertEquals(expectedQueue.isDurable(), actualQueue.isDurable());
Assertions.assertEquals(expectedQueue.isAutoDelete(), actualQueue.isAutoDelete());
Assertions.assertEquals(expectedQueue.isExclusive(), actualQueue.isExclusive());
Binding actualBinding = memoryDataCenter.getBinding("testExchange", "testQueue");
Assertions.assertEquals(expectedBinding.getExchangeName(), actualBinding.getExchangeName());
Assertions.assertEquals(expectedBinding.getQueueName(), actualBinding.getQueueName());
Assertions.assertEquals(expectedBinding.getBindingKey(), actualBinding.getBindingKey());
Message actualMessage = memoryDataCenter.pollMessage("testQueue");
Assertions.assertEquals(expectedMessage.getMessageId(), actualMessage.getMessageId());
Assertions.assertEquals(expectedMessage.getRoutingKey(), actualMessage.getRoutingKey());
Assertions.assertEquals(expectedMessage.getDeliverMode(), actualMessage.getDeliverMode());
Assertions.assertArrayEquals(expectedMessage.getBody(), actualMessage.getBody());
// 4. 清理硬盘的数据, 把整个 data 目录里的内容都删掉(包含了 meta.db 和 队列的目录).
MqApplication.context.close(); //关闭 Spring 应用上下文
File dataDir = new File("./data"); //创建一个 File 对象,表示当前工作目录下的 data 目录。
FileUtils.deleteDirectory(dataDir); //递归删除整个 data 目录及其所有内容。
}
}
2.4 route(交换机转发规则)
在test目录中,创建RouterTests类。
java
package com.example.mq;
import com.example.mq.common.MqException;
import com.example.mq.mqserver.core.Binding;
import com.example.mq.mqserver.core.ExchangeType;
import com.example.mq.mqserver.core.Message;
import com.example.mq.mqserver.core.Router;
import com.example.mq.mqserver.datacenter.MemoryDataCenter;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class RouterTests {
private Router router = new Router();
private Binding binding = null;
private Message message = null;
// 准备工作 setUp--安装、程序 Each--每一个
@BeforeEach
public void setup(){
binding = new Binding();
message = new Message();
}
// 收尾工作 tearDown--拆卸
@AfterEach
public void tearDown(){
binding = null;
message = null;
}
// [测试用例]
// binding key routing key result
// aaa aaa true
// aaa.bbb aaa.bbb true
// aaa.bbb aaa.bbb.ccc false
// aaa.bbb aaa.ccc false
// aaa.bbb.ccc aaa.bbb.ccc true
// aaa.* aaa.bbb true
// aaa.*.bbb aaa.bbb.ccc false
// *.aaa.bbb aaa.bbb false
// # aaa.bbb.ccc true
// aaa.# aaa.bbb true
// aaa.# aaa.bbb.ccc true
// aaa.#.ccc aaa.ccc true
// aaa.#.ccc aaa.bbb.ccc true
// aaa.#.ccc aaa.aaa.bbb.ccc true
// #.ccc ccc true
// #.ccc aaa.bbb.ccc true
@Test
public void test1() throws MqException {
binding.setBindingKey("aaa");
message.setRoutingKey("aaa");
Assertions.assertTrue(router.route(ExchangeType.TOPIC, binding, message));
}
@Test
public void test2() throws MqException {
binding.setBindingKey("aaa.bbb");
message.setRoutingKey("aaa.bbb");
Assertions.assertTrue(router.route(ExchangeType.TOPIC, binding, message));
}
@Test
public void test3() throws MqException {
binding.setBindingKey("aaa.bbb");
message.setRoutingKey("aaa.bbb.ccc");
Assertions.assertFalse(router.route(ExchangeType.TOPIC, binding, message));
}
@Test
public void test4() throws MqException {
binding.setBindingKey("aaa.bbb");
message.setRoutingKey("aaa.ccc");
Assertions.assertFalse(router.route(ExchangeType.TOPIC, binding, message));
}
@Test
public void test5() throws MqException {
binding.setBindingKey("aaa.bbb.ccc");
message.setRoutingKey("aaa.bbb.ccc");
Assertions.assertTrue(router.route(ExchangeType.TOPIC, binding, message));
}
@Test
public void test6() throws MqException {
binding.setBindingKey("aaa.*");
message.setRoutingKey("aaa.bbb");
Assertions.assertTrue(router.route(ExchangeType.TOPIC, binding, message));
}
@Test
public void test7() throws MqException {
binding.setBindingKey("aaa.*.bbb");
message.setRoutingKey("aaa.bbb.ccc");
Assertions.assertFalse(router.route(ExchangeType.TOPIC, binding, message));
}
@Test
public void test8() throws MqException {
binding.setBindingKey("*.aaa.bbb");
message.setRoutingKey("aaa.bbb");
Assertions.assertFalse(router.route(ExchangeType.TOPIC, binding, message));
}
@Test
public void test9() throws MqException {
binding.setBindingKey("#");
message.setRoutingKey("aaa.bbb.ccc");
Assertions.assertTrue(router.route(ExchangeType.TOPIC, binding, message));
}
@Test
public void test10() throws MqException {
binding.setBindingKey("aaa.#");
message.setRoutingKey("aaa.bbb");
Assertions.assertTrue(router.route(ExchangeType.TOPIC, binding, message));
}
@Test
public void test11() throws MqException {
binding.setBindingKey("aaa.#");
message.setRoutingKey("aaa.bbb.ccc");
Assertions.assertTrue(router.route(ExchangeType.TOPIC, binding, message));
}
@Test
public void test12() throws MqException {
binding.setBindingKey("aaa.#.ccc");
message.setRoutingKey("aaa.ccc");
Assertions.assertTrue(router.route(ExchangeType.TOPIC, binding, message));
}
@Test
public void test13() throws MqException {
binding.setBindingKey("aaa.#.ccc");
message.setRoutingKey("aaa.bbb.ccc");
Assertions.assertTrue(router.route(ExchangeType.TOPIC, binding, message));
}
@Test
public void test14() throws MqException {
binding.setBindingKey("aaa.#.ccc");
message.setRoutingKey("aaa.aaa.bbb.ccc");
Assertions.assertTrue(router.route(ExchangeType.TOPIC, binding, message));
}
@Test
public void test15() throws MqException {
binding.setBindingKey("#.ccc");
message.setRoutingKey("ccc");
Assertions.assertTrue(router.route(ExchangeType.TOPIC, binding, message));
}
@Test
public void test16() throws MqException {
binding.setBindingKey("#.ccc");
message.setRoutingKey("aaa.bbb.ccc");
Assertions.assertTrue(router.route(ExchangeType.TOPIC, binding, message));
}
}
2.5 VirtualHost(虚拟主机)
在test目录中,创建VirtualHostTests类。
java
package com.example.mq;
import com.example.mq.common.Consumer;
import com.example.mq.mqserver.VirtualHost;
import com.example.mq.mqserver.core.BasicProperties;
import com.example.mq.mqserver.core.ExchangeType;
import org.apache.tomcat.util.http.fileupload.FileUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.File;
import java.io.IOException;
@SpringBootTest
public class VirtualHostTests {
private VirtualHost virtualHost = null;
@BeforeEach
public void setUp() {
MqApplication.context = SpringApplication.run(MqApplication.class);
virtualHost = new VirtualHost("default"); //创建虚拟主机对象
}
@AfterEach
public void tearDown() throws IOException {
MqApplication.context.close(); //关闭context
virtualHost = null;
File dataDir = new File("./data");
FileUtils.deleteDirectory(dataDir); // 把硬盘的目录删除掉
}
@Test // 测试创建交换机
public void testExchangeDeclare() {
boolean ok = virtualHost.exchangeDeclare("testExchange", ExchangeType.DIREXT,
true, false, null);
Assertions.assertTrue(ok); //判断创建是否成功
}
@Test // 测试删除交换机
public void testExchangeDelete() {
boolean ok = virtualHost.exchangeDeclare("testExchange", ExchangeType.DIREXT,
true, false, null);
Assertions.assertTrue(ok); //判断创建是否成功
ok = virtualHost.exchangeDelete("testExchange");
Assertions.assertTrue(ok); //判断删除是否成功
}
@Test // 测试创建队列
public void testQueueDeclare() {
boolean ok = virtualHost.queueDeclare("testQueue", true,
false, false, null);
Assertions.assertTrue(ok); //判断创建是否成功
}
@Test // 测试删除队列
public void testQueueDelete() {
boolean ok = virtualHost.queueDeclare("testQueue", true,
false, false, null);
Assertions.assertTrue(ok); //判断创建是否成功
ok = virtualHost.queueDelete("testQueue");
Assertions.assertTrue(ok); //判断删除是否成功
}
@Test // 测试创建绑定
public void testQueueBind() {
boolean ok = virtualHost.queueDeclare("testQueue", true,
false, false, null);
Assertions.assertTrue(ok); //判断创建队列是否成功
ok = virtualHost.exchangeDeclare("testExchange", ExchangeType.DIREXT,
true, false, null);
Assertions.assertTrue(ok); //判断创建交换机是否成功
ok = virtualHost.queueBind("testQueue", "testExchange",
"testBindingKey");
Assertions.assertTrue(ok); //判断创建绑定是否成功
}
@Test // 测试删除绑定
public void testQueueUnbind() {
boolean ok = virtualHost.queueDeclare("testQueue", true,
false, false, null);
Assertions.assertTrue(ok); //判断创建队列是否成功
ok = virtualHost.exchangeDeclare("testExchange", ExchangeType.DIREXT,
true, false, null);
Assertions.assertTrue(ok); //判断创建交换机是否成功
ok = virtualHost.queueBind("testQueue", "testExchange",
"testBindingKey");
Assertions.assertTrue(ok); //判断创建绑定是否成功
ok = virtualHost.queueUnbind("testQueue", "testExchange");
Assertions.assertTrue(ok); //判断删除绑定是否成功
}
@Test //测试 发送消息到指定的交换机/队列中
public void testBasicPublish() {
boolean ok = virtualHost.queueDeclare("testQueue", true,
false, false, null);
Assertions.assertTrue(ok); //判断创建队列是否成功
ok = virtualHost.exchangeDeclare("testExchange", ExchangeType.DIREXT,
true, false, null);
Assertions.assertTrue(ok); //判断创建交换机是否成功
ok = virtualHost.basicPublish("testExchange", "testQueue",
null, "hello".getBytes()); //发送消息到指定的交换机/队列中
Assertions.assertTrue(ok); //判断 发送消息到指定的交换机/队列中 是否成功
}
// 测试订阅消息 直接交换机
// 1.先订阅队列, 后发送消息
@Test
public void testBasicConsume1() throws InterruptedException {
boolean ok = virtualHost.queueDeclare("testQueue", true,
false, false, null);
Assertions.assertTrue(ok); //判断创建队列是否成功
ok = virtualHost.exchangeDeclare("testExchange", ExchangeType.DIREXT,
true, false, null);
Assertions.assertTrue(ok); //判断创建交换机是否成功
// 先订阅队列
ok = virtualHost.basicConsume("testConsumerTag",
"testQueue", true, new Consumer() {
@Override
public void handleDelivery(String consumerTag, BasicProperties basicProperties, byte[] body) {
// 上面方法中的三个参数代表 实际 接收到的值,
// 即 RabbitMQ 服务器传递给消费者的真实数据,它们是在消息到达时由框架自动填充的,
// 下面是消费者自身设定的回调方法
try {
// 先打印看看 实际接收到的值 消息中的内容
System.out.println("messageId=" + basicProperties.getMessageId());
System.out.println("body=" + new String(body, 0, body.length));
// 验证预期与实际是否一样,
// 即确保生产者发送的消息能被消费者正确接收,且消息的属性与内容均与发送时一致
// 预期值来源于测试数据(如硬编码的 "testQueue"、1、"hello"),实际值则来自 RabbitMQ 运行时的真实输出。
Assertions.assertEquals("testQueue", basicProperties.getRoutingKey());
Assertions.assertEquals(1, basicProperties.getDeliverMode());
Assertions.assertArrayEquals("hello".getBytes(), body); // assertArrayEquals--专用于数组比较
} catch (Error e) {
// 断言如果失败, 抛出的是 Error, 而不是 Exception!
e.printStackTrace();
System.out.println("error");
}
}
});
Assertions.assertTrue(ok);
Thread.sleep(500); // 确保前者彻底执行完
// 再发送消息到指定的交换机/队列中
ok = virtualHost.basicPublish("testExchange", "testQueue",
null, "hello".getBytes());
Assertions.assertTrue(ok);
}
// 2.先发送消息, 后订阅队列. 和上述方法差不多
@Test
public void testBasicConsume2() throws InterruptedException {
boolean ok = virtualHost.queueDeclare("testQueue", true,
false, false, null);
Assertions.assertTrue(ok); //判断创建队列是否成功
ok = virtualHost.exchangeDeclare("testExchange", ExchangeType.DIREXT,
true, false, null);
Assertions.assertTrue(ok); //判断创建交换机是否成功
// 先发送消息到指定的交换机/队列中
ok = virtualHost.basicPublish("testExchange", "testQueue",
null, "hello".getBytes());
Assertions.assertTrue(ok); //判断 发送消息到指定的交换机/队列中 是否成功
// 再订阅队列
ok = virtualHost.basicConsume("testConsumerTag", "testQueue",
true, new Consumer() {
@Override
public void handleDelivery(String consumerTag, BasicProperties basicProperties, byte[] body) {
System.out.println("messageId=" + basicProperties.getMessageId());
System.out.println("body=" + new String(body, 0, body.length));
Assertions.assertEquals("testQueue", basicProperties.getRoutingKey());
Assertions.assertEquals(1, basicProperties.getDeliverMode());
Assertions.assertArrayEquals("hello".getBytes(), body);
}
});
Assertions.assertTrue(ok);
Thread.sleep(500);
}
// 测试订阅消息 扇出交换机
// 先发送两个消息, 后订阅两个队列.
@Test
public void testBasicConsumeFanout() throws InterruptedException {
boolean ok = virtualHost.exchangeDeclare("testExchange", ExchangeType.FANOUT,
false, false, null);
Assertions.assertTrue(ok); //判断创建交换机是否成功
ok = virtualHost.queueDeclare("testQueue1",
false, false, false, null);
Assertions.assertTrue(ok); //判断创建队列1是否成功
ok = virtualHost.queueBind("testQueue1", "testExchange", "");
Assertions.assertTrue(ok); //判断创建绑定1是否成功
ok = virtualHost.queueDeclare("testQueue2",
false, false, false, null);
Assertions.assertTrue(ok); //判断创建队列2是否成功
ok = virtualHost.queueBind("testQueue2", "testExchange", "");
Assertions.assertTrue(ok); //判断创建绑定2是否成功
// 往交换机中发布一个消息 扇出交换机中 routingKey 为空即可
ok = virtualHost.basicPublish("testExchange", "",
null, "hello".getBytes());
Assertions.assertTrue(ok); //判断 发送消息到指定的交换机/队列中 是否成功
Thread.sleep(500);
// 两个消费者订阅上述的两个队列.
ok = virtualHost.basicConsume("testConsumer1", "testQueue1",
true, new Consumer() {
@Override
public void handleDelivery(String consumerTag, BasicProperties basicProperties, byte[] body) {
System.out.println("consumerTag=" + consumerTag);
System.out.println("messageId=" + basicProperties.getMessageId());
Assertions.assertArrayEquals("hello".getBytes(), body);
}
});
Assertions.assertTrue(ok);
ok = virtualHost.basicConsume("testConsumer2", "testQueue2",
true, new Consumer() {
@Override
public void handleDelivery(String consumerTag, BasicProperties basicProperties, byte[] body) {
System.out.println("consumerTag=" + consumerTag);
System.out.println("messageId=" + basicProperties.getMessageId());
Assertions.assertArrayEquals("hello".getBytes(), body);
}
});
Assertions.assertTrue(ok);
Thread.sleep(500);
}
// 测试订阅消息 主题交换机
// 先发送消息, 后订阅队列.
@Test
public void testBasicConsumeTopic() throws InterruptedException {
boolean ok = virtualHost.exchangeDeclare("testExchange", ExchangeType.TOPIC,
false, false, null);
Assertions.assertTrue(ok);
ok = virtualHost.queueDeclare("testQueue",
false, false, false, null);
Assertions.assertTrue(ok);
// 主题交换机 绑定中要指定 bindingKey aaa.*.bbb
ok = virtualHost.queueBind("testQueue", "testExchange",
"aaa.*.bbb");
Assertions.assertTrue(ok);
// 先发送消息 主题交换机指定 routingKey aaa.ccc.bbb
ok = virtualHost.basicPublish("testExchange", "aaa.ccc.bbb",
null, "hello".getBytes());
Assertions.assertTrue(ok);
ok = virtualHost.basicConsume("testConsumer", "testQueue",
true, new Consumer() {
@Override
public void handleDelivery(String consumerTag, BasicProperties basicProperties, byte[] body) {
System.out.println("consumerTag=" + consumerTag);
System.out.println("messageId=" + basicProperties.getMessageId());
Assertions.assertArrayEquals("hello".getBytes(), body);
}
});
Assertions.assertTrue(ok);
Thread.sleep(500);
}
@Test // 测试 "手动应答"
public void testBasicAck() throws InterruptedException {
boolean ok = virtualHost.queueDeclare("testQueue", true,
false, false, null);
Assertions.assertTrue(ok);
ok = virtualHost.exchangeDeclare("testExchange", ExchangeType.DIREXT,
true, false, null);
Assertions.assertTrue(ok);
// 先发送消息
ok = virtualHost.basicPublish("testExchange", "testQueue",
null, "hello".getBytes());
Assertions.assertTrue(ok);
// 再订阅队列 [要改的地方, 把 autoAck 改成 false]
ok = virtualHost.basicConsume("testConsumerTag", "testQueue",
false, new Consumer() {
@Override
public void handleDelivery(String consumerTag, BasicProperties basicProperties, byte[] body) {
// 消费者自身设定的回调方法.
System.out.println("messageId=" + basicProperties.getMessageId());
System.out.println("body=" + new String(body, 0, body.length));
Assertions.assertEquals("testQueue", basicProperties.getRoutingKey());
Assertions.assertEquals(1, basicProperties.getDeliverMode());
Assertions.assertArrayEquals("hello".getBytes(), body);
// [要改的地方, 新增手动调用 basicAck]
boolean ok = virtualHost.basicAck("testQueue", basicProperties.getMessageId());
Assertions.assertTrue(ok);
}
});
Assertions.assertTrue(ok);
Thread.sleep(500);
}
}
2.6 客户端-服务器
在test目录中,创建MqClient Tests类。
java
package com.example.mq;
import com.example.mq.common.Consumer;
import com.example.mq.common.MqException;
import com.example.mq.mqclient.Channel;
import com.example.mq.mqclient.Connection;
import com.example.mq.mqclient.ConnectionFactory;
import com.example.mq.mqserver.BrokerServer;
import com.example.mq.mqserver.core.BasicProperties;
import com.example.mq.mqserver.core.ExchangeType;
import org.apache.tomcat.util.http.fileupload.FileUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.SpringApplication;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class MqClientTests {
private BrokerServer brokerServer = null; //服务器
private ConnectionFactory factory = null; //客户端
private Thread t = null;
@BeforeEach
public void setUp() throws IOException {
// 1. 先启动服务器
MqApplication.context = SpringApplication.run(MqApplication.class);
brokerServer = new BrokerServer(9090);
t = new Thread(() -> {
// 这个 start 方法会进入一个死循环. 使用一个新的线程来运行 start 即可!
try {
brokerServer.start();
} catch (IOException e) {
e.printStackTrace();
}
});
t.start();
// 2. 配置 ConnectionFactory
factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setPort(9090);
}
@AfterEach
public void tearDown() throws IOException {
brokerServer.stop(); // 停止服务器
// t.join();
MqApplication.context.close();
// 删除必要的文件
File file = new File("./data");
FileUtils.deleteDirectory(file);
factory = null;
}
@Test
public void testConnection() throws IOException {
Connection connection = factory.newConnection();
Assertions.assertNotNull(connection);
}
@Test
public void testChannel() throws IOException {
Connection connection = factory.newConnection();
Assertions.assertNotNull(connection);
Channel channel = connection.createChannel();
Assertions.assertNotNull(channel);
}
@Test
public void testExchange() throws IOException {
Connection connection = factory.newConnection();
Assertions.assertNotNull(connection);
Channel channel = connection.createChannel();
Assertions.assertNotNull(channel);
boolean ok = channel.exchangeDeclare("testExchange",
ExchangeType.DIREXT, true, false, null);
Assertions.assertTrue(ok);
ok = channel.exchangeDelete("testExchange");
Assertions.assertTrue(ok);
// 此处稳妥起见, 把改关闭的要进行关闭.
channel.close();
connection.close();
}
@Test
public void testQueue() throws IOException {
Connection connection = factory.newConnection();
Assertions.assertNotNull(connection);
Channel channel = connection.createChannel();
Assertions.assertNotNull(channel);
boolean ok = channel.queueDeclare("testQueue",
true, false, false, null);
Assertions.assertTrue(ok);
ok = channel.queueDelete("testQueue");
Assertions.assertTrue(ok);
channel.close();
connection.close();
}
@Test
public void testBinding() throws IOException {
Connection connection = factory.newConnection();
Assertions.assertNotNull(connection);
Channel channel = connection.createChannel();
Assertions.assertNotNull(channel);
boolean ok = channel.exchangeDeclare("testExchange",
ExchangeType.DIREXT, true, false, null);
Assertions.assertTrue(ok);
ok = channel.queueDeclare("testQueue", true,
false, false, null);
Assertions.assertTrue(ok);
ok = channel.queueBind("testQueue",
"testExchange", "testBindingKey");
Assertions.assertTrue(ok);
ok = channel.queueUnbind("testQueue", "testExchange");
Assertions.assertTrue(ok);
channel.close();
connection.close();
}
@Test
public void testMessage() throws IOException, MqException, InterruptedException {
Connection connection = factory.newConnection();
Assertions.assertNotNull(connection);
Channel channel = connection.createChannel();
Assertions.assertNotNull(channel);
boolean ok = channel.exchangeDeclare("testExchange", ExchangeType.DIREXT,
true, false, null);
Assertions.assertTrue(ok);
ok = channel.queueDeclare("testQueue", true,
false, false, null);
Assertions.assertTrue(ok);
byte[] requestBody = "hello".getBytes();
ok = channel.basicPublish("testExchange", "testQueue",
null, requestBody);
Assertions.assertTrue(ok);
ok = channel.basicConsume("testQueue", true, new Consumer() {
@Override
public void handleDelivery(String consumerTag, BasicProperties basicProperties,
byte[] body) throws MqException, IOException {
System.out.println("[消费数据] 开始!");
System.out.println("consumerTag=" + consumerTag);
System.out.println("basicProperties=" + basicProperties);
Assertions.assertArrayEquals(requestBody, body);
System.out.println("[消费数据] 结束!");
}
});
Assertions.assertTrue(ok);
Thread.sleep(500);
channel.close();
connection.close();
}
}
