API接口DTO测试数据构造的一个方式

news2024/11/25 22:41:19

自动化测试中,经常需要构造请求参数,例如JSON格式的参数,简单的好说,可以手工修改或是用 Postman、Jmeter 等工具结合简单的代码进行处理, 但当数据传输对象(DTO)很复杂,部分字段依赖性很强、强校验时,就不能单靠手工修改来满足自动化测试需求了,这时候就需要利用代码来解决了,本文给大家分享一种构造测试数据的方法 ====> 通过枚举、1:1 模仿前端请求逻辑接口以及程序解析数据来构造复杂 JSON。

背景

本次示例接口为业务开发中常见的新增保存接口,参数相对比较简单,为方便理解,我这里画个草图讲下大概的业务逻辑:

图片

总体,业务逻辑 为创建采购计划选择不同的采购方式 并发起采购过程,采购过程由节点 1线性流程一直操作到节点 N。其中需特殊说明的:

采购方式、采购类别、议标类型、采购过程节点(以下简称:节点)均具备各自的相关属性(面向对象),属于配置数据(其他业务模块已配置),且具备关联关系:采购类别关联多个采购方式;采购方式关联多个采购节点。

整体后端环境为微服务,服务交互采用 Feign 调用,供应商类别取自其他服务(供应商),项目信息、人员信息取自外部服务(用户中心、基础平台)

整体分析过程重点在分析构造数据,忽略涉及的权限、参数配置、各节点 OA 回调处理等问题,以及具体信息已脱敏。

开发语言采用Java

整体思路

一、 分析接口(业务逻辑)

首先,根据编写功能测试用例时,对业务逻辑的理解,以及后端提供的接口文档来分析

接口文档

接口名称:新增计划 (草稿)

请求方式:POST

接口路径:/***/purchasePlan/save

请求参数:

Headers:

参数含义参数值是否必须
Content-Typeapplication/json
AuthorizationBearer ${token}

Body:

{
    "id":"",//id新增不传,编辑传
    "planName":"测试计划",//计划名称
    "projectId":159211111111111193569,//项目id
    "projectName":"云贵川渝片区",//项目名称
    "estimatedAmount":2530000000.99,//预计签约金额
    "purchaseCategoryId":"1599605780716195842",//采购类别id
    "purchaseCategoryName":"采购类别111",//采购类别name
    "purchaseMethodCode":"inviteBid",//采购方式code
    "purchaseMethodName":"邀请招标",  //采购方式name
    "providerCategoryId":"159706qqq2812802",//供应商类别
    "providerCategoryName":"建筑方案设计",//供应商类别名称
    "recordTime":"2022-12-01 19:00:15",//入场时间
    "planStartTime":"2022-11-25 12:00:15",//计划开始时间
    "planFinishTime":"2023-03-25 13:00:15",//计划完成时间
    "bidTypeName":"议标类型1",//议标name
    "bidTypeValue":"bidType01",//议标值
    "chargeUserId":14341111111111111681,//招标经办人id
    "chargeUserCode":"ATE002",//招标经办人code
    "chargeUserName":"NHATE-员工B",//招标经办人name
    "temporaryPlan":true,//否临时采购计划,1是0否
    "supplementBid":true,//是否补标,1是0否
    "ifStrategic":true,//用于战采协议
    "if2n":true,//是否启用2n+1控制
    "ifSignMoneyControl":false,//是否启用预计签约金额控制
    "ifEvaluationStaffOdd":false,//技术标评标人员是否奇数
    "ifEvaluationStaffGteThree":false,//技术标评标人员是否大于等于3
    "ifControlPriceFloor":true,//是否控制价下限控制
    "nodes":[//节点list
        {
            "id":"",//id新增不传,编辑传
            "name":"招标策划",//节点名称
            "planId":"",//计划id
            "purchaseMethodCode":"inviteBid",//采购关联方式编码
            "purchaseMethodNodeCode":"zbch",//采购关联方式节点编码
            "startTime":"2022-12-01 19:00:15",//计划开始时间
            "finishTime":"2022-12-30 19:00:15",//计划完成时间
            "realFinishTime":"",//实际完成时间
            "chargeUserId":1434111111111150722,//责任人id
            "chargeUserCode":"ATE001",//责任usercode
            "chargeUserName":"NHATE-员工A",//责任人name
            "chargeOrg":"责任部门1",//责任部门
            "chargeOrgValue":"responsibleDept02",//部门code
            "ifNecessity":true,//是否必须
            "sort":1//排序
        }
    ]}

返回数据

{
  "body":"1599941367004532737",
  "code":"0000",
  "message":"操作成功",
  "status":true}

分析方法

1.分析参数结构,嵌套层级关系

首先,请求参数 json 外层是 采购计划对象相关属性,都是一对一(key-value)的关系

其次,采购过程节点(nodes)字段 的数据类型是列表(List<采购过程节点对象>),即该字段会存在多个,根据业务理解,节点数量=采购方式下配置的节点数量,且每个节点都有不同的节点名称定义(枚举、数据库配置)

2.找出哪些字段不具备依赖性,该部分字段可以通过 随机、写死、自定义方式设置 value,该部分字段处理较简单

计划名称、预计签约金额、各种时间、否临时采购计划、是否补标 字段不具备强校验性

,该部分字段优先采用调用接口形式取值,如果处理起来麻烦,可采用枚举形式,在限定范围内取值

项目 id、name,供应商类别 id、name 取自不同外部服务(项目权限、供应商库),影响后续业务逻辑。

采购类别 id、name,采购方式 id、name 取自配置,具备关联性,影响后续业务逻辑

议标类型 name、value 取自字典项配置,具备弱关联性,影响较小,但为了数据合法性,仍需要取真实有效的配置

是否用于战采协议、是否启用 2n+1 控制、是否启用预计签约金额控制、技术标评标人员是否奇数、技术标评标人员是否大于等于 3、是否控制价下限控制 取自采购方式中配置的自有属性,影响后续业务逻辑(存在冗余字段),为数据合法性,需取真实配置有效的配置

招标经办人、各节点责任人 涉及 userId、userCode、userName 取自用户中心账号体系(已提前配置测试账号)

各节点中 节点名称、采购关联方式编码、采购关联方式节点编码、责任部门 code、name、是否必须、序号 均取自节点配置数据,影响后续业务逻辑,需取真实有效配置

二、 编码过程

01 首先创建数据DTO对象类

或者直接复制后端代码文件,这属于Java 基础 - 面向对象,本文就不细介绍,直接贴代码:

PurchasePlanDto 采购计划对象:

@Datapublic class PurchasePlanDto implements Serializable {

    private Long id;

    @NotNull(message = "计划名称不能为空")
    private String planName;

    @NotNull(message = "项目不能为空")
    private Long projectId;

