MiniMQ(单元测试报告)

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();
    }
}
相关推荐
一轮弯弯的明月2 小时前
有序整数对个数-欧拉函数
java·算法·蓝桥杯·学习心得
lifewange2 小时前
Java 自动化测试参数化实现
java·数据库·sqlserver
码上农民2 小时前
Idea2025.3.3专业版安装和无限试用
java·ide·intellij-idea
CDN3602 小时前
CDN 回源异常、源站压力大?负载均衡与回源策略优化
java·运维·负载均衡
ywlovecjy2 小时前
怎么下载安装yarn
java
凌冰_2 小时前
异常: Can not set java.lang.Double field org.hlx.my2.pojo.Book.price
java·开发语言
计算机徐师兄2 小时前
Java基于SSM的文玩销售小程序【附源码、文档说明】
java·小程序·文玩销售小程序·文玩销售·java文玩销售小程序·文玩销售微信小程序·java文玩销售微信小程序
mOok ONSC2 小时前
Spring Boot 3.4 正式发布,结构化日志!
java·spring boot·后端
rainy雨2 小时前
质量工具系统功能详解:针对检验效率低与追溯困难场景的质量工具应用方案
java·大数据·数据库·人工智能·精益工程