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单元测试框架详解

    前面我们学习了Selenium自动化测试框架,但是有的时候测试用例会很多,我们需要一个工具来管理这些测试用例,而Junit就是一个很好的管理工具,简单点来说,Junit就是一个针对Java的单元测试框架; 目录 一. 关于Junit5 二. Junit使用 2.1 添加Maven依赖 2.2 注解 2.3 断言 2.4 套件

    2024年02月06日
    浏览(46)
  • Springboot测试篇 | Junit5

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

    2023年04月08日
    浏览(26)
  • JUnit5用户手册~并行执行

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

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

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

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

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

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

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

    2024年02月16日
    浏览(30)
  • 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日
    浏览(35)
  • 单元测试junit(原始版本、Spring Boot各版本、junit5)使用介绍

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

    2023年04月16日
    浏览(41)
  • 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日
    浏览(42)
  • 静态方法 单元测试 springboot+mokito+junit5

    CodecUtils的方法是静态类,使用@InjectMocks不能有用,因为这个注解只能用于非静态的对象。 想要为静态方法写单元测试,可以使用Mockito.mockStatic(Class classToMock)方法,它可以返回一个MockedStatic对象,用于模拟静态方法的调用。 1.导入依赖 2.单元测试 可以参考如下地址,了解如何

    2024年04月25日
    浏览(46)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包