    private String projectName;
    /**
     * 预计签约金额,元
     */
    @NotNull(message = "预计签约金额不能为空")
    @DecimalMax(value = "99999999999999.99", message = "预计签约金额超限")
    private BigDecimal estimatedAmount;
    /**
     * 采购类别id
     */
    @NotNull(message = "采购类别不能为空")
    private Long purchaseCategoryId;
    /**
     * 采购类别名称
     */
    private String purchaseCategoryName;
    /**
     * 采购方式编码
     */
    @NotBlank(message = "采购方式不能为空")
    private String purchaseMethodCode;
    /**
     * 采购方式名称
     */
    private String purchaseMethodName;
    /**
     * 供应商类别编码
     */
    @NotBlank(message = "供应商类别不能为空")
    private String providerCategoryId;
    /**
     * 供应商类别名称
     */
    private String providerCategoryName;
    /**
     * 入场时间
     */
    private Date recordTime;
    /**
     * 计划开始时间
     */
    @NotNull(message = "计划开始时间不能为空")
    private Date planStartTime;
    /**
     * 计划完成时间
     */
    @NotNull(message = "计划完成时间不能为空")
    private Date planFinishTime;
    /**
     * 经办人id
     */
    @NotNull(message = "经办人不能为空")
    private Long chargeUserId;
    /**
     * 经办人账号
     */
    private String chargeUserCode;
    /**
     * 经办人姓名
     */
    private String chargeUserName;
    /**
     * 议标名称
     */
    private String bidTypeName;
    /**
     * 议标值
     */
    private String bidTypeValue;
    /**
     * 是否临时采购计划,1是0否
     */
    private Boolean temporaryPlan;
    /**
     * 是否补标,1是0否
     */
    private Boolean supplementBid;
    /**
     * 用于战采协议
     */
    private Boolean ifStrategic;
    /**
     * 是否启用2n+1控制
     */
    private Boolean ifTn;
    /**
     * 是否启用预计签约金额控制
     */
    private Boolean ifSignMoneyControl;
    /**
     * 技术标评标人员是否奇数
     */
    private Boolean ifEvaluationStaffOdd;
    /**
     * 技术标评标人员是否大于等于3
     */
    private Boolean ifEvaluationStaffGteThree;
    /**
     * 是否控制价下限控制
     */
    private Boolean ifControlPriceFloor;
    /**
     * 节点list
     */
    @Valid
    @NotEmpty(message = "节点不能为空")
    private List<PlanNodeDto> nodes;}

PlanNodeDto 采购过程节点对象:

@Datapublic class PlanNodeDto implements Serializable {

    private Long id;
    /**
     * 名称
     */
    private String name;
    /**
     * 计划id
     */
    private Long planId;
    /**
     * 采购关联方式编码
     */
    private String purchaseMethodCode;
    /**
     * 采购关联方式节点编码
     */
    private String purchaseMethodNodeCode;
    /**
     * 计划开始时间
     */
    @NotNull(message = "计划开始日期不能为空")
    private Date startTime;
    /**
     * 计划完成时间
     */
    @NotNull(message = "计划完成日期不能为空")
    private Date finishTime;
    /**
     * 实际完成时间
     */
    private Date realFinishTime;
    /**
     * 负责人id
     */
    @NotNull(message = "责任人不能为空")
    private Long chargeUserId;
    /**
     * 负责人账号
     */
    private String chargeUserCode;
    /**
     * 负责人姓名
     */
    private String chargeUserName;
    /**
     * 责任部门
     */
    private String chargeOrg;
    /**
     * 责任部门值
     */
    private String chargeOrgValue;
    /**
     * 是否必要节点
     */
    private Boolean ifNecessity;
    /**
     * 排序
     */
    private Integer sort;}

接着,搭建逻辑框架,工作量梳理后按 TODO 分解清晰

public class PlanTest extends TestBase {

    private static final ReportLog reportLog = new ReportLog(PlanTest.class);

    //获取系统token 封装成请求头
    public Map<String,String> header = getBackTokenHeader("ATE***","****");

    @Test(description = "TestNG 测试- 提交保存采购计划")
    void testAddPlan() {

        //创建入参 采购计划DTO对象
        PurchasePlanDto planDto = buildPlanDto();
        //提交保存计划接口
        String rs = HttpUtils.doPost(HostLH.TEST_HOST.concat(ApiBid.PLAN_SAVE), header, JSONObject.toJSONString(planDto));
        //输出响应结果日志信息
        reportLog.info(" PLAN_SAVE ====> {}",JSONObject.parseObject(rs));

        //省略。。。后续验证逻辑 或 其他目的性测试
    }

    //创建采购计划DTO PurchasePlanDto
    PurchasePlanDto buildPlanDto() {
        PurchasePlanDto planDto = new PurchasePlanDto();

        //TODO 设置 不具备依赖性的字段值

        //TODO 设置项目id、name

        //TODO 设置供应商类别id、name

        //TODO 设置采购类别id、name,采购方式id、name 及 是否用于战采协议、是否启用2n+1控制、是否启用预计签约金额控制、技术标评标人员是否奇数、技术标评标人员是否大于等于3、是否控制价下限控制

        //TODO 设置招标经办人

        //采购节点List
        planDto.setNodes(buildPlanNodeDtoList());
        return planDto;
    }

    // 创建采购计划(过程)节点DTO List<PlanNodeDto>
    List<PlanNodeDto> buildPlanNodeDtoList() {
        List<PlanNodeDto> nodeDtoList = new ArrayList<>();

        //TODO 循环遍历 设置 各节点属性值-节点名称、采购关联方式编码、采购关联方式节点编码、责任部门code、name、是否必须、序号

        //TODO 循环遍历 设置 各节点责任人
        return nodeDtoList;
    }}

02 编写创建采购计划(过程)节点DTO逻辑

怎么开始呢? ==> 这里作者 建议从里层向外层,先简单后复杂进行编写 即优先编写buildPlanNodeDtoList()方法

我们分析下,既然节点有多个,我们方法里为了优雅且快捷,肯定采用循环的办法去设置,但是这个循环次数(节点数量)方法内部是没法感知的,即要么设置全局变量(也要定义循环次数),要么由入参告诉我们(作者采用)。那什么样的入参能告诉我们呢,通过业务理解知道,不同的采购方式,对应设置了具体的采购节点,那么我们可以和前端逻辑一致,页面会先选取采购方式,即拿到了采购方式的编码,那我们用这个编码去调用接口获取采购方式详情信息 - 并获取其关联的节点信息,方法扩展入参为buildPlanNodeDtoList(String purchaseMethod)。

先用 postman 或其他工具调用 API:根据采购方式编码获取采购方式详细信息,查看响应 body 数据结构,分析数据层级结构、哪些值是我们需要的、以及如何取目标值(后续遇到均采用这种模式去分析):

{
    "body":{
        "code":"inviteBid",
        "createDate":null,
        "createUser":"",
        "delFlag":false,
        "id":1,
        "ifControlPriceFloor":true,
        "ifEvaluationStaffGteThree":true,
        "ifEvaluationStaffOdd":true,
        "ifSignMoneyControl":true,
        "ifStrategic":true,
        "ifTn":true,
        "name":"邀请招标",
        "purchaseMethodNodes":[
            {
                "code":"zbch",
                "createDate":null,
                "createUser":"",
                "delFlag":false,
                "id":1,
                "ifHide":false,
                "ifNecessity":true,
                "ifSyncAgent":true,
                "name":"招标策划",
                "purchaseMethodCode":"inviteBid",
                "purchaseMethodId":1,
                "responsibleDept":"responsibleDept01",
                "responsibleDeptName":"责任部门_默认",
                "sort":1,
                "updateDate":null,
                "updateUser":""
            }
            //...此处省略多个节点
        ],
        "remark":"说明",
        "status":true,
        "updateDate":"2022-12-05 14:36:17",
        "updateUser":"ATE001"
    },
    "code":"0000",
    "message":"操作成功",
    "status":true}

我们发现 返回的节点purchaseMethodNodes中有我们需要的所有信息,以及外层还有采购方式的 Boolean 值涉及的字段值,即我们的目标就是去解析这个 json 循环遍历拿到对应的节点各属性值

即:

