• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Junit5-并发测试

武飞扬头像
guaihui_bb
帮助2

多线程执行测试用例

我们的测试用例默认是一种顺序执行的,可以自己规定按照名称或者设置order来顺序执行,但是有时候我们为了提高测试效率,节省测试时间,需要并行去执行测试用例

一、实现并发执行

在 junit-platform.properties文件 该文件名是junit5默认的不可更改,中增加并发规则配置

1.并发规则

需要在配置文件中增加下面一行配置,用来开启并发测试

junit.jupiter.execution.parallel.enabled = true

下面测试用例,共编写3个测试类,每个测试方法打印出当前的线程名称以及打印所在的测试类:

//测试类1
public class Concurrent1_Test {
    @Test
    void test1(){
        System.out.println(Thread.currentThread().getName() "=> Concurrent1_Test");
    }

    @Test
    void test2(){
        System.out.println(Thread.currentThread().getName() "=> Concurrent1_Test");
    }

    @Test
    void test3(){
        System.out.println(Thread.currentThread().getName() "=> Concurrent1_Test");
    }
}
//测试类2
public class Concurrent2_Test {

    @Test
    void test1(){
        System.out.println(Thread.currentThread().getName() "=> Concurrent2_Test");
    }

    @Test
    void test2(){
        System.out.println(Thread.currentThread().getName() "=> Concurrent2_Test");
    }

    @Test
    void test3(){
        System.out.println(Thread.currentThread().getName() "=> Concurrent2_Test");
    }
}
//测试类3
public class Concurrent3_Test {
    @Test
    void test1(){
        System.out.println(Thread.currentThread().getName() "=> Concurrent3_Test");
    }

    @Test
    void test2(){
        System.out.println(Thread.currentThread().getName() "=> Concurrent3_Test");
    }

    @Test
    void test3(){
        System.out.println(Thread.currentThread().getName() "=> Concurrent3_Test");
    }
}

学新通

在开启并行测试配置前执行:得到结果如下:
三个测试类的方法法都在main进程里
学新通
开启配置后,执行用例,会发现 进程是ForkJoinPool-1-worker-3,说明 junit5开始使用了ForkJoin线程池,但是看结果的话,测试用例并没有并行执行
学新通

因此,该配置,是用来声明可以用来并行执行测试用例了,但是并没有声明并行测试的规则
下面有几种执行规则的配置:

same_thread是默认值,默认同一线程下执行
concurrent 是并发执行

测试方法并发执行

增加配置如下,是配置所有测试方法并发执行

#所有的测试方法并行执行
junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = concurrent

这种配置方式,不知道为什么我这里还是按照类去并行执行的 方法并没有并行执行,有待解决
已找到原因,原因是配置文件中有junit.jupiter.testmethod.order.default该配置用来配置用例执行顺序的,与并发配置出现冲突,删除后好了:
执行结果如下图所示,9个测试方法有9个线程执行
学新通

测试类并发,测试方法不并发

#所有的测试类 并行执行
junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = same_thread
junit.jupiter.execution.parallel.mode.classes.default = concurrent

执行结果输出如下,由此看出,执行的顺序全部打乱了,并且不同的测试类使用的不同的线程去执行的,同一个测试类在同一线程下执行
学新通

测试类串行,测试方法并行

junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = concurrent
junit.jupiter.execution.parallel.mode.classes.default = same_thread

测试类测试方法都并行

该配置的执行效果与只开启测试方法并发执行效果一致

junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = concurrent
junit.jupiter.execution.parallel.mode.classes.default = concurrent

2.配置并发策略

上面的配置只是声明了并发规则,并没有实现并发数的这样一个实现,在测试过程中还需要一个多并发,需要规定某个测试节点的并发数
使用配置添加并发执行策略,并发策略共有三个值:dynamic、fixed、custom ,默认值 dynamic,其中custom需要通过自定义形式来配置并发线程数

dynamic(动态)

动态策略(并发数是动态的)
并发数是根据 处理器/内核数 ✖ 因子参数(默认1) 来确定线程数

#开启策略为dynamic
junit.jupiter.execution.parallel.config.strategy = dynamic

设置dynamic的并发数

dynamic 的系数配置项:默认是1
junit.jupiter.execution.parallel.config.dynamic.factor

fixed(固定)

固定策略(并发数是写死的)
所需要的并发数依赖于预定义的并发数

#fixed固定策略
junit.jupiter.execution.parallel.config.strategy = fixed

如下所示,配置并发数为2

junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.config.strategy = fixed
junit.jupiter.execution.parallel.config.fixed.parallelism =  2

执行测试用例,得到 只有两个线程执行:
学新通

custom(自定义)

在配置文件配置custom策略,并发数设置3

junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.config.strategy = custom
junit.jupiter.execution.parallel.config.custom.class = com.xx.CustomStrategy//自定义策略类的路径
junit.jupiter.execution.parallel.config.custom.parallelism = 3

自定义策略方法,实际上是参考fixed和dynamic,fiexd的策略是实现了ParallelExecutionConfigurationStrategy接口,因此自定义custom也需要实现该接口

package com.ceshiren;

import org.junit.platform.commons.JUnitException;
import org.junit.platform.engine.ConfigurationParameters;
import org.junit.platform.engine.support.hierarchical.ParallelExecutionConfiguration;
import org.junit.platform.engine.support.hierarchical.ParallelExecutionConfigurationStrategy;

/**
 * 使用自定义类型 custom实现并发策略
 */
public class CustomStrategy implements ParallelExecutionConfigurationStrategy {
    @Override
    public ParallelExecutionConfiguration createConfiguration(ConfigurationParameters configurationParameters) {
        //如何自定义并发策略,参考ParallelExecutionConfigurationStrategy的其他实现类,例如fixed
//        int parallelism = (Integer)configurationParameters.get("fixed.parallelism", Integer::valueOf).orElseThrow(() -> {
//            return new JUnitException(String.format("Configuration parameter '%s' must be set", "fixed.parallelism"));
//        });
//        return new DefaultParallelExecutionConfiguration(parallelism, parallelism, 256   parallelism, parallelism, 30);
        //双冒号是lambda表达式的用法,相当于 x -> Integer.valueOf(x)
       int count =  configurationParameters.get("custom.parallelism", Integer::valueOf).orElseThrow(() -> {
            return new JUnitException(String.format("Configuration parameter '%s' must be set", "custom.parallelism"));

        });
        return new ParallelExecutionConfiguration() {
            @Override
            public int getParallelism() {
                return count;
            }

            @Override
            public int getMinimumRunnable() {
                return count;
            }

            @Override
            public int getMaxPoolSize() {
                return count;
            }

            @Override
            public int getCorePoolSize() {
                return count;
            }

            @Override
            public int getKeepAliveSeconds() {
                return count;
            }
        };
    }
}

学新通

不过在我看来自定义custom的方式和fixed的方式没有啥区别,还是使用fixed就行,如果fiexd不满足使用,那可能就使用custom自己开发功能

3.使用注解@Execution

也需要在配置文件开启并行测试开关

junit.jupiter.execution.parallel.enabled = true

需要在类上面增加注解@Execution(ExecutionMode.CONCURRENT)

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;

/**
 * 并发测试从junit5.3以后才有
 */
@Execution(ExecutionMode.CONCURRENT)
public class Concurrent1_Test {
    @Test
    void test1(){
        System.out.println(Thread.currentThread().getName() "=> Concurrent1_Test1");
    }

    @Test
    void test2(){
        System.out.println(Thread.currentThread().getName() "=> Concurrent1_Test2");
    }

    @Test
    void test3(){
        System.out.println(Thread.currentThread().getName() "=> Concurrent1_Test3");
    }

    @Test
    void test4(){
        System.out.println(Thread.currentThread().getName() "=> Concurrent1_Test4");
    }
}
学新通

直接执行这个类,方法是 并发执行的
学新通
将注解放在方法上,将类上的注解改成默认(串行),第一个和第二个方法增加注解,第三和第四不增加注解:

@Execution(ExecutionMode.SAME_THREAD)
public class Concurrent1_Test {
    @Test
    @Execution(ExecutionMode.CONCURRENT)
    void test1() {
        System.out.println(Thread.currentThread().getName()   "=> Concurrent1_Test1");
    }

    @Test
    @Execution(ExecutionMode.CONCURRENT)
    void test2() {
        System.out.println(Thread.currentThread().getName()   "=> Concurrent1_Test2");
    }

    @Test
    void test3() {
        System.out.println(Thread.currentThread().getName()   "=> Concurrent1_Test3");
    }

    @Test
    void test4() {
        System.out.println(Thread.currentThread().getName()   "=> Concurrent1_Test4");
    }

}
学新通

执行结果如下,方法一和方法二是并发执行 方法三和方法四在另一个线程中串行执行,由此看出来 将注解放在方法上比放在类上的优先级更高

学新通

4.并发策略 @Execution

在配置文件中开启并发执行,并设置每个测试方法并发执行 配置并发数为 3,并使用注解

#custom 自定义
junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = concurrent
junit.jupiter.execution.parallel.config.strategy = custom
junit.jupiter.execution.parallel.config.custom.class = com.ceshiren.CustomStrategy
junit.jupiter.execution.parallel.config.custom.parallelism = 20

