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

这篇具有很好参考价值的文章主要介绍了CI+JUnit5并发单测机制创新实践。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一. 现状·问题

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

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

二. 分析原因

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

🤔自我反思

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

三. 采取措施

并发测试前置

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

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

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

四. 实践步骤

前提:配置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 {


    @Resource
    ManualCheckAppService 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++;
    }


    /**
     * 断言最终结果:数据无问题,线程执行无问题
     */
    @AfterAll
    public 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"})
    @Override
    void prepareData()

多场景并发-@Execution(CONCURRENT)

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

👉 核心代码块

public class CheckAppConcurrentTest extends ConcurrentTest {


    @Resource
    ManualCheckAppService manualCheckAppService;
    @Resource
    AutoCheckAppService autoCheckAppService;


    ///////////////////////////////////////////////////////////////////////////
    // 多场景并发
    ///////////////////////////////////////////////////////////////////////////
    @DisplayName("(多场景并发)并发测试【自动确认复核】")
    @Description("与手动复核发生并发场景,期望可能存在业务异常(自定义锁冲突发生的消息)")
    @Execution(CONCURRENT)
    @Test
    public void testAutoCheckBySo() {
        autoCheckAppService.autoCheckBySo(Lists.newArrayList("SO-6_6_601-1492066800186167296"), mockAutoCheckBySoDto());
    }


    @DisplayName("(多场景并发)并发测试【手动确认复核】")
    @Description("与自动复核发生并发场景,期望可能存在业务异常(自定义锁冲突发生的消息)")
    @Execution(CONCURRENT)
    @Test
    public void testConfirmChecked() {
        manualCheckAppService.confirmChecked(mockConfirmCheckedDto());
    }
    /**
     * 断言最终结果:数据无问题
     */
    @AfterAll
    public 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"})
    @Override
    void prepareData() {}


并发单测基类-@Transactional

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

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

👉 核心代码块


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


   /**
     * 清除数据
     */
    @Rollback(false)
    @AfterAll
    public static void clearData(){
        new DatabaseSyncTest().execute("wms_check","wms_check_test");
    }


数据准备-@Sql

如何准备数据?

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

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

准备测试场景的前置数据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流水线(集成测试相对耗时)

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

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

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

https://junit.org/junit5/docs/current/user-guide/#writing-tests-tagging-and-filtering

mvn test -Dtest.mode=parallel

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

配置IDEA-本地测试

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

—— 只运行并发测试用例

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

执行结果

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

单接口并发单测

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

多场景并发单测

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

五. 效能提升

5.1需求交付效率提升

5.1.1降低测试周期阶段时长

2022-02月实践后

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

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

2022-Q1

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

2022-Q2

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

2022-Q3

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

2022-Q4

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

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

5.1.2缩短需求交付全周期

2022-02月实践后

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

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

5.2人效提升

5.2.1提升验证全面性

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

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

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

5.2.2降低测试人力成本

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

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

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

5.2.3提升需求吞吐量

2022-02月实践后

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

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

5.3过程质量提升

5.3.1降低问题的发生概率

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

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

5.3.2减少线上问题数

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

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

5.3.2减少Bug数

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

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

作者:京东物流 周奕儒

来源:京东云开发者社区 自猿其说Tech文章来源地址https://www.toymoban.com/news/detail-639902.html

到了这里,关于CI+JUnit5并发单测机制创新实践的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • JUnit5用户手册~并行执行

    两种运行模式 SAME_THREAD: 默认的,测试方法在同一个线程 CONCURRENT: 并行执行,除非有资源锁 junit-platform.properties 配置参数 配置所有测试方法都并行 @Execution(CONCURRENT) 指定类或方法并行 配置top-level类并行,测试方法同一线程执行 配置top-level类串行,测试方法并行  相关参数

    2023年04月18日
    浏览(34)
  • JUnit5-单元测试操作详解

    JUnit 5是JUnit测试框架的下一个主要版本,用于编写和运行单元测试。 与以前版本的 JUnit 不同,JUnit 5 由来自三个不同子项目的多个不同模块组成。 JUnit 5 =  JUnit 平台 +  JUnit Jupiter  +  JUnit Vintage JUnit 平台 是在 JVM 上启动测试框架的基础。它还定义了 TestEngine 用于开发在平台