//创建采购计划(过程)节点DTO List<PlanNodeDto>
   List<PlanNodeDto> buildPlanNodeDtoList(String purchaseMethod) {

       //调用接口(API功能-根据采购方式编码获取详情信息-含关联的采购节点信息)
       String apiUrl = String.format(HostLH.TEST_HOST.concat(ApiBid.PURCHASE_METHOD_DETAIL), purchaseMethod);
       String rs = HttpUtils.doGet(apiUrl, header);

       //解析响应json拿到所有的节点List
       List<JSONObject> purchaseMethodNodes = JSON.parseObject(rs).getJSONObject("body").getJSONArray("purchaseMethodNodes").toJavaList(JSONObject.class);

       //基本逻辑:完成时间必须 > 开始时间, 后节点开始时间需 > 前节点完成时间 这里使用原子类来控制每次加30天
       DateTime now = DateUtil.date();
       AtomicInteger loopInt = new AtomicInteger(1);

       List<PlanNodeDto> nodeDtoList = new ArrayList<>(purchaseMethodNodes.size());

       purchaseMethodNodes.forEach(node -> {

           PlanNodeDto nodeDto = new PlanNodeDto();

           //取json中各字段值,因大部分字段名存在差异(各接口定义、业务出发点不一样,属正常现象),单独每个字段设置值
           nodeDto.setName(node.getString("name"));                                //节点名称
           nodeDto.setPurchaseMethodCode(node.getString("purchaseMethodCode"));    //关联采购方式编码
           nodeDto.setPurchaseMethodNodeCode(node.getString("code"));              //关联采购方式节点编码
           //责任部门
           nodeDto.setChargeOrg(node.getString("responsibleDept"));
           nodeDto.setChargeOrgValue(node.getString("responsibleDeptName"));
           nodeDto.setIfNecessity(node.getBoolean("ifNecessity"));                         //是否必要节点
           nodeDto.setSort(node.getInteger("sort"));                                       //排序

           nodeDto.setStartTime(DateUtil.offsetDay(now,loopInt.getAndAdd(30)));           //节点计划开始时间
           nodeDto.setFinishTime(DateUtil.offsetDay(now,loopInt.getAndAdd(30)));          //节点计划完成时间

           //TODO 循环遍历 设置 各节点责任人


           nodeDtoList.add(nodeDto);

       });

       return nodeDtoList;

   }

接下来,需要设置节点责任人了。 采用策略:每个节点独立设置一个账号,与真实业务场景保持一致,每个测试账号编码与节点编码保持同步,进行拼接(ATE 拼接编码)PS:ATE — Auto Test Engineer 首字母
出于自动化需要,测试用户账号已缩小范围,我们不需要从数据库中取(本文均未连接数据库,后续章节会举例 连接库来辅助测试),即采用枚举方式将账号定义写死,后续业务测试需要账号则从该枚举中取

测试账号枚举类 TestUserEnum

@Getter@AllArgsConstructorpublic enum TestUserEnum {
    ATEzbch("XXXX19962050001","ATEzbch","T-****","13655666001","1","zbch"),
    ATEct("XXXX19962050002","ATEct","T-****","13655666002","1","chut"),
    ATEjswjsb("XXXX19962050003","ATEjswjsb","T-****","13655666003","1","jswjsb"),
    ATEjswjxg("XXXX19912050001","ATEjswjxg","T-****","13655616665","1","jswjxg"),
    ATEjxwjzj("XXXX19922050001","ATEjxwjzj","T-****","13655626665","1","jswjzj"),
    ATEcqd("XXXX19962050004","ATEcqd","T-****","13655666004","1","cqd"),
    ATEzbwj("XXXX19962050005","ATEzbwj","T-****","13655666005","1","zbwj"),
    ATEdwrw("XXXX19962050006","ATEdwrw","T-****","13655666006","1","dwrw"),
    ATEfb("XXXX19962050007","ATEfb","T-****","13655666007","1","fab"),
    ATEdy("XXXX19962050008","ATEdy","T-****","13655666008","1","day"),
    ATEhb("XXXX19962050009","ATEhb","T-****","13655666009","1","huib"),
    ATEpjsb("XXXX19962050010","ATEpjsb","T-****","13655666010","1","pjsb"),
    ATEpswb("XXXX19962050011","ATEpswb","T-****","13655666011","1","pswb"),
    ATEelhb("XXXX19962050012","ATEelhb","T-****","13655666012","1","huib2"),
    ATEelpb("XXXX19962050013","ATEelpb","T-****","13655666013","1","pswb2"),
    ATEdb("XXXX19962050014","ATEdb","T-****","13655666014","1","dingb"),
    ATEfqd("XXXX19962050015","ATEfqd","T-****","13655666015","1",""),
    ATEqy("XXXX19962050016","ATEqy","T-****","13655666016","1","qiany"),
    ATEhtjd("XXXX19962050017","ATEhtjd","T-****","13655666017","1",""),

    ATEzbjbr("XXXX19932050001","ATEzbjbr","T-****","13655636665","2",""),

    ATEswpbr1("XXXX19962150001","ATEswpbr1","T-****1","13655666018","3",""),
    ATEswpbr2("XXXX19962150002","ATEswpbr2","T-****2","13655666019","3",""),
    ATEswpbr3("XXXX19962150003","ATEswpbr3","T-****3","13655666020","3",""),
    ATEswpbr4("XXXX19962150004","ATEswpbr4","T-****4","13655666021","3",""),
    ATEswpbr5("XXXX19962150005","ATEswpbr5","T-****5","13655666022","3",""),
    ATEswpbr6("XXXX19962150006","ATEswpbr6","T-****6","13655666023","3",""),
    ATEswpbr7("XXXX19962150007","ATEswpbr7","T-****7","13655666024","3",""),
    ATEswpbr8("XXXX19962150008","ATEswpbr8","T-****8","13655666025","3",""),
    ATEswpbr9("XXXX19962150009","ATEswpbr9","T-****9","13655666026","3",""),
    ATEjspbr1("XXXX19962250001","ATEjspbr1","T-****1","13655666027","3",""),
    ATEjspbr2("XXXX19962250002","ATEjspbr2","T-****2","13655666028","3",""),
    ATEjspbr3("XXXX19962250003","ATEjspbr3","T-****3","13655666029","3",""),
    ATEjspbr4("XXXX19962250004","ATEjspbr4","T-****4","13655666030","3",""),
    ATEjspbr5("XXXX19962250005","ATEjspbr5","T-****5","13655666031","3",""),
    ATEjspbr6("XXXX19962250006","ATEjspbr6","T-****6","13655666032","3",""),
    ATEjspbr7("XXXX19962250007","ATEjspbr7","T-****7","13655666033","3",""),
    ATEjspbr8("XXXX19962250008","ATEjspbr8","T-****8","13655666034","3",""),
    ATEjspbr9("XXXX19962250009","ATEjspbr9","T-****9","13655666035","3",""),

    ;


    private String userId;
    private String username;
    private String realname;
    private String phone;
    // 1-采购过程节点经办人  2-计划经办人(采购过程经办人)(采购外层)   3-其他参与人
    private String type;
    private String bidNodeCode;}

账号源设置好了,为了方便,我们将所有测试账号转成List对象,并定义成全局变量,提供给后续使用:

