CI+JUnit5并发单测机制创新实践

目录

一. 现状·问题

二. 分析原因

三. 采取措施

四. 实践步骤

五. 效能提升

资料获取方法


一. 现状·问题

针对现如今高并发场景的业务系统,“并发问题” 终归是必不可少的一类(占比接近10%),每次出现问题和事故后,需要耗费大量人力成本排查分析并修复。那如果能在事前尽可能避免岂不是很香?

二. 分析原因

  • 当前并发测试多数依赖测试人员进行脚本测试,同时还依赖了研发和产品识别出并发操作的场景用例。
  • 对于并发测试,大概两条路子:
  1. 所有修改同样数据的命令式接口都测一遍?【耗费巨大测试成本】
  2. 保证黄金流程的接口,研发从头扒代码。【可能会遗漏,耗费一定研发成本】

🤔自我反思

  • 作为研发,是不是在刚开发接口时候,识别到并发场景随着单元测试阶段同时进行并发测试,这样的成本是最小的,收益是最高效的!

三. 采取措施

并发测试前置

采用CI持续集成机制,依靠行云流水线,底层利用junit5单元测试框架并发parallel引擎,嵌入同步数据库的自定义unit test脚本,将每个并发case维护成单元测试,数据自我闭环,可重复执行

将核心的并发场景进行及时的运行验证,最早洞察,最早验证,最小成本,最大保障!

四. 实践步骤

前提:配置junit-platform.properties

# src/test/resources/junit-platform.properties
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.config.strategy=fixed
junit.jupiter.execution.parallel.config.fixed.parallelism=20

单接口并发-@RepeatedTest

  • ManualCheckAppConcurrentTest 出库复核并发测试「单接口并发」-> 手动复核 10个线程

👉 核心代码块

public class ManualCheckAppConcurrentTest extends ConcurrentTest {@ResourceManualCheckAppService manualCheckAppService;//记录执行成功的线程数static int successThreadCount = 0;///// 单接口并发///@DisplayName("(单接口并发)并发测试【手动确认复核】")@Description("(10个线程)场景:复核1件,一共5件,应该有5个线程成功,5个线程失败:没有查询到容器明细记录" +"使用友好式分布式锁防止并发,并发后等待重试,保证顺序执行无异常!")@Execution(CONCURRENT)@RepeatedTest(value = 10, name = "{displayName}:{totalRepetitions}-{currentRepetition}")public void testConfirmChecked(TestInfo testInfo) {manualCheckAppService.confirmChecked(mockConfirmCheckedDto());successThreadCount++;}/*** 断言最终结果:数据无问题,线程执行无问题*/@AfterAllpublic static void assertResult() {//线程执行成功数期望:一共5件,每个线程复核1件,共有5个线程成功Assertions.assertEquals(5, successThreadCount);//数据成功期望:没有待复核的容器明细了,因为都复核成功了,一共5件ConfirmCheckedDto confirmCheckedDto = mockConfirmCheckedDto();List<ContainerDetailPo> containerDetailPos = SpringUtil.getBean(ContainerDetailDao.class).selectUncheckDetailsBySoAndSku(confirmCheckedDto.getTaskNo(), confirmCheckedDto.getShipmentOrderNo(), confirmCheckedDto.getSku(), confirmCheckedDto.getWarehouseNo());Assertions.assertTrue(CollectionUtils.isEmpty(containerDetailPos));}@Test@Sql({"/concurrent/manualCheck.sql"})@Overridevoid prepareData()

多场景并发-@Execution(CONCURRENT)

