当前位置: 代码网 > it编程>软件设计>软件测试 > CI+JUnit5并发单测机制创新实践 | 京东物流技术团队

CI+JUnit5并发单测机制创新实践 | 京东物流技术团队

2024年08月06日 软件测试 我要评论
# 一. 现状·问题 针对现如今高并发场景的业务系统,“并发问题” 终归是必不可少的一类(占比接近10%),每次出现问题和事故后,需要耗费大量人力成本排查分析并修复。那如果能在事前尽可能避免岂不是很香? ![](https://mp.toutiao.com/mp/agw/article_material/open_image/get?code=OWFkODY5MDI2ZGUzMmRhM2RiYzgxMTEzYWI1ODhiYmMsMTY5MTcxOTk1NzE5MQ==) # 二. 分析原因 - 当前并发测试多数依赖测试人员进行脚本测试,同时还依...

一. 现状·问题

针对现如今高并发场景的业务系统,“并发问题” 终归是必不可少的一类(占比接近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 {


    @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

如何准备数据?

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

准备测试场景的前置数据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

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

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数

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

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com