//全局变量public List<BidUcUser> allTestUsers = Arrays.stream(TestUserEnum.values()).map(u -> {
        BidUcUser bidUcUser = new BidUcUser();
        bidUcUser.setType(u.getType());
        bidUcUser.setBidNodeCode(u.getBidNodeCode());
        bidUcUser.setUserId(Long.parseLong(u.getUserId()));
        bidUcUser.setRealName(u.getRealname());
        bidUcUser.setUserName(u.getUsername());
        return bidUcUser;
    }).collect(Collectors.toList());

这里涉及 User 对象,UcUser与BidUcUser,其中UcUser是示例系统用户中心用户对象(注意,这里对象是测试自己定义的,与后端的区分开,这里是测试需要的属性组成的对象),BidUcUser在UcUser的基础上扩展了本次测试需要的属性(节点 code-为了循环中与节点 code 进行识别绑定,type-测试定义的类型)Java 基础 - 继承父类属性扩展子类属性

接下来在循环中设置责任人:

//节点责任人(经办人)BidUcUser chargeUser = allTestUsers.stream()
    .filter(u -> node.getString("code").equals(u.getBidNodeCode()) && "1".equals(u.getType()))
        .findFirst().orElse(null);if (null == chargeUser) {
    throw new BusinessException("测试账号节点code未匹配,请检查配置!");}nodeDto.setChargeUserId(chargeUser.getUserId());nodeDto.setChargeUserName(chargeUser.getRealName());nodeDto.setChargeUserCode(chargeUser.getUserName());

设置其他业务逻辑

if (isDeletedNodes) {
    //删除不必要节点
    purchaseMethodNodes = purchaseMethodNodes.stream().filter(json -> json.getBoolean("ifNecessity")).collect(Collectors.toList());}

03 编写创建采购计划 DTO 逻辑

根据先前的 TODO 任务,我们先易后难,一个一个拆解:

不影响主逻辑的边缘字段,采用随机处理,符合真实业务即可,甚至不传参也可以

//设置 不具备依赖性的字段值planDto.setEstimatedAmount(new BigDecimal(RandomUtil.randomDouble(99999999999999.99,2, RoundingMode.HALF_UP)).setScale(2,RoundingMode.HALF_UP));    //预计签约金额,元planDto.setRecordTime(RandomUtil.randomDay(-1000,0));                             //入场时间planDto.setPlanStartTime(RandomUtil.randomDay(-100,0));                           //计划开始时间planDto.setPlanFinishTime(RandomUtil.randomDay(500,1000));                        //计划完成时间

经办人根据测试需要写死

//设置 招标经办人TestUserEnum chargeUser = TestUserEnum.ATEzbjbr;planDto.setChargeUserId(Long.parseLong(chargeUser.getUserId()));planDto.setChargeUserName(chargeUser.getRealname());planDto.setChargeUserCode(chargeUser.getUsername());

设置采购类别属性 id、name 产品原型页面操作逻辑先选类别、再选类别关联的采购方式,为了编码快捷,采用内部类 - 对象模式,创建采购类别对象、采购方式对象-----PS:测试创建的对象 非后端定义的对象

@Data@AllArgsConstructor@NoArgsConstructor@Builderpublic static class PurchaseCategory{
    private String PurchaseCategoryName;
    private String PurchaseCategoryId;}@Data@AllArgsConstructor@NoArgsConstructor@Builderpublic static class PurchaseMethod{
    private String PurchaseMethodName;
    private String PurchaseMethodCode;
    private Boolean ifControlPriceFloor;
    private Boolean ifEvaluationStaffGteThree;
    private Boolean ifEvaluationStaffOdd;
    private Boolean ifSignMoneyControl;
    private Boolean ifStrategic;
    private Boolean ifTn;}

考虑到可以根据需要定义目标采购类别,即计划创建方法buildPlanDto()中增加采购类别入参 >> buildPlanDto(String purchaseCategoryName)

//采购类别//调用API-采购类别列表 查询启用状态的采购类别ListString rs = HttpUtils.doGet(HostLH.TEST_HOST.concat(ApiBid.PURCHASE_CATEGORY_LIST), header, "status=true");List<JSONObject> categoryList = JSON.parseObject(rs).getJSONArray("body").toJavaList(JSONObject.class);PlanTest.PurchaseCategory targetCategory = null;//获取目标采购类别 若指定,则匹配if (ObjectUtil.isNotEmpty(purchaseCategoryName)) {
    targetCategory = categoryList.stream().filter(cate -> purchaseCategoryName.equals(cate.getString("name")))
            .map(cate -> {
                return PurchaseCategory.builder()
                        .purchaseCategoryName(cate.getString("name"))
                        .purchaseCategoryId(cate.getString("id"))
                        .build();
            }).findAny().orElse(null);}//若匹配不到或不指定,则写死类别(采购类别-所有采购方式)if (null == targetCategory) {
    targetCategory = PurchaseCategory.builder().purchaseCategoryId("1600406567327461378").purchaseCategoryName("采购类别-所有采购方式").build();}planDto.setPurchaseCategoryName(targetCategory.getPurchaseCategoryName());planDto.setPurchaseCategoryId(Long.parseLong(targetCategory.getPurchaseCategoryId()));

设置采购方式属性 与类别相似,接收调用者入参,提供匹配 方法改为buildPlanDto(String purchaseCategoryName,String purchaseMethodName)

//采购方式//调用API-根据ID获取采购方式详情   ID来自类别targetCategoryrs = HttpUtils.doGet(HostLH.TEST_HOST.concat(String.format(ApiBid.PURCHASE_CATEGORY_METHOD, targetCategory.getPurchaseCategoryId())), header);List<JSONObject> methodList = JSON.parseObject(rs).getJSONArray("body").toJavaList(JSONObject.class);PlanTest.PurchaseMethod targetMethod = null;//获取采购类别关联的 采购方式  若指定,则匹配if (ObjectUtil.isNotEmpty(purchaseMethodName)) {
    targetMethod = methodList.stream().filter(method -> purchaseMethodName.equals(method.getString("name")))
            .map(method -> {
                return PlanTest.PurchaseMethod.builder()
                        .purchaseMethodCode(method.getString("code"))
                        .purchaseMethodName(method.getString("name"))
                        .ifControlPriceFloor(method.getBoolean("ifControlPriceFloor"))
                        .ifEvaluationStaffGteThree(method.getBoolean("ifEvaluationStaffGteThree"))
                        .ifEvaluationStaffOdd(method.getBoolean("ifEvaluationStaffOdd"))
                        .ifSignMoneyControl(method.getBoolean("ifSignMoneyControl"))
                        .ifStrategic(method.getBoolean("ifStrategic"))
                        .ifTn(method.getBoolean("ifTn"))
                        .build();
            }).findAny().orElse(null);}//若匹配不到或不指定,则写死采购方式(邀请招标)if (null == targetMethod) {
    targetMethod = PurchaseMethod.builder()
            .purchaseMethodName("邀请招标")
            .purchaseMethodCode("inviteBid")
            .ifControlPriceFloor(true)
            .ifEvaluationStaffGteThree(true)
            .ifEvaluationStaffOdd(true)
            .ifSignMoneyControl(true)
            .ifStrategic(true)
            .ifTn(true)
            .build();}//浅拷贝 targetMethod所有属性到planDtoBeanUtil.copyProperties(targetMethod,planDto);

设置议标类型