    2024年04月10日
    浏览(53)
  • Springboot测试篇 | Junit5

    PS: 文章将持续更新修订 简单介绍吧本篇文章将从Junit5到一些Springboot的特殊场景测试配置。 JUnit5简单介绍:Spring Boot2.2.0版本开始引入JUnit5作为单元测试默认库,作为最新版本的JUnit框架,JUnit5与之前版本的Junit框架有很大的不同,由三个不同子项目的几个不同模块组成。 JU

    2023年04月08日
    浏览(28)
  • 【单元测试】如何使用 JUnit5 框架?

      Junit5是一个用于在Java平台上进行单元测试的框架。JUnit 5 框架主要由三部分组成:JUnit Platform、JUnit Jupiter 和 JUnit Vintage。 JUnit Platform:定义了测试引擎的 API,是 JVM 上用于启动测试框架的基础服务,支持通过 IDE、构建工具、命令行等方式运行单元测试。 JUnit Jupiter:包含

    2024年04月10日
    浏览(46)
  • Junit5+Mockito单元测试详解

    1.宏观层面:AIR原则 A:Automatic(自动化) 全自动执行,输出结果无需人工检查,而是通过断言验证。 I:Independent(独立性) 分层测试,各层之间不相互依赖。 R:Repeatable(可重复) 可重复执行,不受外部环境( 网络、服务、中间件等)影响。 2.微观层面:BCDE原则 B: Bord

    2024年01月17日
    浏览(48)
  • 【测试学习】Junit5的简单使用

    目录 🌟需要知道: 🌟Junit学习 🌈1、常用的注解 🌈2、测试用例的执行顺序 🌈3、参数化 🌈4、断言 🌈5、测试套件 问题1:Selenium与Junit之间的关系?         就像雷锋和雷峰塔的关系,就是没关系。 问题2:为什么学习了Selenium之后还要学习Junit?         Junit是针对

    2024年02月16日
    浏览(33)
  • TestNG和Junit5测试框架梳理

    1. testNG优势 2. testNG常用注解 3. testNG中@Test有哪些参数 举例: 4.  在 TestNG 中实现参数化测试 以Yaml文件为例 创建一个Yaml文件testdata.yaml 获取测试数据及使用 5. 执行顺序 6. testNG断言 7. 失败重试 1. junit5的优势 2. 常用注解 3. TestFactory使用示例 TestFactory其实就是参数化执行同一条

    2024年02月13日
    浏览(37)
  • 单元测试junit(原始版本、Spring Boot各版本、junit5)使用介绍

    🍓 简介:java系列技术分享(👉持续更新中…🔥) 🍓 初衷:一起学习、一起进步、坚持不懈 🍓 如果文章内容有误与您的想法不一致,欢迎大家在评论区指正🙏 🍓 希望这篇文章对你有所帮助,欢迎点赞 👍 收藏 ⭐留言 📝 🍓 更多文章请点击 单元测试 junit各版本 使用介绍 官

    2023年04月16日
    浏览(44)
  • SpringBoot2---单元测试(Junit5)(1)

    org.junit.vintage junit-vintage-engine test org.hamcrest hamcrest-core org.springframework.boot spring-boot-starter-test test 现在版本: @SpringBootTest class Boot05WebAdminApplicationTests { @Test void contextLoads() { } } 以前: @SpringBootTest + @RunWith(SpringRunner.class) SpringBoot整合Junit以后。 编写测试方法:@Test标注(注意需要

    2024年04月29日
    浏览(44)
  • SpringBoot单元测试--Mockito+Junit5框架使用

    作为程序员为了提前发现代码bug,优化代码; 通常我们写完某个功能模块代码后都需要写单元测试对代码块进行测试(特别是敏捷开发中);Java项目最常用的单元测试框架即为Junit(目前最新版本为Junit5),SpringBoot本身也整合了该框架。在写单元测试时代码块中的调到第三方接口方

    2024年02月02日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包