  • CheckAppConcurrentTest 出库复核并发测试「多场景并发」-> 手动复核|自动复核

👉 核心代码块

public class CheckAppConcurrentTest extends ConcurrentTest {@ResourceManualCheckAppService manualCheckAppService;@ResourceAutoCheckAppService autoCheckAppService;///// 多场景并发///@DisplayName("(多场景并发)并发测试【自动确认复核】")@Description("与手动复核发生并发场景,期望可能存在业务异常(自定义锁冲突发生的消息)")@Execution(CONCURRENT)@Testpublic void testAutoCheckBySo() {autoCheckAppService.autoCheckBySo(Lists.newArrayList("SO-6_6_601-1492066800186167296"), mockAutoCheckBySoDto());}@DisplayName("(多场景并发)并发测试【手动确认复核】")@Description("与自动复核发生并发场景,期望可能存在业务异常(自定义锁冲突发生的消息)")@Execution(CONCURRENT)@Testpublic void testConfirmChecked() {manualCheckAppService.confirmChecked(mockConfirmCheckedDto());}/*** 断言最终结果:数据无问题*/@AfterAllpublic static void assertResult() {//数据成功期望:没有待复核的容器明细了,无论是手动复核还是自动复核,都会全部复核完ConfirmCheckedDto confirmCheckedDto = mockConfirmCheckedDto();List<ContainerDetailPo> containerDetailPos = SpringUtil.getBean(ContainerDetailDao.class).selectUncheckDetailsBySoAndSku(confirmCheckedDto.getTaskNo(), confirmCheckedDto.getShipmentOrderNo(), confirmCheckedDto.getSku(), confirmCheckedDto.getWarehouseNo());Assertions.assertTrue(CollectionUtils.isEmpty(containerDetailPos));}@Test@Sql({"/concurrent/manualCheck.sql"})@Overridevoid prepareData() {}

并发单测基类-@Transactional

ConcurrentTest 建议抽出并发测试基类(主要目的:准备数据、设置路由、数据清除、独立执行)

@Tag("parallel")分组: 并发测试用例,有助于单独执行套件! ​

👉 核心代码块


@SpringBootTest(classes = WebApplication.class)
@Tag("parallel")
public abstract class ConcurrentTest {/*** 并发测试场景的前提数据准备* { @Sql 数据脚本配置 }*/@Transactional@Order(0)@Rollback(false)abstract void prepareData();/*** 设置当前线程数据源*/@BeforeTransactionpublic void setThreadDataSource() {DataSourceContextHolder.clearDataSourceKey();//多数据源,分库分表DataSourceContextHolder.setDataSource("ds0");}/*** 清除数据*/@Rollback(false)@AfterAllpublic static void clearData(){new DatabaseSyncTest().execute("wms_check","wms_check_test");}

数据准备-@Sql

如何准备数据?

=> 新建一个专门单元测试/并发测试的空数据库

准备测试场景的前置数据SQL脚本

👉 源脚本

DELETE FROM ck_task;
INSERT INTO ck_task (id, task_no, sku_qty, total_qty, platform_no, status, warehouse_no, create_user,update_user, create_time, update_time, ts, deleted, suggest_platform, uuid,parent_task_no, pick_differ_allow, operation_type, picking_flag, task_type,ext_info,subtask_qty, tenant_code, current_stream_no, confluence, batch_no, requirements)
VALUES (1492071049884340224, 'T6X6X60122021100000329', 1.0000, 5.0000, '', 0, '6_6_601', 'xiaoyan', 'xiaoyan','2022-02-11 17:45:26', '2022-02-11 17:45:26', '2022-02-11 17:45:26', 0, '', 'zyr1228003', '', 0, 0, 0, 0, null,null, 'TC30020150', 0, 1, 'cj006001', '{"allowBatchCheck": true}');     

数据回滚-@ParameterizedTest

CI自动同步数据库表结构: 测试环境数据库->单测数据库

利好:(研发无需被动维护schema,自动与真实数据库结构同步)

只需要将下面单测copy到代码中,将fromDb和toDb参数修改成自己数据库即可!

👉 源代码

    @DisplayName("单元测试MYSQL-DB结构同步")@SneakyThrows@ParameterizedTest@CsvSource("wms_check,wms_check_test")public void execute(String fromDb, String toDb) {ResultSet resultSet = null;Class.forName("com.mysql.jdbc.Driver");try (Connection connection = DriverManager.getConnection("***","user", "***");Statement statement = connection.createStatement()) {String initDb = "DROP DATABASE IF EXISTS " + toDb + ";CREATE DATABASE " + toDb + ";";log.info(initDb);statement.executeUpdate(initDb);resultSet = statement.executeQuery("SHOW TABLES FROM " + fromDb + ";");List<String> tableNames = Lists.newArrayList();while (resultSet.next()) {tableNames.add(resultSet.getString("Tables_in_" + fromDb));}for (String tableName : tableNames) {String syncSql = "DROP TABLE IF EXISTS " + toDb + "." + tableName + ";" +"CREATE TABLE " + toDb + "." + tableName + " LIKE " + fromDb + "." + tableName + ";";log.info(syncSql);statement.executeUpdate(syncSql);}} finally {if(resultSet != null){resultSet.close();}}}

配置CI-@行云流水线

建议在提测流水线增加,不要再日常dev流水线(集成测试相对耗时)

只执行并发单测用例-Dtest.mode 基于junit5 @Tag

JUnit 5 User Guide

mvn test -Dtest.mode=parallel

配置IDEA-本地测试

—— 只运行并发测试用例

执行结果

单接口并发单测

多场景并发单测

五. 效能提升

5.1需求交付效率提升

5.1.1降低测试周期阶段时长

2022-02月实践后

因为「并发测试」前置到「研发单元测试」环节,所以「测试阶段」时长缩短 (2.5 天 -> 1 天)

2022-Q1

2022-Q2

2022-Q3

2022-Q4

「测试周期」阶段停留时长和占比,呈下降趋势!

5.1.2缩短需求交付全周期

2022-02月实践后

因为「测试周期」缩短,研发单元测试成本几乎不变,所以「需求交付全周期」随之缩短(55 天 -> 35 天)!

5.2人效提升

5.2.1提升验证全面性

「case by case」 ,通过单元测试「断言机制」,最细粒度全方位验证!

在【开发阶段】识别到接口存在并发问题,及时编写单元测试进行验证,针对分布式锁和乐观锁等常用防并发手段,对应不同的assert方式:

