VeloQueue-测试报告

目录

一、项目背景

[二、核心 API](#二、核心 API)

[2.1 Broker](#2.1 Broker)

[2.2 交换机类型(Exchange Type)](#2.2 交换机类型(Exchange Type))

[2.3 持久化](#2.3 持久化)

[2.4 网络通信](#2.4 网络通信)

[2.5 客户端操作 API](#2.5 客户端操作 API)

[2.6 Connection 和 Channel 的关系](#2.6 Connection 和 Channel 的关系)

三、模块划分

四、功能测试

[4.1 数据库封装信息测试](#4.1 数据库封装信息测试)

[4.1.0 前期准备](#4.1.0 前期准备)

[4.1.1 初始化表断言测试](#4.1.1 初始化表断言测试)

[4.1.2 插入交换机断言测试](#4.1.2 插入交换机断言测试)

[4.1.3 删除交换机断言测试](#4.1.3 删除交换机断言测试)

[4.1.4 创造绑定断言测试](#4.1.4 创造绑定断言测试)

[4.1.5 删除绑定断言测试](#4.1.5 删除绑定断言测试)

[4.2 文件硬盘管理测试](#4.2 文件硬盘管理测试)

[4.2.0 前期准备](#4.2.0 前期准备)

[4.2.1 创建文件断言测试](#4.2.1 创建文件断言测试)

[4.2.2 读取文件数量断言测试](#4.2.2 读取文件数量断言测试)

[4.2.3 发送消息断言测试](#4.2.3 发送消息断言测试)

[4.2.4 获取文件所有数据断言测试](#4.2.4 获取文件所有数据断言测试)

[4.2.5 删除文件银盘消息断言测试](#4.2.5 删除文件银盘消息断言测试)

[4.2.6 文件垃圾回收断言测试](#4.2.6 文件垃圾回收断言测试)

[4.3 内存管理信息测试](#4.3 内存管理信息测试)

[4.3.0 前期准备](#4.3.0 前期准备)

[4.3.1 交换机创建删除断言测试](#4.3.1 交换机创建删除断言测试)

[4.3.2 队列创建删除断言测试](#4.3.2 队列创建删除断言测试)

[4.3.3 绑定创建删除断言测试](#4.3.3 绑定创建删除断言测试)

[4.3.4 消息创建删除断言测试](#4.3.4 消息创建删除断言测试)

[4.3.5 发送消息断言测试](#4.3.5 发送消息断言测试)

[4.3.6 未应答消息断言测试](#4.3.6 未应答消息断言测试)

[4.3.7 重启断言测试](#4.3.7 重启断言测试)

[4.4 转发规则断言测试](#4.4 转发规则断言测试)

[4.5 虚拟主机断言测试](#4.5 虚拟主机断言测试)

[4.5.0 前期准备](#4.5.0 前期准备)

[4.5.1 发送消息断言测试](#4.5.1 发送消息断言测试)

[4.5.2 消费者订阅消息先后顺序断言测试](#4.5.2 消费者订阅消息先后顺序断言测试)

[4.5.3 FANOUT 交换机订阅消费断言测试](#4.5.3 FANOUT 交换机订阅消费断言测试)

[4.5.4 TOPIC 交换机订阅消费断言测试](#4.5.4 TOPIC 交换机订阅消费断言测试)

[4.5.5 手动应答消息断言测试](#4.5.5 手动应答消息断言测试)

[4.5.6 交换机队列绑定创建上面的已经包括了](#4.5.6 交换机队列绑定创建上面的已经包括了)

[4.6 客户端断言测试](#4.6 客户端断言测试)

[4.6.0 前期准备](#4.6.0 前期准备)

[4.6.1 创建连接和信道断言测试](#4.6.1 创建连接和信道断言测试)

[4.6.2 交换机请求和删除断言测试](#4.6.2 交换机请求和删除断言测试)

[4.6.3 队列请求和删除断言测试](#4.6.3 队列请求和删除断言测试)

[4.6.4 绑定请求和删除断言测试](#4.6.4 绑定请求和删除断言测试)

[4.6.5 消息发送与订阅断言测试](#4.6.5 消息发送与订阅断言测试)

[4.7 创建服务器和客户端模拟测试](#4.7 创建服务器和客户端模拟测试)

[4.7.1 创建消费者类](#4.7.1 创建消费者类)

[4.7.2 创建生产者类](#4.7.2 创建生产者类)

[4.7.3 打开服务器, 并运行这两个类](#4.7.3 打开服务器, 并运行这两个类)


一、项目背景

在我学习到阻塞队列(BlockingQueue) , 我了解到, 阻塞队列最大的用途, 就是⽤来实现**⽣产者消费者模型**

⽣产者消费者模型是后端开发的常用编程方式, 存在一些优点

  • 解耦合

  • 削峰填谷

在实际的后端开发中,尤其是分布式系统里,跨主机之间使用生产者消费者模型,也是非常普遍的需求。

因此,我们通常会把阻塞队列,封装成一个独立的服务器程序,并且赋予其更丰富的功能.

这样的程序我们就称为消息队列(MessageQueue,MQ)

所以来仿照一下 RabbitMQ, 实现一个简单的队列

源码: https://github.com/QiLuNuo928/mq_/tree/main/src/main

N 个 生产者, N 个消费者:

二、核心 API

2.1 Broker

对于 Broker 来说,要实现以下核心 API。通过这些 API 来实现消息队列的基本功能。

  1. 创建队列 (queueDeclare)

  2. 销毁队列 (queueDelete)

  3. 创建交换机 (exchangeDeclare)

  4. 销毁交换机 (exchangeDelete)

  5. 创建绑定 (queueBind)

  6. 解除绑定 (queueUnbind)

  7. 发布消息 (basicPublish)

  8. 订阅消息 (basicConsume)

  9. 确认消息 (basicAck)

另一方面,Producer 和 Consumer 则通过网络的方式,远程调用这些 API,实现生产者消费者模型。

2.2 交换机类型**(Exchange Type)**

对于 RabbitMQ 来说,主要支持四种交换机类型:

  • Direct

  • Fanout

  • Topic

  • Header

其中 Header 这种方式比较复杂,比较少见。常用的是前三种交换机类型。哈们此处也主要实现这三种。

2.3 持久化

保证重启内容不丢失

2.4 网络通信

  • 生产者和消费者都是客户端程序,Broker 作为服务器,通过网络进行通信。

  • 在网络通信过程中,客户端需要提供对应的 API 来实现对服务器的操作

2.5 客户端操作 API

  1. 创建 Connection

  2. 关闭 Connection

  3. 创建 Channel

  4. 关闭 Channel

  5. 创建队列 (queueDeclare)

  6. 销毁队列 (queueDelete)

  7. 创建交换机 (exchangeDeclare)

  8. 销毁交换机 (exchangeDelete)

  9. 创建绑定 (queueBind)

  10. 解除绑定 (queueUnbind)

  11. 发布消息 (basicPublish)

  12. 订阅消息 (basicConsume)

  13. 确认消息 (basicAck)

2.6 Connection 和 Channel 的关系

  • Connection 对应一个 TCP 连接。

  • Channel 是 Connection 中的逻辑通道。

  • 一个 Connection 中可以包含多个 Channel。

三、模块划分

四、功能测试

4.1 数据库封装信息测试

4.1.0 前期准备

我们将交换机队列绑定存储在 sqlite 中, 主要测试增删改查以及初始化的数据库建表操作

java 复制代码
@BeforeEach
public void setUp(){
    //由于在init中, 需要 context 对象拿到 metaMapper 实例
    MqApplication.context = SpringApplication.run(MqApplication.class);
    dataBaseManger.init();
}

@AfterEach
public void tearDown(){
    //此处不能直接删除, 需要关闭上面的context对象
    //此处context对象持有, MetaMapper的实例化对象, 而MetaMapper又打开了数据库链接
    MqApplication.context.close();
    dataBaseManger.deleteDB();
}

通过 setUp 和 tearDown 来每次测试自动删除表和断开数据库连接

4.1.1 初始化表断言测试

java 复制代码
@Test
public void testInitTable(){
    List<Exchange> exchangeList = dataBaseManger.selectAllExchanges();
    List<MSGQueue> msgQueueList = dataBaseManger.selectAllMSGQueues();
    List<Binding> bindingList = dataBaseManger.selectAllBindings();

    //使用断言, 这两个参数的顺序, 无所谓
    //但是第一个形参叫做 期望的 expected, 第一个形参叫做 实际的 actual
    Assertions.assertEquals(1,exchangeList.size());
    Assertions.assertEquals("",exchangeList.get(0).getName());
    Assertions.assertEquals(ExchangeType.DIRECT,exchangeList.get(0).getType());
    Assertions.assertEquals(0,msgQueueList.size());
    Assertions.assertEquals(0, bindingList.size());
}

4.1.2 插入交换机断言测试

java 复制代码
@Test
public void insertExchangeTest(){
    //构造一个exchange 插入查询观察是否相同
    Exchange exchange = createExchangeTest("testExchange");
    dataBaseManger.insertExchange(exchange);
    List<Exchange> exchangeList = dataBaseManger.selectAllExchanges();
    Assertions.assertEquals(2,exchangeList.size());
    Assertions.assertEquals("testExchange",exchangeList.get(1).getName());
    Assertions.assertEquals(ExchangeType.DIRECT,exchangeList.get(1).getType());
    Assertions.assertEquals(true,exchangeList.get(1).isDurable());
    Assertions.assertEquals(false,exchangeList.get(1).isAutoDelete());
    Assertions.assertEquals(1,exchangeList.get(1).getArguments("aaa"));
    Assertions.assertEquals(2,exchangeList.get(1).getArguments("bbb"));
}

4.1.3 删除交换机断言测试

java 复制代码
@Test
public void deleteExchangeTest(){
    //构造一个exchange 插入查询观察是否相同
    Exchange exchange = createExchangeTest("testExchange");
    dataBaseManger.insertExchange(exchange);
    List<Exchange> exchangeList = dataBaseManger.selectAllExchanges();
    Assertions.assertEquals(2,exchangeList.size());
    Assertions.assertEquals("testExchange",exchangeList.get(1).getName());

    //进行删除操作
    dataBaseManger.deleteExchange("testExchange");
    exchangeList = dataBaseManger.selectAllExchanges();
    Assertions.assertEquals(1,exchangeList.size());
    Assertions.assertEquals("",exchangeList.get(0).getName());

}

4.1.4 创造绑定断言测试

java 复制代码
@Test
public void insertBindingTest(){
    Binding bindIng = createBindingTest("testExchange","testQueue");
    dataBaseManger.insertBinding(bindIng);
    List<Binding> bindingList = dataBaseManger.selectAllBindings();
    Assertions.assertEquals(1, bindingList.size());
    Assertions.assertEquals("testExchange", bindingList.get(0).getExchangeName());
    Assertions.assertEquals("testQueue", bindingList.get(0).getQueueName());
    Assertions.assertEquals("testBindingKey", bindingList.get(0).getBindingKey());
}

4.1.5 删除绑定断言测试

java 复制代码
@Test
public void deleteBindingTest(){
    Binding bindIng = createBindingTest("testExchange","testQueue");
    dataBaseManger.insertBinding(bindIng);
    List<Binding> bindingList = dataBaseManger.selectAllBindings();
    Assertions.assertEquals(1, bindingList.size());
    Assertions.assertEquals("testExchange", bindingList.get(0).getExchangeName());
    Assertions.assertEquals("testQueue", bindingList.get(0).getQueueName());
    Assertions.assertEquals("testBindingKey", bindingList.get(0).getBindingKey());
    //删除
    Binding toDeleteBinding = createBindingTest("testExchange","testQueue");
    dataBaseManger.deleteBinding(toDeleteBinding);
    bindingList = dataBaseManger.selectAllBindings();
    Assertions.assertEquals(0, bindingList.size());
}

其他的不举例子源码中都包含

一次测试全部成功

4.2 文件硬盘管理测试

4.2.0 前期准备

同样对前后测试一个用例进行处理, 防止影响数据库的数据

java 复制代码
@BeforeEach
public void setUp() throws IOException {
    // 准备阶段, 创建出两个队列, 以备后用
    messageFileManager.createQueueFiles(queueName1);
    messageFileManager.createQueueFiles(queueName2);
}

//收尾
@AfterEach
public void tearDown() throws IOException {
    //收尾会把创建的两个队列销毁掉
    messageFileManager.destroyQueueFiles(queueName1);
    messageFileManager.destroyQueueFiles(queueName2);
}

4.2.1 创建文件断言测试

java 复制代码
@Test
public void createFilesTest(){
    //创建文件已经在开始执行过了, 验证文件是否存在
    File queueDataFile1 = new File("./data/" + queueName1 + "/queue_data.txt");
    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());
}

4.2.2 读取文件数量断言测试

java 复制代码
@Test
public void ReadWriteStatTest(){
    MessageFileManager.Stat stat = new MessageFileManager.Stat();
    stat.totalCount = 100;
    stat.validCount = 50;
    // 此处需要使用反射的方式来调用 writeStat 和 readStat
    // Java原生的反射比较难用, 我们使用 spring 封装好的反射工具来使用
    ReflectionTestUtils.invokeMethod(messageFileManager,"writeStat",queueName1,stat);

    // 写入完毕之后, 再调用一下读取, 验证一下读取结果与写入结果是否一致

    MessageFileManager.Stat newStat = ReflectionTestUtils.invokeMethod(messageFileManager,"readStat",queueName1);
    Assertions.assertEquals(100,newStat.totalCount);
    Assertions.assertEquals(50,newStat.validCount);
}

4.2.3 发送消息断言测试

java 复制代码
@Test
public void sentMessageTest() throws IOException, MqException, ClassNotFoundException {
    // 构造出消息, 并且构造出队列
    Message message = createTestMessage("testMessage");

    // 此处创建的 queue 对象的 name 不能随便写, 因为需要保证该队列的文件和目录都要存在
    //我们开始只准备了两个队列的文件和目录, 所以只能用那两个先来测试
    MSGQueue queue = createTestQueue(queueName1);

    // 调用发送消息的方法
    messageFileManager.sendMessage(queue,message);

    //检查一下 stat 文件
    MessageFileManager.Stat stat = ReflectionTestUtils.invokeMethod(messageFileManager,"readStat",queueName1);
    Assertions.assertEquals(1,stat.totalCount);
    Assertions.assertEquals(1,stat.validCount);

    //检查一下 data 文件
    LinkedList<Message> messages = messageFileManager.loadAllMessageFromQueue(queueName1);
    Assertions.assertEquals(1,messages.size());
    Message curMessage = messages.get(0);
    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);
}

4.2.4 获取文件所有数据断言测试

java 复制代码
@Test
public void loadAllMessageFromQueueTest() throws IOException, MqException, ClassNotFoundException {
    // 往队列插入 100 条消息, 然后验证这 100 条消息从文件中读取后, 看看是否一致
    MSGQueue queue = createTestQueue(queueName1);
    LinkedList<Message> expectedMessages = new LinkedList<>();
    for (int i = 0; i < 100; i++) {
        Message message = createTestMessage("testMessage" + i);
        messageFileManager.sendMessage(queue,message);
        expectedMessages.add(message);

    }
    // 读取所有的消息
    LinkedList<Message> actualMessages = messageFileManager.loadAllMessageFromQueue(queueName1);
    Assertions.assertEquals(expectedMessages.size(),actualMessages.size());
    for (int i = 0; i < actualMessages.size(); i++) {
        Message expectedMessage = expectedMessages.get(i);
        Message actualMessage = actualMessages.get(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());
    }
}

4.2.5 删除文件银盘消息断言测试

java 复制代码
@Test
public void testDeleteMessage() throws IOException, MqException, ClassNotFoundException {
    MSGQueue queue = createTestQueue(queueName1);
    LinkedList<Message> expectedMessages = new LinkedList<>();
    for (int i = 0; i < 10; i++) {
        Message message = createTestMessage("testMessage" + i);
        messageFileManager.sendMessage(queue,message);
        expectedMessages.add(message);
    }
    // 删除其中 3 个消息
    messageFileManager.deleteMessage(queue,expectedMessages.get(9));
    messageFileManager.deleteMessage(queue,expectedMessages.get(8));
    messageFileManager.deleteMessage(queue,expectedMessages.get(7));

    // 对比这里的内容是否正确
    LinkedList<Message> actualMessages = messageFileManager.loadAllMessageFromQueue(queueName1);
    Assertions.assertEquals(7,actualMessages.size());
    for (int i = 0; i < 7; i++) {
        Message expectedMessage = expectedMessages.get(i);
        Message actualMessage = actualMessages.get(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());
    }
}

4.2.6 文件垃圾回收断言测试

java 复制代码
@Test
public void gcTest() throws IOException, MqException, ClassNotFoundException {
    // 先往队列写 100 个消息
    // 再把 100 个消息的一半都给删除掉
    // 手动调用 gc 方法, 检测新文件的大小是否比之前缩小了
    MSGQueue queue = createTestQueue(queueName1);
    LinkedList<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();
    System.out.println("beforeGcLength 文件大小 = " + beforeGcLength);

    // 删除偶数下标的信息
    for (int i = 0; i < 100; i += 2) {
        messageFileManager.deleteMessage(queue,expectedMessages.get(i));
    }

    //手动调用 gc
    messageFileManager.gc(queue);

    //重新读取文件, 验证新的文件的内容是否和之前的内容匹配
    LinkedList<Message> actualMessages = messageFileManager.loadAllMessageFromQueue(queueName1);
    for (int i = 0; i < actualMessages.size(); i++) {
        // 把之前偶数下表的都删了, 只剩下奇数下标的了
        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("afterGcLength 文件大小 = " + afterGcLength);
    Assertions.assertTrue(beforeGcLength > afterGcLength);
}

一次性测试全部成功

4.3 内存管理信息测试

4.3.0 前期准备

一样先做好前置条件

java 复制代码
@BeforeEach
public void setUp(){
    memoryDataCenter = new MemoryDataCenter();
}

@AfterEach
public void tearDown(){
    memoryDataCenter = null;
}

4.3.1 交换机创建删除断言测试

java 复制代码
// 针对交换机进行测试
@Test
public void exchangeTest(){
    // 1. 先构造一个交换机并插入
    Exchange expectedExchange = createExchangeTest("testExchange");
    memoryDataCenter.insertExchange(expectedExchange);
    // 2. 查询这个交换机是否一致, 此处是看引用是否相同
    Exchange actualExchange = memoryDataCenter.getExchange("testExchange");
    Assertions.assertEquals(expectedExchange,actualExchange);
    // 3. 删除这个交换机
    memoryDataCenter.deleteExchange(actualExchange.getName());
    // 4. 再次查询看看是否查不到了
    actualExchange = memoryDataCenter.getExchange("testExchange");
    Assertions.assertNull(actualExchange);
}

4.3.2 队列创建删除断言测试

java 复制代码
/ 针对队列进行测试
@Test
public void queueTest(){
    // 1. 构造一个队列并插入
    MSGQueue expectedQueue = createMSGQueueTest("testQueue");
    memoryDataCenter.insertQueue(expectedQueue);
    // 2. 查询这个队列并比较
    MSGQueue actualQueue = memoryDataCenter.getQueue("testQueue");
    Assertions.assertEquals(expectedQueue,actualQueue);
    // 3. 删除这个队列
    memoryDataCenter.deleteQueue("testQueue");
    // 4. 再次查询这个队列
    actualQueue = memoryDataCenter.getQueue("testQueue");;
    Assertions.assertNull(actualQueue);
}

4.3.3 绑定创建删除断言测试

java 复制代码
// 针对绑定进行测试
@Test
public void bindingTest() throws MqException {
    // 1. 创建并插入绑定
    Binding expectedBinding = new Binding();
    expectedBinding.setExchangeName("testExchange");
    expectedBinding.setQueueName("testQueue");
    expectedBinding.setBindingKey("testBinding");
    memoryDataCenter.insertBinding(expectedBinding);
    // 2. 查询这个绑定并比较, 还要测试一下 getBindings 这个方法
    Binding actualBinding = memoryDataCenter.getBinding("testExchange","testQueue");
    ConcurrentHashMap<String, Binding> bindingMap = memoryDataCenter.getBindings("testExchange");
    Assertions.assertEquals(expectedBinding,actualBinding);
    Assertions.assertEquals(1,bindingMap.size());
    Assertions.assertEquals(expectedBinding,bindingMap.get("testQueue"));
    // 3. 删除绑定
    memoryDataCenter.deleteBinding(actualBinding);
    actualBinding = memoryDataCenter.getBinding("testExchange","testQueue");
    Assertions.assertNull(actualBinding);
}

4.3.4 消息创建删除断言测试

java 复制代码
@Test
public void MessageTest(){
    Message expectedMessage = createTestMessage("testMessage");
    memoryDataCenter.addMessage(expectedMessage);
    Message actualMessage = memoryDataCenter.getMessageById(expectedMessage.getMessageId());
    Assertions.assertEquals(expectedMessage,actualMessage);

    memoryDataCenter.deleteMessage(actualMessage.getMessageId());
    actualMessage = memoryDataCenter.getMessageById(expectedMessage.getMessageId());
    Assertions.assertNull(actualMessage);
}

4.3.5 发送消息断言测试

java 复制代码
@Test
public void sentMessageTest(){
    // 1. 创建一个队列, 创建 10 条消息, 把这些消息都插入到队列中
    MSGQueue queue = createMSGQueueTest("testQueue");
    List<Message> expectedMessages = new LinkedList<>();
    for (int i = 0; i < 10; i++) {
        Message message = createTestMessage("testMessage" + i);
        memoryDataCenter.sendMessage(queue,message);
        expectedMessages.add(message);
    }
    // 2. 从消息队列取出这些消息
    List<Message> actualMessages = new LinkedList<>();
    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 < 10; i++) {
        Assertions.assertEquals(expectedMessages.get(i),actualMessages.get(i));
    }
}

4.3.6 未应答消息断言测试

java 复制代码
@Test
public void MessageWaitAckTest(){
    Message expectedMessage = createTestMessage("testMessage");
    memoryDataCenter.addMessageWaitAck("testQueue",expectedMessage);

    Message actualMessage = memoryDataCenter.getMessageWaitAck("testQueue",expectedMessage.getMessageId());
    Assertions.assertEquals(expectedMessage,actualMessage);

    memoryDataCenter.deleteMessageWaitAck("testQueue",expectedMessage.getMessageId());
    actualMessage = memoryDataCenter.getMessageWaitAck("testQueue",expectedMessage.getMessageId());
    Assertions.assertNull(actualMessage);
}

4.3.7 重启断言测试

java 复制代码
@Test
public void recoveryTest() throws IOException, MqException, ClassNotFoundException {

    // 由于后续会进行数据库操作, 所以要先启动一下 SpringApplication, 这样才可以进行后续操作
    MqApplication.context = SpringApplication.run(MqApplication.class);
    // 1. 在硬盘上构造好数据
    DiskDataCenter diskDataCenter = new DiskDataCenter();
    diskDataCenter.init();
    // 构造交换机
    Exchange expectedExchange = createExchangeTest("testExchange");
    diskDataCenter.insertExchange(expectedExchange);
    // 构造队列
    MSGQueue expectedQueue = createMSGQueueTest("testQueue");
    diskDataCenter.insertQueue(expectedQueue);
    // 构造绑定
    Binding expectedBinding = new Binding();
    expectedBinding.setExchangeName("testExchange");
    expectedBinding.setQueueName("testQueue");
    expectedBinding.setBindingKey("testBindingKey");
    diskDataCenter.insertBinding(expectedBinding);
    // 构造消息
    Message expectedMessage = createTestMessage("testMessage");
    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.isAutoDelete(),actualExchange.isAutoDelete());
    Assertions.assertEquals(expectedExchange.isDurable(),actualExchange.isDurable());

    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.getBindingKey(),actualBinding.getBindingKey());
    Assertions.assertEquals(expectedBinding.getExchangeName(),actualBinding.getExchangeName());
    Assertions.assertEquals(expectedBinding.getQueueName(),actualBinding.getQueueName());

    Message actualMessage = memoryDataCenter.pollMessage("testQueue");
    Assertions.assertEquals(expectedMessage.getMessageId(),actualMessage.getMessageId());
    Assertions.assertArrayEquals(expectedMessage.getBody(),actualMessage.getBody());
    Assertions.assertEquals(expectedMessage.getRoutingKey(),actualMessage.getRoutingKey());
    Assertions.assertEquals(expectedMessage.getDeliverMode(),actualMessage.getDeliverMode());


    // 4. 清楚硬盘中的数据, 把整个 data 目录里的内容都删掉 (包含 data.db 和队列的目录)
    MqApplication.context.close();
    File dataDir = new File("./data");
    FileUtils.deleteDirectory(dataDir);
}

一次运行全部通过

4.4 转发规则断言测试

这是我设计的测试用例

sql 复制代码
// [测试用例]
// 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

前后进行处理

java 复制代码
@BeforeEach
public void setUp(){
    binding = new Binding();
    message = new Message();
}

@AfterEach
public void tearDown(){
    binding = null;
    message = null;
}

可以通过所有的测试用例

4.5 虚拟主机断言测试

4.5.0 前期准备

虚拟主机封装了前面的数据库, 硬盘操作以及内存管理操作

先做好前置处理

java 复制代码
@BeforeEach
public void setUp(){
    MqApplication.context = SpringApplication.run(MqApplication.class);
    virtualHost = new VirtualHost("default");
}

@AfterEach
public void tearDown() throws IOException {
    MqApplication.context.close();
    // 删除硬盘的目录
    File dataDir = new File("./data");
    FileUtils.deleteDirectory(dataDir);
}

4.5.1 发送消息断言测试

java 复制代码
@Test
public void basicPublishTest() {
    boolean ok = virtualHost.exchangeDeclare("testExchange", ExchangeType.DIRECT,
            true, false, null);
    Assertions.assertTrue(ok);
    ok = virtualHost.queueDeclare("testQueue", true, false, false,null);
    Assertions.assertTrue(ok);
    ok = virtualHost.basicPublish("testExchange","testQueue",null,"hello".getBytes());
}

4.5.2 消费者订阅消息先后顺序断言测试

java 复制代码
@Test
public void basicConsumeTest1() throws InterruptedException {
    boolean ok = virtualHost.exchangeDeclare("testExchange", ExchangeType.DIRECT,
            true, false, null);
    Assertions.assertTrue(ok);
    ok = virtualHost.queueDeclare("testQueue", true, false, false,null);
    Assertions.assertTrue(ok);
    // 先订阅队列, 后发送消息
    ok = virtualHost.basicConsume("testConsumerTag", "testQueue", true, new Consumer() {
        @Override
        public void handleDelivery(String consumerTag, BasicProperties basicProperties, byte[] body) {
            try {
                // 消费者自身设定的回调方法
                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());
            } catch (Error e) {
                // 断言失败抛出的不是Exception 而是 Error
                e.printStackTrace();
                System.out.println("Error");
            }
        }
    });
    Assertions.assertTrue(ok);
    Thread.sleep(500);
    ok = virtualHost.basicPublish("testExchange","testQueue",new BasicProperties(),"hello".getBytes());
    Assertions.assertTrue(ok);
}

@Test
public void basicConsumeTest2() throws InterruptedException {
    boolean ok = virtualHost.exchangeDeclare("testExchange", ExchangeType.DIRECT,
            true, false, null);
    Assertions.assertTrue(ok);
    ok = virtualHost.queueDeclare("testQueue", true, false, false,null);
    Assertions.assertTrue(ok);
    // 先发送消息, 后订阅队列
    ok = virtualHost.basicPublish("testExchange","testQueue",new BasicProperties(),"hello".getBytes());
    Assertions.assertTrue(ok);

    Thread.sleep(500);
    ok = virtualHost.basicConsume("testConsumerTag", "testQueue", true, new Consumer() {
        @Override
        public void handleDelivery(String consumerTag, BasicProperties basicProperties, byte[] body) {
            try {
                // 消费者自身设定的回调方法
                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());
            } catch (Error e) {
                // 断言失败抛出的不是Exception 而是 Error
                e.printStackTrace();
                System.out.println("Error");
            }
        }
    });
    Assertions.assertTrue(ok);
}

4.5.3 FANOUT 交换机订阅消费断言测试

java 复制代码
@Test
public void basicConsumeFanoutTest() 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);
    ok = virtualHost.queueBind("testQueue1","testExchange", "");
    Assertions.assertTrue(ok);


    ok = virtualHost.queueDeclare("testQueue2", false, false, false,null);
    Assertions.assertTrue(ok);
    ok = virtualHost.queueBind("testQueue2","testExchange", "");
    Assertions.assertTrue(ok);


    // 往交换机中发布一个消息
    virtualHost.basicPublish("testExchange","",null, "hello".getBytes());
    Thread.sleep(500);
    // 两个消费者订阅一下消息
    ok = virtualHost.basicConsume("testConsumerTag1", "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("testConsumerTag2", "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);
}

4.5.4 TOPIC 交换机订阅消费断言测试

java 复制代码
@Test
public void testBasicConsumeTopic() throws InterruptedException {
    boolean ok = virtualHost.exchangeDeclare("testExchange", ExchangeType.TOPIC,
            false, false, null);
    Assertions.assertTrue(ok);
    ok = virtualHost.queueDeclare("testQueue1", false, false, false,null);
    Assertions.assertTrue(ok);
    ok = virtualHost.queueBind("testQueue1","testExchange", "aaa.*.bbb");
    Assertions.assertTrue(ok);

    // 往交换机中发布一个消息
    virtualHost.basicPublish("testExchange","aaa.ccc.bbb",null, "hello1".getBytes());
    Thread.sleep(500);
    // 两个消费者订阅一下消息
    ok = virtualHost.basicConsume("testConsumerTag1", "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("hello1".getBytes(), body);
        }
    });
    Assertions.assertTrue(ok);

}

4.5.5 手动应答消息断言测试

java 复制代码
public boolean basicAck(String queueName, String messageId) {
    try {
        queueName = virtualHostName + queueName;
        // 1. 获取队列和消息
        MSGQueue queue = memoryDataCenter.getQueue(queueName);
        if (queue == null) {
            throw new MqException("[VirtualHost] 要确认的队列不存在! queueName = " + queueName);
        }
        Message message = memoryDataCenter.getMessageById(messageId);
        if (message == null) {
            throw new MqException("[VirtualHost] 要确认的队列不存在! messageId = " + messageId);
        }
        // 2. 删除硬盘的数据
        if (message.getDeliverMode() == 2) {
            diskDataCenter.deleteMessage(queue,message);
        }
        // 3. 删除内存的数据
        memoryDataCenter.deleteMessage(messageId);
        // 4. 删除待确认队列的消息
        memoryDataCenter.deleteMessageWaitAck(queueName,messageId);
        System.out.println("[VirtualHost] basicAck 成功! 消息确认成功 queueName = " + queueName
        + " messageId = " + messageId);
        return true;
    } catch (Exception e) {
        System.out.println("[VirtualHost] basicAck 失败! 消息确认失败 queueName = " + queueName
                + " messageId = " + messageId);
        e.printStackTrace();
        return false;
    }
}

4.5.6 交换机队列绑定创建上面的已经包括了

直接一次测试全部通过

4.6 客户端断言测试

4.6.0 前期准备

开始, 启动服务器, 并配置一下 ConnectionFactory 客户端的配置

结束, 停止服务器, 关闭数据库, 删掉硬盘上的文件

java 复制代码
@BeforeEach
    public void setUp() throws IOException {
        // 1. 先启动服务器
        MqApplication.context = SpringApplication.run(MqApplication.class);
        brokerServer = new BrokerServer(9090);
        // 这个 start 会进入一个死循环, 使用一个新的线程运行 start 即可!
        t = new Thread(() -> {
            try {
                brokerServer.start();
            } catch (IOException e) {
                e.printStackTrace();
            }
        });

        t.start();

        // 2. 配置 ConnectionFactory
        connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(9090);
    }

    @AfterEach
    public void tearDown() throws IOException, InterruptedException {
        // 停止服务器
        brokerServer.stop();
//        t.join();
        MqApplication.context.close();

        // 删除必要的文件
        File file = new File("./data");
        FileUtils.deleteDirectory(file);
        connectionFactory = null;
    }

4.6.1 创建连接和信道断言测试

java 复制代码
@Test
public void ConnectionTest() throws IOException {
    Connection connection = connectionFactory.newConnection();
    Assertions.assertNotNull(connection);
}

@Test
public void channelTest() throws IOException {
    Connection connection = connectionFactory.newConnection();
    Assertions.assertNotNull(connection);
    Channel channel = connection.createChannel();
    Assertions.assertNotNull(channel);
}

4.6.2 交换机请求和删除断言测试

java 复制代码
@Test
public void exchangeTest() throws IOException {
    Connection connection = connectionFactory.newConnection();
    Assertions.assertNotNull(connection);
    Channel channel = connection.createChannel();
    Assertions.assertNotNull(channel);

    boolean ok = channel.exchangeDeclare("testExchange", ExchangeType.DIRECT,true,false,null);
    Assertions.assertTrue(ok);
    ok = channel.exchangeDelete("testExchange");
    Assertions.assertTrue(ok);
    // 稳妥起见关闭一下
    channel.close();
    connection.close();
}

4.6.3 队列请求和删除断言测试

java 复制代码
@Test
public void queueTest() throws IOException {
    Connection connection = connectionFactory.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();
}

4.6.4 绑定请求和删除断言测试

java 复制代码
@Test
public void bindingTest() throws IOException {
    Connection connection = connectionFactory.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.exchangeDeclare("testExchange", ExchangeType.DIRECT,true,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();
}

4.6.5 消息发送与订阅断言测试

java 复制代码
@Test
public void messageTest() throws IOException, MqException, InterruptedException {
    Connection connection = connectionFactory.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.exchangeDeclare("testExchange", ExchangeType.DIRECT,true,false,null);
    Assertions.assertTrue(ok);

    ok = channel.basicPublish("testExchange","testQueue",null,"hello1".getBytes());
    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(body,"hello1".getBytes());
            System.out.println("消费结束");
        }
    });

    Assertions.assertTrue(ok);
    Thread.sleep(500);

    channel.close();
    connection.close();
}

一次执行全部成功

4.7 创建服务器和客户端模拟测试

4.7.1 创建消费者类

java 复制代码
package com.fafu.mq.demo;

import com.fafu.mq.common.Consumer;
import com.fafu.mq.common.MqException;
import com.fafu.mq.mqclient.Channel;
import com.fafu.mq.mqclient.Connection;
import com.fafu.mq.mqclient.ConnectionFactory;
import com.fafu.mq.mqserver.core.BasicProperties;
import com.fafu.mq.mqserver.core.ExchangeType;

import java.io.IOException;

public class DemoConsumer {
    public static void main(String[] args) throws IOException, MqException, InterruptedException {
        System.out.println("启动消费者");
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(9090);
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();

        channel.exchangeDeclare("testExchange", ExchangeType.DIRECT,true,false,null);
        channel.queueDeclare("testQueue",true,false,false,null);
        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);
                String bodyString = new String(body,0,body.length);
                System.out.println("body = " + bodyString);
                System.out.println("[消费数据] 结束");
            }
        });

        // 由于消费者也不知道生产者会生成多少消息, 所以在这里一直等待
        while (true) {
            Thread.sleep(500);
        }
    }
}

4.7.2 创建生产者类

java 复制代码
package com.fafu.mq.demo;

import com.fafu.mq.common.Consumer;
import com.fafu.mq.common.MqException;
import com.fafu.mq.mqclient.Channel;
import com.fafu.mq.mqclient.Connection;
import com.fafu.mq.mqclient.ConnectionFactory;
import com.fafu.mq.mqserver.core.BasicProperties;
import com.fafu.mq.mqserver.core.ExchangeType;

import java.io.IOException;

public class DemoConsumer {
    public static void main(String[] args) throws IOException, MqException, InterruptedException {
        System.out.println("启动消费者");
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(9090);
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();

        channel.exchangeDeclare("testExchange", ExchangeType.DIRECT,true,false,null);
        channel.queueDeclare("testQueue",true,false,false,null);
        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);
                String bodyString = new String(body,0,body.length);
                System.out.println("body = " + bodyString);
                System.out.println("[消费数据] 结束");
            }
        });

        // 由于消费者也不知道生产者会生成多少消息, 所以在这里一直等待
        while (true) {
            Thread.sleep(500);
        }
    }
}

4.7.3 打开服务器, 并运行这两个类

先启动我们的 BrokerServer

然后启动生产者

最后启动消费者

都可以正常读取数据, 没有发生报错

BrokerServer 也正常打印日志

相关推荐
茅盾体2 小时前
Electron图标相关
java·前端·electron
minji...2 小时前
Linux 网络套接字编程(四)支持多客户端同时在线、消息能转发给所有人的 UDP 聊天室服务器
linux·运维·开发语言·网络·c++·算法·udp
XS0301062 小时前
Java 基础(十一)反射
java·开发语言
凤山老林2 小时前
Spring Boot 集成 TigerGraph 实现图谱分析技术方案
java·spring boot·后端·图谱分析·tigergraph
t***5442 小时前
Dev-C++中使用Clang调试有哪些常见错误
java·开发语言·c++
代码漫谈2 小时前
探索RabbitMQ集群:如何实现消息的高可用性和负载均衡
分布式·消息队列·rabbitmq·负载均衡
ydmy2 小时前
强化学习/对齐(个人理解)
开发语言·python
xuhaoyu_cpp_java2 小时前
Mybatis学习(四)
java·经验分享·笔记·学习·mybatis