前面我们没有配置并发数,只是用注解的话,会根据这个注解来开启线程数,如果我们配置了线程数,那么会限制创建的并线程数
下面执行 全部的3个测试类,其中包含带有注解的测试类
执行结果如下:我们一共允许开启20个线程,这个数超过了所需要的线程数,我们也看到了:

1.没有使用注解的类中的方法全部都并发执行,
2.使用注解的类中 标明要并发的方法并发执行(类1中方法12是并发执行),而没有声明并发执行的方法是串行执行(类1中方法34是串行执行)

学新通
由此看出来,配置文件是全局生效,注解针对于类和方法,其中注解的优先级大于配置(按照类进行并发、按照方法并发)

二、多线程的数据共享

有些时候资源是需要被共享的,多个线程同时访问同一资源,但是多并发使用同一资源容易造成问题,因此又出来了锁机制来实现并发安全,在并发操作中操作同一资源需要上锁

  • @ResourceLock注解实现资源同步,该注解相当于synchronized、@sychronized
  • @ResourceLock 为测试类和测试方法提供同步机制
  • 该注解有2个参数:一个是String型参数,指定资源值,另一个是访问模式-ResourceAccessMode ,访问模式可以是READ(只读)、 READ_WRITE(读写)

预定义资源:

  • Resources.SYSTEM_PROPERTIES:表示java系统属性
  • Resources.SYSTEM_OUT:表示当前进程的标准输出流
  • Resources.SYSTEM_ERR:表示当前进程的标准错误流
  • Resources.LOCALE:当前JVM实例的默认语言环境
  • Resources.TIMEZONE:当前JVM实例的默认时区

配置多线程执行测试用例,给测试类增加@Execution(ExecutionMode.CONCURRENT),方法使用@ResourceLock注解来读写同一个系统资源

@Execution(ExecutionMode.CONCURRENT)
public class ResourceLockTest {
    Properties properties;

    @BeforeEach
    void setUpClass() {
        properties = new Properties(System.getProperties());
    }

    @Test
    @ResourceLock(value = Resources.SYSTEM_PROPERTIES, mode = ResourceAccessMode.READ)
    void test1() {
        assertNull(System.getProperty("custom"));
    }

    @Test
    @ResourceLock(value = Resources.SYSTEM_PROPERTIES, mode = ResourceAccessMode.READ_WRITE)
    void test2() {
        System.setProperty("custom", "custom_value");
        assertEquals("custom_value", System.getProperty("custom"));
    }

    @Test
    @ResourceLock(value = Resources.SYSTEM_PROPERTIES, mode = ResourceAccessMode.READ_WRITE)
    void test3() {
        System.setProperty("custom", "custom_value2");
        assertEquals("custom_value2", System.getProperty("custom"));
    }
}
学新通

自定义资源,这里用并发读写map为例,定义一个Map 给他增加增删改查方法,并发对其进行增删改查操作,验证结果是否正确

@Execution(ExecutionMode.CONCURRENT)
public class ResourceLock2Test {
    public static final String GLOBAL_USER = "com.xxx.entity.User";//自定义资源类
    @BeforeEach
    void before() {
        User.clear();
    }

    @Test
    @ResourceLock(value = GLOBAL_USER,mode = ResourceAccessMode.READ)
    void get() {
        System.out.println("get => "   User.getUser());
        assertTrue(User.getUser().isEmpty());
    }

    @Test
    @ResourceLock(value = GLOBAL_USER,mode = ResourceAccessMode.READ_WRITE)
    void add() {
        User.add(1, "小明");
        System.out.println("add => "   User.getUser());
        assertEquals("小明", User.get(1));
    }

    @Test
    @ResourceLock(value = GLOBAL_USER,mode = ResourceAccessMode.READ_WRITE)
    void update() {
        User.update(1, "小红");
        System.out.println("update => "   User.getUser());
        assertEquals("小红", User.get(1));
    }

    @Test
    @ResourceLock(value = GLOBAL_USER,mode = ResourceAccessMode.READ_WRITE)
    void del() {
        User.add(2, "思思");
        System.out.println("remove => "   User.getUser());
        User.del(2);
        assertNull(User.get(2));
    }
}
学新通

通过向 maven surefire 插件提供参数

使用maven surefire 可以使用mvn命令:mvn test -Dtest 来执行测试用例
在这里可以mvn命令来进行并行测试:

mvn test -Djunit.jupiter.execution.parallel.enabled=true

待补充相关知识点

通过向 JVM 提供系统属性

待补充相关知识点


注意:使用配置来进行并发执行用例时,配置文件中不能同时有
junit.jupiter.testmethod.order.default这个配置,该配置时用来自定义用例执行顺序的,会与并发执行 存在冲突,导致并发执行的配置不生效

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgcbcke
系列文章
更多 icon
同类精品
更多 icon
继续加载