//议标类型//调用API-字典项列表接口获取listrs = HttpUtils.doGet(HostLH.TEST_HOST.concat(ApiBid.BIDDICT_LIST), header, "type=bidType&status=true");List<JSONObject> bidDictList = JSONObject.parseObject(rs).getJSONArray("body").toJavaList(JSONObject.class);//优先取默认的JSONObject bidType = bidDictList.stream().filter(json -> json.getBoolean("ifDefault")).findAny().orElse(null);if (null == bidType) {
    //若不存在默认的,则随机取
    bidType = bidDictList.get(RandomUtil.randomInt(bidDictList.size()));}planDto.setBidTypeName(bidType.getString("name"));planDto.setBidTypeValue(bidType.getString("value"));    

设置项目 id、name

//筛选项目分期String rs = HttpUtils.doGet(HostLH.TEST_HOST.concat(ApiBid.COMMON_GET_USER_TREE), header);reportLog.info("{}", JSONObject.parseObject(rs));

通过获取项目分期树状 JSON 发现,项目组织层级较深,最高达 8 层。目标 JSON 如图所示:

在这里插入图片描述

图片

为符合真实场景,如果要取目标值(逻辑要求只取最末级,且只取末级的 type 为PROJECT与STAGE),这最好的办法是编写树的遍历算法,通过递归获取,且取出来后,需提供根据入参匹配的功能。考虑到目标字段较少,只有项目 id、name,取值方式多种多样,且正常业务测试,用不上这么多项目,如果通过递归取,从编码效率上低于连接数据库取值,本文采用最简单的办法,不用手动写死,仍然采用枚举类的方法,将备用测试的项目分期写死在代码中,这样编码更快捷。

项目分期枚举 ProjectEnum:

@user29@user30publicenumProjectEnum{

    STAGE_001("1601124116981645313","猪产业-黑吉辽蒙法人公司0森林绿化项目2-无分期"),
    STAGE_002("1601128411869249538","猪产业-黑吉辽蒙法人公司1飞机购买项目2一期"),
    STAGE_003("1601128413421142017","猪产业-黑吉辽蒙法人公司1飞机购买项目2二期"),
    STAGE_004("1601128127541575681","禽产业-山东法人公司0飞机购买项目3一期"),
    STAGE_005("1601128296962097153","禽产业-山东法人公司1精装样板项目1一期"),
    STAGE_006("1601128298522378242","禽产业-山东法人公司1精装样板项目1二期"),
    STAGE_007("1601124250926743554","禽产业-山东法人公司1精装样板项目2-无分期"),
    STAGE_008("1601128278775595009","禽产业-豫晋陕甘鄂法人公司0搅拌机项目1一期"),
    STAGE_009("1601128283146059777","禽产业-豫晋陕甘鄂法人公司0搅拌机项目1三期"),
    STAGE_010("1601128280331681794","禽产业-豫晋陕甘鄂法人公司0搅拌机项目1二期"),
    STAGE_011("1601124234438934530","禽产业-黑吉辽蒙法人公司0仓库扩建项目2-无分期"),
    STAGE_012("1601128212170047489","禽产业-黑吉辽蒙法人公司0仓库扩建项目3一期"),
    STAGE_013("1601124237773406210","禽产业-黑吉辽蒙法人公司1森林绿化项目1-无分期"),
    STAGE_014("1601124150825484289","食品-豫晋陕甘鄂法人公司1精装样板项目3-无分期"),
    STAGE_015("1601124185164251137","食品产业-京津冀江苏法人公司0搅拌机项目1-无分期"),
    STAGE_016("1601128230545293314","食品产业-京津冀江苏法人公司0搅拌机项目2一期"),
    STAGE_017("1601128232042659842","食品产业-京津冀江苏法人公司0搅拌机项目2二期"),
    STAGE_018("1601128143983247362","食品产业-黑吉辽蒙法人公司0仓库扩建项目1一期"),
    STAGE_019("1601128152548016129","食品产业-黑吉辽蒙法人公司0仓库扩建项目2一期"),
    STAGE_020("1601128349235707906","食品产业-黑吉辽蒙法人公司0仓库扩建项目3一期"),
    STAGE_021("1601124208862068738","饲料产业-云贵川渝法人公司1森林绿化项目1-无分期"),
    STAGE_022("1601128163788750849","饲料产业-云贵川渝法人公司1森林绿化项目2一期"),
    STAGE_023("1601128166812844034","饲料产业-云贵川渝法人公司1森林绿化项目2三期"),
    STAGE_024("1601128165298700289","饲料产业-云贵川渝法人公司1森林绿化项目2二期"),
    STAGE_025("1601124215635869698","饲料产业-云贵川渝法人公司2森林绿化项目2-无分期"),
    STAGE_026("1601128172168970241","饲料产业-云贵川渝法人公司3仓库扩建项目3一期"),
    STAGE_027("1601128173741834241","饲料产业-云贵川渝法人公司3仓库扩建项目3二期"),
    ;

    privateStringprojectId;
    privateStringprojectName;}

也为方便测试,添加内部类 - 项目对象

@Data@AllArgsConstructor@NoArgsConstructor@Builderpublic static class Project{
    private String projectId;
    private String projectName;}

同时设置全局变量供后续使用

//全局变量public List<Project> allProject = Arrays.stream(ProjectEnum.values()).map(o -> {return Project.builder().projectId(o.getProjectId()).projectName(o.getProjectName()).build();}).collect(Collectors.toList());
//项目分期PlanTest.Project targetProject;if (ObjectUtil.isNotEmpty(projectName)) {
    //-若指定 则匹配(匹配不到 则随机)
    targetProject = allProject.stream().filter(o -> projectName.equals(o.getProjectName())).findAny().orElse(allProject.get(RandomUtil.randomInt(allProject.size())));}else {
    //若不指定 则随机
    targetProject = allProject.get(RandomUtil.randomInt(allProject.size()));}planDto.setProjectId(Long.parseLong(targetProject.getProjectId()));planDto.setProjectName(targetProject.getProjectName());

然后,设置其他属性

//计划名称if (ObjectUtil.isNotEmpty(planName)) {
    //若指定名称,则按需求拼接
    planDto.setPlanName(String.format("自动化测试-[采购计划]-%s",planName));}else {
    //若不指定名称,则按不重复拼接
    String format = String.format("自动化测试-[%s]采购计划", ChineseCharUtils.genFixedLengthChineseChars(4));
    planDto.setPlanName(TestDataUtils.getRandomStrNum(format,2000));}

供应商类别,与项目 id 相似的情况,但我们可以交给调用者来指定(测试时,由具体测试人员选择目标类别)

//供应商类别planDto.setProviderCategoryId(providerCategoryId);planDto.setProviderCategoryName(providerCategoryName);

04 完善方法入参及注释

最终我们的 2 个主要核心方法,以及测试用例调用方式就完成了:

创建采购计划(过程)节点 DTO List

List<PlanNodeDto> buildPlanNodeDtoList(String purchaseMethod,Boolean isDeletedNodes)

创建 Plan 采购计划 DTO 对象

PurchasePlanDto buildPlanDto(String planName,String projectName,String purchaseCategoryName,
                                 String purchaseMethodName,String providerCategoryId,
                                 String providerCategoryName,Boolean isDeletedNodes)

测试用例调用 - 新增保存计划

@Testvoid savePlan(@Optional("")String planName,
            @Optional("")String projectName,
            @Optional("")String purchaseCategoryName,
            @Optional("")String purchaseMethodName,
            @Optional("1597061939012812802")String providerCategoryId,
            @Optional("建筑方案设计")String providerCategoryName,
            @Optional("false")Boolean isDeletedNodes)

三、总结分析

应用远景

当数据构造变得简单且高效后,可扩展的功能方向很多,例如:复杂业务数据核对验证(断言)、长链路业务测试创建高可用数据、全链路压测、全自动回归测试(定时任务、调度算法、脚本转换成接口)