  • 数据库乐观锁:通过判断最终数据保证执行无问题
  • 分布式友好锁:不会报错,会等待,最终所有请求处理成功
  • 分布式冲突锁:直接报错,断言异常信息
  • ......

5.2.2降低测试人力成本

减少花大量时间专项测试N个接口并发测试成本,「最早发现,最早处理,最小成本」!

根据下图可见,从编码阶段、单元测试阶段、接口测试阶段、集成测试阶段、预发布阶段等软件生命周期中,越早发现问题,付出成本越小。

5.2.3提升需求吞吐量

2022-02月实践后

因为减少人力成本,所以会直接提升需求的吞吐量(200个 -> 225个)!

5.3过程质量提升

5.3.1降低问题的发生概率

「并发测试前置」 到研发单元测试环节,可减少缺陷数,降低问题发生概率!

5.3.2减少线上问题数

👉 今年线上问题-并发问题 类别为 0

5.3.2减少Bug数

👉过程质量中并发问题趋势逐步降低


资料获取方法

【留言777】

各位想获取源码等教程资料的朋友请点赞 + 评论 + 收藏,三连!

三连之后我会在评论区挨个私信发给你们~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/34067.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CNN经典网络模型之GoogleNet论文解读

目录 1. GoogleNet 1.1 Inception模块 1.1.1 1x1卷积 1.2 辅助分类器结构 1.3 GoogleNet网络结构图 1. GoogleNet GoogleNet&#xff0c;也被称为Inception-v1&#xff0c;是由Google团队在2014年提出的一种深度卷积神经网络架构&#xff0c;专门用于图像分类和特征提取任…

vConsole手机调试模式uniapp和原生h5

手机打开调试模式的方法 尽量放在页面头部&#xff0c;底部有可能不行 原生 <script src"https://cdn.bootcss.com/vConsole/3.3.0/vconsole.min.js"></script> <script>// init vConsolevar vConsole new VConsole();console.log(Hello world)…

ElasticSearch安装与启动

ElasticSearch安装与启动 【服务端安装】 1.1、下载ES压缩包 目前ElasticSearch最新的版本是7.6.2&#xff08;截止2020.4.1&#xff09;&#xff0c;我们选择6.8.1版本&#xff0c;建议使用JDK1.8及以上。 ElasticSearch分为Linux和Window版本&#xff0c;基于我们主要学习…

Linux服务器映射到本地磁盘

内容来自网友博客。 把linux服务器上的文件夹映射到本地作为一个磁盘来访问&#xff0c;步骤如下 一. samba的安装: sudo apt-get install samba // (sudo get temp root auth) sudo apt-get install smbfs //旧版本 sudo apt-get install cifs-utils //新版本 上…

【reactNative混合安卓开发~使用问题持续更】

reactNative混合安卓开发 reactNative开发移动端reactNative界面开发前端init.bat文件部分组件第三方组件解析1、定义theme主题shopify/restyle&#xff1b;菜单导航react-navigation/drawer、react-navigation/native&#xff1b; RN问题记录1、使用theme.js写的公共组件报错&…

【博客699】docker daemon预置iptables剖析

docker daemon预置iptables剖析 没有安装docker的机器&#xff1a;iptables为空&#xff0c;且每个链路的默认policy均为ACCEPT [root~]# iptables-save[root ~]# iptables -t raw -nvL Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)pkts bytes target prot opt …

k8s node 误删除了如何自动创建 csr重新加入集群

worker node 节点当部署晚 kubelet、kube-proxy就会加入集群&#xff0c;如何加入呢&#xff0c; [rootkube-node01 ssl]# mv kubelet-client-2023-08-13-01-19-00.pem kubelet-client-current.pem kubelet.crt kubelet.key /tmp/kubelet [rootkube-node01 ssl]# systemctl da…

Java 正则表达式【基本语法】

基本介绍 首先我们需要了解的是正则表达式中的各种元字符的功能&#xff0c;主要分为以下几种&#xff1a; 限定符选择匹配符分组组合和反向引用符特殊字符字符匹配符定位符 需要注意的是&#xff1a;在 Java正则表达式中&#xff0c;两个斜杠 \\ 相当于其它语言中的一个斜杠…

认识vite

一.了解vite的不同版本的更新 vite1版本是基于vue项目的&#xff0c;无法跨框架使用vite2可以跨框架&#xff08;vue2&#xff0c;vue3&#xff0c;react&#xff09;vite3模板变更&#xff1b;vite cli优化&#xff1b;import.meta.glob API变化&#xff1b;其他vite4主版本主…

Django框架-使用celery(一):django使用celery的通用配置,不受版本影响

目录 一、依赖包情况 二、项目目录结构 2.1、怎么将django的应用创建到apps包 三、celery的配置 2.1、celery_task/celery.py 2.2、celery_task/async_task.py 2.3、celery_task/scheduler_task.py 2.4、utils/check_task.py 四、apps/user中配置相关处理视图 4.1、基本…

【数据结构】复杂度

&#x1f525;博客主页&#xff1a;小王又困了 &#x1f4da;系列专栏&#xff1a;数据结构 &#x1f31f;人之为学&#xff0c;不日近则日退 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 目录 一、什么是数据结构 二、什么是算法 三、算法的效率 四、时间复杂度 4.…

k8s基础

k8s基础 文章目录 k8s基础一、k8s组件二、k8s组件作用1.master节点2.worker node节点 三、K8S创建Pod的工作流程&#xff1f;四、K8S资源对象1.Pod2.Pod控制器3.service && ingress 五、K8S资源配置信息六、K8s部署1.K8S二进制部署2.K8S kubeadm搭建 七、K8s网络八、K8…

人大金仓三大兼容:Oracle迁移无忧

企业级应用早期的架构模式是C/S&#xff08;Client/Server&#xff09;模式&#xff0c;Client做人机交互逻辑的呈现&#xff0c;Sever做业务计算逻辑的实现。这就类似餐馆的运作模式&#xff0c;Client是前台的服务员提供点菜和上菜服务&#xff0c;而Server则是后厨完成菜品的…

设计模式之工厂方法模式(FactoryMethod)

一、概述 定义一个用于创建对象的接口&#xff0c;让子类决定实例化哪一个类。FactoryMethod使一个类的实例化延迟到其子类。 二、适用性 1.当一个类不知道它所必须创建的对象的类的时候。 2.当一个类希望由它的子类来指定它所创建的对象的时候。 3.当类将创建对象的职责委…

vue生命周期解析

Vue.js 是一个流行的前端 JavaScript 框架&#xff0c;它提供了一种用于构建用户界面的渐进式方法。Vue 组件的生命周期钩子函数是一些特定的函数&#xff0c;它们在组件不同阶段被自动调用&#xff0c;可以让你在这些阶段执行特定的逻辑。下面是 Vue 组件的生命周期钩子函数以…

【Spring】核心容器——Setter注入

1、引入 首先引入Spring框架依赖 <dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.1.6.RELEASE</version> </dependency> 在dao层和service层分别准备两个接口以及他…

Stable Diffuion webui Mac版本安装过程

系统环境 操作系统&#xff1a;MacOS Ventura13.5 芯片&#xff1a;Apple M2 Max Python: 3.10 安装前置准备 git clone https://github.com/AUTOMATIC1111/stable-diffusion-webui.git注意事项&#xff1a;修改源码内全部 git clone 链接&#xff0c;设置代理 https://ghpr…

26 | 谷歌应用APP数据分析

基于kaggle公开数据集,对谷歌应用市场的APP情况进行数据探索和分析。 from kaggle: https://www.kaggle.com/lava18/google-play-store-apps 分析思路: 0、数据准备 1、数据概览 2、种类对Rating的影响 3、定价策略 4、因素相关性分析 5、用户评价 6、总结 0、数据准备 (…

Fast SAM与YOLOV8检测模型一起使用实现实例分割以及指定物体分割

Fast SAM与YOLOV8检测模型一起使用 部分源代码在结尾处可获取 晓理紫 1 使用场景 实例分割数据集的获取要比检测数据的获取更加困难&#xff0c;在已有检测模型不想从新标注分割数据进行训练但是又想获取相关物体的mask信息以便从像素级别对物体进行操作&#xff0c;这时就可以…

学习内容散记

git下载网址 &#xff1a;https://registry.npmmirror.com/binary.html?pathgit-for-windows/ error: remote origin already exists 如果你clone下来一个别人的仓库&#xff0c;在此基础上完成你的代码&#xff0c;推送到自己的仓库可能遇到如下问题&#xff1a; error: r…