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

random 测试交流2117字数 31401阅读模式

前言

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

  - 文章作者:gitee@ 随性自然 fqc, 转载时,请注明来源,注明作者,这是对文章作者的尊重,也是对知识的尊重。文章源自玩技e族-https://www.playezu.com/492941.html

背景

  本次示例接口为业务开发中常见的新增保存接口,参数相对比较简单,为方便理解,我这里画个草图讲下大概的业务逻辑:
文章源自玩技e族-https://www.playezu.com/492941.html

API 接口 DTO 测试数据构造 方式一-图片1


总体,业务逻辑 为创建采购计划选择不同的采购方式 并发起采购过程,采购过程由节点 1线性流程一直操作到节点 N。其中需特殊说明的:文章源自玩技e族-https://www.playezu.com/492941.html

  • 采购方式、采购类别、议标类型、采购过程节点(以下简称:节点)均具备各自的相关属性(面向对象),属于配置数据(其他业务模块已配置),且具备关联关系:采购类别关联多个采购方式;采购方式关联多个采购节点。
  • 整体后端环境为微服务,服务交互采用 Feign 调用,供应商类别取自其他服务(供应商),项目信息、人员信息取自外部服务(用户中心、基础平台)
  • 整体分析过程重点在分析构造数据,忽略涉及的权限、参数配置、各节点 OA 回调处理等问题,以及具体信息已脱敏。

  开发语言采用Java文章源自玩技e族-https://www.playezu.com/492941.html

整体思路

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

首先,根据编写功能测试用例时,对业务逻辑的理解,以及后端提供的接口文档来分析文章源自玩技e族-https://www.playezu.com/492941.html

接口文档

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

请求方式:POST

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

请求参数:文章源自玩技e族-https://www.playezu.com/492941.html

Headers:
| 参数含义 | 参数值 | 是否必须 |
| -------- | -------- | -------- |
| Content-Type | application/json | 是 |
| Authorization | Bearer ${token} | 是 |文章源自玩技e族-https://www.playezu.com/492941.html

Body:文章源自玩技e族-https://www.playezu.com/492941.html

{"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,//否临时采购计划,10"supplementBid":true,//是否补标,10"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//排序}]}

返回数据文章源自玩技e族-https://www.playezu.com/492941.html

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

分析方法

1、分析参数结构,嵌套层级关系文章源自玩技e族-https://www.playezu.com/492941.html

  • 首先,请求参数 json 外层是 采购计划对象相关属性,都是一对一(key-value)的关系
  • 其次,采购过程节点(nodes)字段 的数据类型是列表(List<采购过程节点对象>),即该字段会存在多个,根据业务理解,节点数量=采购方式下配置的节点数量,且每个节点都有不同的节点名称定义(枚举、数据库配置)

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

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

3、找出各强校验、具备逻辑关联关系的字段,并梳理清楚它们的取值源、取值逻辑、影响后续业务逻辑,我们的编程主要是针对该部分字段进行设置 value,该部分字段优先采用调用接口形式取值,如果处理起来麻烦,可采用枚举形式,在限定范围内取值

  • 项目 id、name,供应商类别 id、name 取自不同外部服务(项目权限、供应商库),影响后续业务逻辑。
  • 采购类别 id、name,采购方式 id、name 取自配置,具备关联性,影响后续业务逻辑
  • 议标类型 name、value 取自字典项配置,具备弱关联性,影响较小,但为了数据合法性,仍需要取真实有效的配置
  • 是否用于战采协议、是否启用 2n+1 控制、是否启用预计签约金额控制、技术标评标人员是否奇数、技术标评标人员是否大于等于 3、是否控制价下限控制 取自采购方式中配置的自有属性,影响后续业务逻辑(存在冗余字段),为数据合法性,需取真实配置有效的配置
  • 招标经办人、各节点责任人 涉及 userId、userCode、userName 取自用户中心账号体系(已提前配置测试账号)
  • 各节点中 节点名称、采购关联方式编码、采购关联方式节点编码、责任部门 code、name、是否必须、序号 均取自节点配置数据,影响后续业务逻辑,需取真实有效配置

二、 编码过程

01 首先创建数据 DTO 对象类

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

PurchasePlanDto 采购计划对象


@Data
public 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 采购过程节点对象

@Data
public 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
@AllArgsConstructor
public 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<user>对象,并定义成全局变量,提供给后续使用:

//全局变量
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 对象,UcUserBidUcUser,其中UcUser是示例系统用户中心用户对象(注意,这里对象是测试自己定义的,与后端的区分开,这里是测试需要的属性组成的对象),BidUcUserUcUser的基础上扩展了本次测试需要的属性(节点 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());
}

02 编写创建采购计划 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
@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;
}

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

//采购类别
//调用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()));

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

//采购方式
//调用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);

设置议标类型

//议标类型
//调用API-字典项列表接口获取list
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"));

设置项目 id、name

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

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

API 接口 DTO 测试数据构造 方式一-图片2


API 接口 DTO 测试数据构造 方式一-图片3

为符合真实场景,如果要取目标值(逻辑要求只取最末级,且只取末级的 type 为PROJECTSTAGE),这最好的办法是编写树的遍历算法,通过递归获取,且取出来后,需提供根据入参匹配的功能。考虑到目标字段较少,只有项目 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
@Builder
public 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);

03 完善方法入参及注释

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

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

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)

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

@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 对象较简单,往往业务中接口数据较复杂,不过再复杂的数据对象,分析模式也与本文相同,一个一个去分析,最终,你会对系统更加了解(提高测试效率)。
  • 实际开发中需结合多方因素(长期运用、高可用、容错、编码时效等)来考虑,采用什么方式取值,并非只能通过调用接口、枚举的方式
  • 本文重在分享一种构造数据 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;
}
}
玩技站长微信
添加好友自动发送入群邀请
weinxin
rainbow-shownow
玩技官方公众号
官方微信公众号
weinxin
PLAYEZU
 
评论  2  访客  2
    • 随性自然fqc
      随性自然fqc 9

      感谢关注,作者意图是给有一定代码基础的同行在编写测试脚本中构建参数一种参考的可行性方案意见,后续也会整理些其他的方式,总结分析中有注明。 作者 python 能力比 java 差很大一截,正在努力提高 coding 能力,欢迎同行能分享优雅封装的 python 相关代码,让大家学习学习。最后再分享个点:
      作者学习 java 收获最大的不是代码能力,而是对 JVM、spring IOC AOP 等、以及 springcloud 微服务生态的理解提升,这也帮助我切身地运用到了日常开发测试,近期在研究流量回放相关实现,也受到不少前辈分享的启发,更加明白知识的分享能促进共同进步

      • 林月
        林月 9

        一堆这种 说到底就是构造 参数 掉接口 只是 java 写起来麻烦点 换成 python 直接构造一个字典 啥都解决了

      匿名

      发表评论

      匿名网友
      确定