关于Junit @Test 不支持多线程测试

1,095 阅读4分钟

太长不看版本

JUnit@Test的测试 不支持多线程,代码执行完就会直接退出,不会检测子线程是否结束

Junit 测试的入口TestRunnermain方法,在执行aTestRunner.start()之后,不会去判断子线程是否结束,而是调用wasSuccessful()进行判断:然后主线程接下来就会执行System.exit() 进行退出。

public synchronized boolean wasSuccessful() {
    return failureCount() == 0 && errorCount() == 0;
}

解决方法 一 : 放在main函数中执行肯定没问题,因为main函数就是用户线程默认创建出来的子线程也是用户线程,jvm会等待所有用户线程执行完毕以后才会退出jvm,但是不会等守护线程(GC就是守护线程)

解决方法 二 : thread.join() 使用该方法通知主线程等待子线程执行完毕以后再结束

解决方法 三 : 同理给主线程添加耗时操作,Thread.sleep(1000);

解决方法 四 : 使用测试方法中CountDownLatch类,来等其他线程执行完再执行主方法中的类容

解决方法 五 : 使用executor.awaitTermination()

解决方法 六: 使用扩展测试类,如GroboUtils

1. 问题

最近在学习RabbitMQ的相关内容,在做练习的时候发现了一个问题,basicConsume 在Junit 的@Test 会直接结束,不会去监听消息。

生产者 发送消息

public class Provider {

    //生产消息的代码
    @Test
    public void testSendMessage() throws IOException, TimeoutException, InterruptedException {

        //创建连接mq的连接工厂
        var connectionFactory = new ConnectionFactory();
        //设置连接rabbitmq的主机
        connectionFactory.setHost("127.0.0.1");
        //设置端口号
        connectionFactory.setPort(5672);
        //设置连接的虚拟主机
        connectionFactory.setVirtualHost("/ems");
        //设置访问虚拟主机的用户名密码
        connectionFactory.setUsername("ems");
        connectionFactory.setPassword("123");

        //获取连接对象
        var connection = connectionFactory.newConnection();

        //获取连接通道对象
        var channel = connection.createChannel();

        //通道绑定消息队列
        //参数1: 队列名称 不存在自动创建
        //参数2: durable 队列的对象是否要持久化
        //参数3: exclusive 是否独占队列 代表当前队列是否当前连接可用
        //参数4: autoDelete 是否在消费完成后自动删除队列
        //参数5: 额外参数
        channel.queueDeclare("hello", false, false, false, null);

        //发布消息
        //参数1: 交换机名称
        //参数2: 队列名称
        //参数3: 传递消息额外设置
        //参数4: 消息的具体内容
        var scanner = new Scanner(System.in);
        while (scanner.hasNext()) {
            channel.basicPublish("", "hello", null,scanner.nextLine().getBytes());
        }
        //关闭连接
        channel.close();
    }
}

消费者 接收消息

public class Customer {

    @Test
    public void testAcceptMsg() throws IOException, TimeoutException, InterruptedException {

        //创建连接mq的连接工厂
        var connectionFactory = new ConnectionFactory();
        //设置连接rabbitmq的主机
        connectionFactory.setHost("127.0.0.1");
        //设置端口号
        connectionFactory.setPort(5672);
        //设置连接的虚拟主机
        connectionFactory.setVirtualHost("/ems");
        //设置访问虚拟主机的用户名密码
        connectionFactory.setUsername("ems");
        connectionFactory.setPassword("123");

        //创建连接对象
        var connection = connectionFactory.newConnection();

        //创建通道
        var channel = connection.createChannel();

        //通道绑定队列
        channel.queueDeclare("hello", false, false, false, null);

        //消费消息
        //参数1:消费哪个队列
        //参数2:开启消息的自动确认机制
        //参数3:消费消息时候的回调接口
        channel.basicConsume("hello", true, new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("String.valueOf(body) = " + new String(body));
            }
        });

        // 需要一直监听队列进行处理
    }

消费者会直接结束 image.png

2. 分析

2.1 channel.basicConsume

image.png

image.png

image.png

image.png

image.png

image.png

会以子线程的方式去处理

2.2 可以使用 jvm jstack工具观察

没有消息 image.png

消费一条消息

image.png

消费多条消息

image.png

2.2 Junit @Test

可以阅读 守护线程和非守护线程

Junit 入口

Junit 测试的入口TestRunnermain方法,在执行aTestRunner.start()之后,不会去判断子线程是否结束,而是调用wasSuccessful()进行判断:然后主线程接下来就会执行System.exit() 进行退出。

public synchronized boolean wasSuccessful() {
    return failureCount() == 0 && errorCount() == 0;
}

3. 解决方案:

可以保证主线程不结束就可以,等待子线程全部运行结束后在结束主线程。

解决方法 一 : 放在main函数中执行肯定没问题,因为main函数就是用户线程默认创建出来的子线程也是用户线程,jvm会等待所有用户线程执行完毕以后才会退出jvm,但是不会等守护线程(GC就是守护线程)

解决方法 二 : thread.join() 使用该方法通知主线程等待子线程执行完毕以后再结束

解决方法 三 : 同理给主线程添加耗时操作,Thread.sleep(1000);

解决方法 四 : 使用测试方法中CountDownLatch类,来等其他线程执行完再执行主方法中的类容

解决方法 五 : 使用executor.awaitTermination()

解决方法 六: 使用扩展测试类,如GroboUtils