给测试小姐姐优化代码——干掉if-else
友情提示,这篇的代码比较多,可以选择性阅读
最近测试小姐姐在写自动化测试case,之前我就和测试小姐姐提过,她写的代码确实有点臃肿,可以考虑优化一下,减少冗余代码,提高可读性和可维护性......
估计小姐姐听了我一番高谈阔论被我给忽悠到了。这天晚上大概9点,我正准备跑路,测试小姐姐突然找我
测试:你看看我这个代码该怎么优化一下
me:emmm..(扔给我一个js文件,467行,我统计了一下,三个if控制段,每个if带19个else,平均每一个条件6-7行的代码,就是说加起来有三四百行的代码是在重复工作)
describe ("退货分仓逻辑",function(){
it("第三方商家、自主发货、已签收状态:退回商家地址",function(){
var checkMap = {
"type" : 1
}
var data = returnWhcode(checkMap);
checkDataResults(data,checkMap);
});
// 省略19种不同的case
}
function returnWhcode(checkMap){
//数据准备
var info = {};
if(checkMap.type == 1){
info = {
"seller" : "***",
"status" : 7,
"whcode" : "CPartner",
"logistics" : "auto"
}
}else if(checkMap.type == 2){
info = {
"seller" : "5ac442c71d9771377df40c8d",
"status" : 6,
"whcode" : "CPartner",
"logistics" : "consolidation"
}
}
//此处省略18个else if
var data = null;
// 业务逻辑
return data;
}
function checkDataResults(data,checkMap){
caseResult.setResultMsg("商家:" + data.afterSaleMap.seller_name);
caseResult.setResultMsg("包裹状态:" + data.info.status);
caseResult.setResultMsg("发货仓:" + data.info.whcode);
caseResult.setResultMsg("物流模式:" + data.info.logistics);
if(checkMap.type == 1 || checkMap.type == 2 || checkMap.type == 3 || checkMap.type == 5 || checkMap.type == 7 || checkMap.type == 9 || checkMap.type == 11 || checkMap.type == 13){
expect(data.afterSaleMap.return_wh_code).toBe("CPartner");
caseResult.setResultMsg("退货地址为第三方商家地址:" + data.afterSaleMap.return_address);
}
// 这里当然也省略好多个else if
}
me: 这么多if-else你看着不难受么
测试:难受啊,所以来找你看看怎么优化嘛
me: 呃,要不你先说说这个测试的场景是什么
测试:测试退货分仓逻辑,根据不同的商家类型、物流模式、和发货仓库,把货物退到不同的地方,目前有20种退货组合,会分别退货到5个不同的仓库
me:明白了,就是说你现在要测一个接口,参数就那么几个,但是请求参数组合有很多种,是这样吧
测试:是哒
me: 那完全不需要把所有的请求数据全部hardcode到代码里面啊,你把请求参数抽出来,放到配置文件里面去,然后用个for循环去调用就好了
我的初版设计
{
"第三方商家、自主发货、已签收状态->退回商家地址" : {
"seller" : "***",
"name" : "测试",
"status" : 7,
"whcode" : "CPartner",
"logistics" : "auto",
// 预期退货仓库
"return_wh_code" : "CPartner",
// 预期退货地址
"return_address" : "上海 上海市 ***"
}
}
describe ("退货分仓逻辑",function(){
var whCodeInfos = JSON.parse(JsonSchemaUtils.getJsonFile(PWD+'/whCodeInfo.json'));
for(var key in whCodeInfos){
info = whCodeInfos[key];
data = returnWhCode(info);
checkDataResults(data,info);
}
}
function returnWhcode(info){
var data = null;
// TODO 业务逻辑
return data;
}
function checkDataResults(data,checkMap){
expect(data.afterSaleMap.return_wh_code).toBe("CPartner");
caseResult.setResultMsg("退货地址为第三方商家地址:" + data.afterSaleMap.return_address);
}
me : 你看,我们把数据和控制逻辑分离开,几百行代码一下子就变成几十行了,如果之后有新的请求组合出现,你只需要在json文件里面新增一个key-value对就好了(快夸我)
测试: 嗯,我先改好测一下看看me: (中间经历了不少次失败,反正是折腾了很久) 十点多了,我得下班了,你写好了我明天再看看,应该还可以改进的
测试:这样很好了啊,还要怎么改进
me: 你先把这个写完,明天再看
第二天,测试小姐姐的新版
describe("退货分仓逻辑",function(){
it("第三方商家、自营分仓",function(){
for(var key in whCodeInfo){
caseResult.setResultMsg("--------------分仓case分割线--------------");
caseResult.setResultMsg("|" + key + "|");
info = whCodeInfo[key];
data = returnWhCode(info);
checkDataResults(data,info);
}
});
});
function returnWhCode(info){
var data = null;
// 省略业务逻辑
return data;
}
function checkDataResults(data,info){
caseResult.setResultMsg("商家:" + info.name);
caseResult.setResultMsg("包裹状态:" + info.status);
caseResult.setResultMsg("发货仓:" + info.whcode);
caseResult.setResultMsg("物流模式:" + info.logistics);
caseResult.setResultMsg("期望退货仓:" + info.return_wh_code);
caseResult.setResultMsg("期望退货地址:" + info.return_address);
caseResult.setResultMsg("实际退货仓:" + data.afterSaleMap.return_wh_code);
caseResult.setResultMsg("实际退货地址:" + data.afterSaleMap.return_address);
expect(data.afterSaleMap.return_wh_code).toBe(info.return_wh_code);
expect(data.afterSaleMap.return_address).toBe(info.return_address);
}
me:你上面的caseResult.setResultMsg("商家:" + info.name);这一串msg是需要展示到界面的么
测试:是的,我得在页面上看到结果
me: 那要是之后这个接口加了个字段,你又得来改代码
测试:没关系啊,反正只需要加两行就好了
me:白帮你优化了,可以通过改配置解决的问题,就不要改代码,像这样
我的终版设计1.0(其实后面还有一些改进,就不在这里列出来了)
{
"第三方商家、自主发货、已签收状态->退回商家地址":{
"input":{
"seller":"5ac442c71d9771377df40c8d",
"name":"测试redqa021",
"status":7,
"whcode":"CPartner",
"logistics":"auto"
},
"except_output":{
"return_wh_code":"CPartner",
"return_address":"上海 上海市 黄浦区马当路388号小红书"
}
}
}
function checkDataResults(data,info){
// 输入数据展示
for(var key in info["input"]){
caseResult.setResultMsg(key + ": " + info["input"][key])
}
// 实际结果和预期结果展示
for(var key in info["expect_output"]){
caseResult.setResultMsg(key + "_actrual: " + info["expect_output"][key])
caseResult.setResultMsg(key + "_expect: " + info["expect_output"][key])
}
// 实际结果和预期结果对比
for(var key in info["expect_output"]){
expect(data[key]).toBe(info["expect_output"][key]);
}
}
总结一下
1. 为什么会有这么多if-else
就这次测试小姐姐的案例来看,其实整个主流程的逻辑是很单一的,之所以会有这么多代码量,都是因为数据影响了控制逻辑,其实本次测试的每一个case的测试流程都是一样的,完全不需为每个case写一个条件判断。
2. 我们做了什么
其实前辈们已经提供了方向,把常改变的和不常改变的东西分开,在这个案例中很明显,随着业务的变更,这一个服务可能会产生更多的case,所以case数据是经常变动的,但是我们的接口调用流程,请求响应的整体结构是不会经常变动的。
如果对变和不变的预测没有一个好的标准,那么我这里提出一个清晰一点的方法:把数据和控制逻辑分开
3. 有什么好处
- 看着舒服(467行和67行,你说呢)
- 几乎不用改代码了
这样就可以更专注于测试数据,不用担心改代码的时候出错,造成测试结果不符合预期 - 就算要改代码也会很轻松,参考第一条
- 接入配置中心,以后测试的时候你连代码都不用拉下来,直接在平台上就可以新增、修改case