数据构造抽离出通用性,提供自定义入参功能,可提供给后续 测试用例调用,能大大提高测试效率

需注意事项

本文示例 DTO 对象较简单,往往业务中接口数据较复杂,不过再复杂的数据对象,分析模式也与本文相同,一个一个去分析,最终,你会对系统更加了解(提高测试效率)。

实际开发中需结合多方因素(长期运用、高可用、容错、编码时效等)来考虑,采用什么方式取值,并非只能通过调用接口、枚举的方式

本文重在分享一种构造数据 DTO 的思路,如果代码有不明白的,建议多学习下 Java-基础,将基础打牢(多敲多练),如果使用 Python,同样可以按这个思路来分析处理,无非是代码技巧不一样

四、完整代码

public class PlanTest extends TestBase {

    private static final ReportLog reportLog = new ReportLog(PlanTest.class);

    public Map<String,String> header = getBackTokenHeader("*****","*****");
    public List<BidUcUser> allTestUsers = Arrays.stream(TestUserEnum.values()).map(u -> {
        BidUcUser bidUcUser = new BidUcUser();
        bidUcUser.setType(u.getType());
        bidUcUser.setBidNodeCode(u.getBidNodeCode());
        bidUcUser.setUserId(Long.parseLong(u.getUserId()));
        bidUcUser.setRealName(u.getRealname());
        bidUcUser.setUserName(u.getUsername());
        return bidUcUser;
    }).collect(Collectors.toList());
    public List<Project> allProject = Arrays.stream(ProjectEnum.values()).map(o -> {return Project.builder().projectId(o.getProjectId()).projectName(o.getProjectName()).build();}).collect(Collectors.toList());

    /**
     * 测试用例调用-新增保存计划
     * @param planName  计划名称 选填 不填则按代码规则随机生成
     * @param projectName 项目名称 选填 项目范围在枚举类 ProjectEnum 中配置,若需扩大范围,需自行填加
     * @param purchaseCategoryName 采购类别名称 选填 若指定,则匹配   若匹配不到或不指定,则写死类别(采购类别-所有采购方式)
     * @param purchaseMethodName 采购方式名称 选填 若指定,则匹配   若匹配不到或不指定,则写死采购方式(邀请招标)
     * @param providerCategoryId  供应商类别ID 必填
     * @param providerCategoryName  供应商类别名称 必填
     * @param isDeletedNodes 是否删除不必要节点
     */
    @Test
    void savePlan(@Optional("")String planName,
                  @Optional("")String projectName,
                  @Optional("")String purchaseCategoryName,
                  @Optional("")String purchaseMethodName,
                  @Optional("1597061939012812802")String providerCategoryId,
                  @Optional("建筑方案设计")String providerCategoryName,
                  @Optional("false")Boolean isDeletedNodes) {

        //创建入参 采购计划DTO对象
        PurchasePlanDto planDto = buildPlanDto(planName, projectName, purchaseCategoryName, purchaseMethodName, providerCategoryId, providerCategoryName, isDeletedNodes);
        //提交保存计划接口
        String rs = HttpUtils.doPost(HostLH.TEST_HOST.concat(ApiBid.PLAN_SAVE), header, JSONObject.toJSONString(planDto));
        //输出响应结果日志信息
        reportLog.info(" PLAN_SAVE ====> {}",JSONObject.parseObject(rs));

    }

    /**
     * 创建Plan 采购计划DTO对象
     * @param planName  计划名称 选填 不填则按代码规则随机生成
     * @param projectName 项目名称 选填 项目范围在枚举类 ProjectEnum 中配置,若需扩大范围,需自行填加
     * @param purchaseCategoryName 采购类别 选填 若指定,则匹配   若匹配不到或不指定,则写死类别(采购类别-所有采购方式)
     * @param purchaseMethodName 采购方式 选填 若指定,则匹配   若匹配不到或不指定,则写死采购方式(邀请招标)
     * @param providerCategoryId  供应商类别ID 必填
     * @param providerCategoryName  供应商类别名称 必填
     * @param isDeletedNodes 是否删除不必要节点
     */
    PurchasePlanDto buildPlanDto(String planName,String projectName,String purchaseCategoryName,
                                 String purchaseMethodName,String providerCategoryId,
                                 String providerCategoryName,Boolean isDeletedNodes) {
        PurchasePlanDto planDto = new PurchasePlanDto();

            //计划名称
            if (ObjectUtil.isNotEmpty(planName)) {
                //若指定名称,则按需求拼接
                planDto.setPlanName(String.format("自动化测试-[采购计划]-%s",planName));
            }else {
                //若不指定名称,则按不重复拼接
                String format = String.format("自动化测试-[%s]采购计划", ChineseCharUtils.genFixedLengthChineseChars(4));
                planDto.setPlanName(TestDataUtils.getRandomStrNum(format,2000));
            }

        //设置 不具备依赖性的字段值
        planDto.setEstimatedAmount(new BigDecimal(RandomUtil.randomDouble(99999999999999.99,2, RoundingMode.HALF_UP)).setScale(2,RoundingMode.HALF_UP));    //预计签约金额,元
        planDto.setRecordTime(RandomUtil.randomDay(-1000,0));                             //入场时间
        planDto.setPlanStartTime(RandomUtil.randomDay(-100,0));                           //计划开始时间
        planDto.setPlanFinishTime(RandomUtil.randomDay(500,1000));                        //计划完成时间


        //项目分期
        PlanTest.Project targetProject;
        if (ObjectUtil.isNotEmpty(projectName)) {
            //-若指定 则匹配(匹配不到 则随机)
            targetProject = allProject.stream().filter(o -> projectName.equals(o.getProjectName())).findAny().orElse(allProject.get(RandomUtil.randomInt(allProject.size())));
        }else {
            //若不指定 则随机
            targetProject = allProject.get(RandomUtil.randomInt(allProject.size()));
        }
        planDto.setProjectId(Long.parseLong(targetProject.getProjectId()));
        planDto.setProjectName(targetProject.getProjectName());

        //采购类别
        //调用API-采购类别列表 查询启用状态的采购类别List
        String rs = HttpUtils.doGet(HostLH.TEST_HOST.concat(ApiBid.PURCHASE_CATEGORY_LIST), header, "status=true");
        List<JSONObject> categoryList = JSON.parseObject(rs).getJSONArray("body").toJavaList(JSONObject.class);

        PlanTest.PurchaseCategory targetCategory = null;
        //获取目标采购类别 若指定,则匹配
        if (ObjectUtil.isNotEmpty(purchaseCategoryName)) {
            targetCategory = categoryList.stream().filter(cate -> purchaseCategoryName.equals(cate.getString("name")))
                    .map(cate -> {
                        return PurchaseCategory.builder()
                                .purchaseCategoryName(cate.getString("name"))
                                .purchaseCategoryId(cate.getString("id"))
                                .build();
                    }).findAny().orElse(null);
        }

        //若匹配不到或不指定,则写死类别(采购类别-所有采购方式)
        if (null == targetCategory) {
            targetCategory = PurchaseCategory.builder().purchaseCategoryId("1600406567327461378").purchaseCategoryName("采购类别-所有采购方式").build();
        }
        planDto.setPurchaseCategoryName(targetCategory.getPurchaseCategoryName());
        planDto.setPurchaseCategoryId(Long.parseLong(targetCategory.getPurchaseCategoryId()));

        //采购方式
        //调用API-根据ID获取采购方式详情   ID来自类别targetCategory
        rs = HttpUtils.doGet(HostLH.TEST_HOST.concat(String.format(ApiBid.PURCHASE_CATEGORY_METHOD, targetCategory.getPurchaseCategoryId())), header);
        List<JSONObject> methodList = JSON.parseObject(rs).getJSONArray("body").toJavaList(JSONObject.class);
        PlanTest.PurchaseMethod targetMethod = null;
        //获取采购类别关联的 采购方式  若指定,则匹配
        if (ObjectUtil.isNotEmpty(purchaseMethodName)) {
            targetMethod = methodList.stream().filter(method -> purchaseMethodName.equals(method.getString("name")))
                    .map(method -> {
                        return PlanTest.PurchaseMethod.builder()
                                .purchaseMethodCode(method.getString("code"))
                                .purchaseMethodName(method.getString("name"))
                                .ifControlPriceFloor(method.getBoolean("ifControlPriceFloor"))
                                .ifEvaluationStaffGteThree(method.getBoolean("ifEvaluationStaffGteThree"))
                                .ifEvaluationStaffOdd(method.getBoolean("ifEvaluationStaffOdd"))
                                .ifSignMoneyControl(method.getBoolean("ifSignMoneyControl"))
                                .ifStrategic(method.getBoolean("ifStrategic"))
                                .ifTn(method.getBoolean("ifTn"))
                                .build();
                    }).findAny().orElse(null);
        }
        //若匹配不到或不指定,则写死采购方式(邀请招标)
        if (null == targetMethod) {
            targetMethod = PurchaseMethod.builder()
                    .purchaseMethodName("邀请招标")
                    .purchaseMethodCode("inviteBid")
                    .ifControlPriceFloor(true)
                    .ifEvaluationStaffGteThree(true)
                    .ifEvaluationStaffOdd(true)
                    .ifSignMoneyControl(true)
                    .ifStrategic(true)
                    .ifTn(true)
                    .build();
        }

        //浅拷贝 targetMethod所有属性到planDto
        BeanUtil.copyProperties(targetMethod,planDto);

        //议标类型
        rs = HttpUtils.doGet(HostLH.TEST_HOST.concat(ApiBid.BIDDICT_LIST), header, "type=bidType&status=true");
        List<JSONObject> bidDictList = JSONObject.parseObject(rs).getJSONArray("body").toJavaList(JSONObject.class);
        //优先取默认的
        JSONObject bidType = bidDictList.stream().filter(json -> json.getBoolean("ifDefault")).findAny().orElse(null);
        if (null == bidType) {
            //若不存在默认的,则随机取
            bidType = bidDictList.get(RandomUtil.randomInt(bidDictList.size()));
        }
        planDto.setBidTypeName(bidType.getString("name"));
        planDto.setBidTypeValue(bidType.getString("value"));

        //供应商类别
        planDto.setProviderCategoryId(providerCategoryId);
        planDto.setProviderCategoryName(providerCategoryName);

        //设置 招标经办人
        TestUserEnum chargeUser = TestUserEnum.ATEzbjbr;
        planDto.setChargeUserId(Long.parseLong(chargeUser.getUserId()));
        planDto.setChargeUserName(chargeUser.getRealname());
        planDto.setChargeUserCode(chargeUser.getUsername());

        //采购节点List
        planDto.setNodes(buildPlanNodeDtoList(planDto.getPurchaseMethodCode(),isDeletedNodes));
        return planDto;
    }

