一、需求背景
1、自动化测试:一个自动化测试脚本有成千上万条用例,每次执行的时间在小时级别,leader说为了能先于用户知道线上的问题,能否加快脚本的执行速度
2、功能测试:不使用jmeter等工具,使用junit5实现来测试接口是否存在线程安全以及分布式线程安全问题,是否有做幂等性处理
3、功能测试:来测试增删改查接口,同时发送请求,检查是否有数据库安全问题,即是否会引起锁表,而引起接口超时
二、解决方案
junit5的并行测试
三、前提条件
自身的测试用例独立,用例之间没有依赖关系,容错性好
四、具体步骤
1、在src/main/resources/路径下新建文件junit-platform.properties
2、在文件中添加如下内容
#是否允许并行执行true/false junit.jupiter.execution.parallel.enabled=true #是否支持方法级别多线程same_thread/concurrent junit.jupiter.execution.parallel.mode.default=concurrent #是否支持类级别多线程same_thread/concurrent junit.jupiter.execution.parallel.mode.classes.default=concurrent # the maximum pool size can be configured using a ParallelExecutionConfigurationStrategy junit.jupiter.execution.parallel.config.strategy=fixed junit.jupiter.execution.parallel.config.fixed.parallelism=5
3、测试代码
package com.wechat.testcase; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.parallel.Execution; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; /** * 线程测试 */ //@Execution(CONCURRENT) @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class Demo_07_1 { private static final Logger logger = LoggerFactory.getLogger(Demo_07_1.class); @DisplayName("线程测试01") @RepeatedTest(10) @Execution(CONCURRENT) void threadTest01() throws InterruptedException { Thread.sleep(3000); long id = Thread.currentThread().getId(); logger.info("线程号" + id + "==>装入坚果01 " + "\n"); } @DisplayName("线程测试02") @Execution(CONCURRENT) @RepeatedTest(10) void threadTest02() throws InterruptedException { Thread.sleep(3000); long id = Thread.currentThread().getId(); logger.info("线程号" + id + "==>装入坚果02 " + "\n"); } }
测试结果:
4、真实案例(企业微信部门创建接口)---测试接口是否是线程安全以及分布式线程安全
package com.wechat.testcase; import com.wechat.apiobject.DepartmentApiObject; import com.wechat.apiobject.TokenHelper; import io.restassured.response.Response; import org.junit.jupiter.api.*; import org.junit.jupiter.api.parallel.Execution; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; /** * 线程测试 */ //@Execution(CONCURRENT) @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class Demo_07_2 { private static final Logger logger = LoggerFactory.getLogger(Demo_07_2.class); static String accessToken; @BeforeAll public static void getAccessToken() throws Exception { accessToken = TokenHelper.getAccessToken(); logger.info(accessToken); } @DisplayName("创建部门") @RepeatedTest(20) @Execution(CONCURRENT) void createDepartment() { String creatName = "name1234567"; String creatEnName = "en_name1234567"; Response creatResponse = DepartmentApiObject.createDepartment(creatName, creatEnName, accessToken); assertEquals("0", creatResponse.path("errcode").toString()); } }
测试结果:
备注:说明企业微信的部门创建接口是做了线程安全和幂等性处理,5个线程同时执行20条相同的部门创建请求,只有1条是成功,19条是失败
5、真实案例(企业微信部门创建和修改接口)---测试数据库是否安全
/** * projectName: WeChatWorkApiTest * fileName: Demo_01.java * packageName: com.wechat.testcase * date: 2020-07-18 2:49 下午 */ package com.wechat.testcase; import com.wechat.apiobject.DepartmentApiObject; import com.wechat.apiobject.TokenHelper; import com.wechat.utils.FakeUtils; import io.qameta.allure.Description; import io.restassured.response.Response; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.parallel.Execution; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; /** * 优化记录: * 1、增加了入参实时获取的逻辑 * 2、增加了脚本的独立性改造 * 3、通过添加evnClear方法解决脚本无法重复运行的问题 * 4、对脚本行了分层,减少了重复代码,减少了很多维护成本 **/ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class Demo_07_03 { private static final Logger logger = LoggerFactory.getLogger(Demo_07_03.class); static String accessToken; static String departmentId; @BeforeAll static void getAccessToken() { accessToken = TokenHelper.getAccessToken(); } @Description("创建部门") @RepeatedTest(10) @Execution(CONCURRENT) void creatDepartment() { String backentStr = Thread.currentThread().getId() + FakeUtils.getTimeStamp(); String creatName = "creatName" + backentStr; String creatNameEn = "creatNamEn" + backentStr; Response creatResponse = DepartmentApiObject.createDepartment(creatName, creatNameEn, accessToken); departmentId = creatResponse.path("id") != null ? creatResponse.path("id").toString() : null; assertEquals("0", creatResponse.path("errcode").toString()); } @Description("更新部门") @RepeatedTest(10) @Execution(CONCURRENT) void updateDepartment() { String backentStr = Thread.currentThread().getId() + FakeUtils.getTimeStamp(); String creatName = "creatName" + backentStr; String creatNameEn = "creatNamEn" + backentStr; Response creatResponse = DepartmentApiObject.createDepartment(creatName, creatNameEn, accessToken); departmentId = creatResponse.path("id") != null ? creatResponse.path("id").toString() : null; String updateName = "updateName" + backentStr; String updateNameEn = "updateNameEn" + backentStr; Response updateResponse = DepartmentApiObject.updateDepartment(updateName, updateNameEn, departmentId, accessToken); assertEquals("0", updateResponse.path("errcode").toString()); } }
测试结果:
备注:20条用例被5个线程同时执行,都没有报错,说明其接口的表索引和程序处理得当
五、总结
我们需要扎实的掌握junit5,理论结合实际,才能让我们的产品不出重大bug