    /**
     * 创建采购计划(过程)节点DTO List<PlanNodeDto>
     * @param purchaseMethod 采购方法编码
     * @param isDeletedNodes 是否删除非必要节点
     * @return
     */
    List<PlanNodeDto> buildPlanNodeDtoList(String purchaseMethod,Boolean isDeletedNodes) {

        //调用接口(API功能-根据采购方式编码获取详情信息-含关联的采购节点信息)
        String apiUrl = String.format(HostLH.TEST_HOST.concat(ApiBid.PURCHASE_METHOD_DETAIL), purchaseMethod);
        String rs = HttpUtils.doGet(apiUrl, header);

        //解析响应json拿到所有的节点List
        List<JSONObject> purchaseMethodNodes = JSON.parseObject(rs).getJSONObject("body").getJSONArray("purchaseMethodNodes").toJavaList(JSONObject.class);

        DateTime now = DateUtil.date();
        AtomicInteger loopInt = new AtomicInteger(1);

        List<PlanNodeDto> nodeDtoList = new ArrayList<>(purchaseMethodNodes.size());

        purchaseMethodNodes.forEach(node -> {

            PlanNodeDto nodeDto = new PlanNodeDto();

            nodeDto.setName(node.getString("name"));                                //节点名称
            nodeDto.setPurchaseMethodCode(node.getString("purchaseMethodCode"));    //关联采购方式编码
            nodeDto.setPurchaseMethodNodeCode(node.getString("code"));              //关联采购方式节点编码
            //责任部门
            nodeDto.setChargeOrg(node.getString("responsibleDept"));
            nodeDto.setChargeOrgValue(node.getString("responsibleDeptName"));
            nodeDto.setIfNecessity(node.getBoolean("ifNecessity"));                         //是否必要节点
            nodeDto.setSort(node.getInteger("sort"));                                       //排序
            nodeDto.setStartTime(DateUtil.offsetDay(now,loopInt.getAndAdd(30)));           //节点计划开始时间
            nodeDto.setFinishTime(DateUtil.offsetDay(now,loopInt.getAndAdd(30)));          //节点计划完成时间

            //节点经办人
            BidUcUser chargeUser = allTestUsers.stream().filter(u -> node.getString("code").equals(u.getBidNodeCode()) && "1".equals(u.getType())).findFirst().orElse(null);
            if (null == chargeUser) {
                throw new BusinessException("测试账号节点code未匹配,请检查配置!");
            }
            nodeDto.setChargeUserId(chargeUser.getUserId());
            nodeDto.setChargeUserName(chargeUser.getRealName());
            nodeDto.setChargeUserCode(chargeUser.getUserName());

            nodeDtoList.add(nodeDto);

        });

        return nodeDtoList;

    }


    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Builder
    public static class PurchaseCategory{
        private String purchaseCategoryName;
        private String purchaseCategoryId;
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Builder
    public static class PurchaseMethod{
        private String purchaseMethodName;
        private String purchaseMethodCode;
        private Boolean ifControlPriceFloor;
        private Boolean ifEvaluationStaffGteThree;
        private Boolean ifEvaluationStaffOdd;
        private Boolean ifSignMoneyControl;
        private Boolean ifStrategic;
        private Boolean ifTn;
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Builder
    public static class Project{
        private String projectId;
        private String projectName;
    }}

最后的话:

作者学习java收获最大的不是代码能力,而是对JVM、spring IOC AOP等、以及 springcloud 微服务生态的理解提升,这也帮助我切身地运用到了日常开发测试,近期在研究流量回放相关实现,也受到不少前辈分享的启发,更加明白知识的分享能促进共同进步。


资源分享【这份资料必须领取~】

下方这份完整的软件测试视频学习教程已经上传CSDN官方认证的二维码,朋友们如果需要可以自行免费领取 【保证100%免费】

在这里插入图片描述

在这里插入图片描述

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

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

相关文章

同样是项目经理,为啥就干不过他?

早上好&#xff0c;我是老原。 很多人和我抱怨说&#xff0c;做工作太难了&#xff0c;领导针对我&#xff0c;同样都是项目经理&#xff0c;就老是挑我的刺&#xff0c;找我的麻烦。 其实在我看来&#xff0c;工作其实没有那么难&#xff0c;80%的工作问题&#xff0c;都是沟…

C#语言实例源码系列-虚拟键盘

专栏分享点击跳转>Unity3D特效百例点击跳转>案例项目实战源码点击跳转>游戏脚本-辅助自动化点击跳转>Android控件全解手册 &#x1f449;关于作者 众所周知&#xff0c;人生是一个漫长的流程&#xff0c;不断克服困难&#xff0c;不断反思前进的过程。在这个过程中…

guitar pro8吉他谱软件好用吗?2023GTP全新功能解析

我们常见的GTP格式吉他谱就是用这款软件制作出来的曲谱&#xff0c;也只能用这款软件打开查看。看介绍感觉还不错&#xff0c;原生支持Apple的芯片了。这也是一款能陪着我们一起进步&#xff0c;提升自己的软件。我们在练习吉他等乐器的过程中&#xff0c;音阶与和弦的熟练掌握…

ffmpeg-AVBuffer、AVBufferRef、引用计数机制

目录 引用计数 定义 优点 AVBuffer AVBufferRef av_buffer_create av_buffer_ref av_buffer_unref 参考&#xff1a; 引用计数 定义 引用计数是一种内存管理的方式&#xff0c;当一份内存可能被多个对象使用&#xff0c;可以通过引用计数的方式实现内存复用。 优点 …

深入理解Maven的各项配置

深入理解Maven的各项配置1. Introduction1.1 Big Data -- Postgres2. Install2.1 Maven Install2.2 Config Setting.xml2.3 Local Package Install Maven3. Project4.AwakeningMaven Document: https://maven.apache.org/. Maven Download: https://maven.apache.org/download.…

基于微信小程序的好物分享系统ssm框架-计算机毕业设计

项目介绍 我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;好物分享系统小程序被用户普遍使用&#xff0c;为方便用户…

【技术应用】mybatis数据库操作(insert、update、delete)返回值为0的场景

【技术应用】mybatis数据库操作insert、update、delete返回值为0的场景一、前言二、数据库异常处理三、insert操作返回值为: 0四、update操作返回值为: 0五、delete操作返回值为: 0六、总结一、前言 最近在review项目组成员代码时&#xff0c;发现代码中有很多mybatis执行数据…

涉及准考证相关需要关注的一系列问题,涉及防疫、考点信息、计算器等内容

12月14日起可以打印准考证&#xff01;这是一件操作并不复杂的工作&#xff0c;但打印下来以后可能会遇到一些细节问题&#xff0c;对此我们梳理出来供大家参考&#xff0c;有则改之&#xff0c;无则更好&#xff01; 1.有关省份个人健康申报表等如何填写&#xff1f;如您报考点…

web网页设计期末课程大作业:美食餐饮文化主题网站设计——美食汇5页HTML+CSS+JavaScript

&#x1f380; 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

事业编招聘:南方科技大学附属实验学校2022年·面向应届毕业生招聘在编教师公告

南方科技大学是在中国高等教育改革发展背景下创建的一所高起点公办创新型大学&#xff0c;2022年2月14日&#xff0c;教育部等三部委公布第二轮“双一流”建设高校及建设学科名单&#xff0c;南方科技大学入选“双一流”建设高校名单。 南方科技大学附属实验学校&#xff0c;地…

部署了一个个人博客(好歹服务器不是闲着了)

界面前台界面展示&#xff08;给到浏览用户&#xff09;后台界面展示&#xff08;简单介绍&#xff09;技术说明前台界面展示&#xff08;给到浏览用户&#xff09; 肯定首先将界面展示一下。声明一下这个不是我原创的界面&#xff0c;当然这个是可以改造的。这个在安全上还有…

血氧仪的分类与价格区别

有没有发现最近血氧仪、额温枪、壁挂式测温仪又开始火了&#xff1f;并且市场活跃度越来越高。而作为我们血氧仪方案提供商或者生产企业来说&#xff0c;您是不是和优优一样会时常听到客户发出如下反馈&#xff1a; “我刚问了另外一家&#xff0c;和你这个样子差不多的&#…

VC++2010中使用MSDN library 200X版本

前言 以前一直用VS2005 ,帮助文档调用很方便&#xff0c;现在要使用VS2010版本来写程序&#xff0c;这个帮助文档真是不方…… 自己写了一个F1的按键助手&#xff0c;这下我可 以使用VS2008中的帮助文档了&#xff0c;虽然帮助低一个版本&#xff0c;但是写C程序&#xff0c;…

七个步骤覆盖 API 接口测试

接口测试作为最常用的集成测试方法的一部分&#xff0c;通过直接调用被测试的接口来确定系统在功能性、可靠性、安全性和性能方面是否能达到预期&#xff0c;有些情况是功能测试无法覆盖的&#xff0c;所以接口测试是非常必要的。首先需要对接口测试的基本信息做一些了解&#…

阿里面试题库被“泄露“导致多人进大厂,惨遭多家大厂威胁下架

我有一个玩得特别好的朋友找我帮忙&#xff0c;说他儿子去大厂面试几次没刷下来&#xff0c;要求我帮他儿子出出主意&#xff0c;大概了解他儿子的情况后&#xff0c;我就给他推荐了这本软件测试10万总结&#xff0c;大概过了几个月他就给我发信息了&#xff0c;说他已经靠这份…

(一) Oriented R-CNN

文章目录0.基础介绍1.旋转框的中点偏移表示法2.Oriented R-CNN架构2.1 Oriented RPN2.2 Rotated RoI Alignment参考资料欢迎访问个人网络日志&#x1f339;&#x1f339;知行空间&#x1f339;&#x1f339; 论文&#xff1a;Oriented R-CNN for Object Detection 代码&#xf…

Erlang - 入门之一日志系统如何打印输出使用lager框架

在合适的地方插入一句代码即可&#xff1a; lager:error("Some Message"), 就这样&#xff0c;谢谢阅读 。。。。。。。 。。。。。。。。。 。。。。。。。。。。。 。。。。。。。。。。。。。。 。。。。。。。。。。。。。。。。。 没有什么比从直接“”…

web前端网页课程设计大作业 html+css+javascript天津旅游(11页) dw静态旅游网页设计实例 企业网站制作

&#x1f468;‍&#x1f393;学生HTML静态网页基础水平制作&#x1f469;‍&#x1f393;&#xff0c;页面排版干净简洁。使用HTMLCSS页面布局设计,web大学生网页设计作业源码&#xff0c;这是一个不错的旅游网页制作&#xff0c;画面精明&#xff0c;排版整洁&#xff0c;内容…

草料二维码如何在多行业实现管理和自动化工作

二维码&#xff0c;在我们的日常生活中随处可见&#xff0c;不管是我们常用的微信支付&#xff0c;还是日常出入的场所码&#xff0c;已经渗透在我们生活的点点滴滴中。 如果我们自己也希望通过二维码&#xff0c;来传达文字、数据等信息&#xff0c;那么很多人都肯定给你推荐…

NS3笔记

NS3笔记1 Docker环境配置1.1 Docker安装1.2 镜像安装1.3 安装其他软件1.4 安装ns31 Docker环境配置 1.1 Docker安装 俺是macOS&#xff0c;直接参考这个网址。 1.2 镜像安装 俺打算安装Ubuntu18.04&#xff0c;镜像版本可以Ubuntu 镜像库 # 拉取镜像 docker pull ubuntu:18…