smart-auto工具经过多年的迭代和好几代测试同学的开发,现在功能已经非常强大,支持各种HSF接口调用返回值的对比和断言能力,每日跑的测试件已经占到了淘宝买菜所有自动化测试件的55%以上。但是随着功能的不断增加,代码也越来越来庞大,之前单一的功能也变得复杂,代码的可维护性也在不停的降低。所以针对smart-auto接口测试相关的核心代码进行了一次重构,使代码变得更清晰和可维护。
可以看下优化之前接口测试相关的核心代码,可以由下面简单的表述下这段代码意思:
1.
工具中针对,Hsf接口校验共有四种方式HsfCheck1、HsfCheck2、HsfCheck3、HsfCheck4;
2.
所有的接口校验方式都包含在一个Handle
r
中,且不同的方式之间全部通过各种复杂的if-else分支来判断;
public CheckOutputModel doHandle(CheckCaseModel caseParam, BuildTestsuiteModel checkRecordModel, ExecuteResultModel executeResultModel) throws Exception {
if(!jsonPathList.isEmpty()){
if (attributeModel.isCompare()) {
HsfCheck1
}
}else{
if(attributeModel.isCompare()) {
HsfCheck2
}
}
if ( checkConfigStatusObject == null ||checkConfigStatusObject.isEmpty() || checkConfigStatusObject.getBoolean("isCheck") == false ){
return result;
}else{
if(assertExpectStr == null || !node.isCustom()){
HsfCheck3
}else{
HsfCheck4
}
}
}
@Service("commonCheckHandlerAbandon")
public class CommonCheckHandler implements CheckHandler{
@Resource
private CaseConfigHandlerService caseConfigHandlerService;
@Resource
private CheckDataCaseService checkDataCaseService;
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public CheckOutputModel doHandle(CheckCaseModel caseParam, BuildTestsuiteModel checkRecordModel, ExecuteResultModel executeResultModel) throws Exception {
ThubNodeConfig node = JSON.parseObject(checkRecordModel.getTestsuiteDO().getStepConfig(), ThubNodeConfig.class);
TestsuiteAttributeModel attributeModel = JSON.parseObject(checkRecordModel.getAttributes(), TestsuiteAttributeModel.class);
if (checkRecordModel.getTestsuiteDO().getShadow()) {
EagleEye.putUserData("t", "1");
}
CheckOutputModel result = new CheckOutputModel();
if(node==null){
result.setSuccess(false);
return result;
}
List<String> jsonPathList = Collections.emptyList();
if(node.getJsonPath() != null && !node.getJsonPath().trim().isEmpty() ){
jsonPathList = Arrays.asList(node.getJsonPath().split(";"));
}
try{
if(!jsonPathList.isEmpty()){
List totalFailInfo = new ArrayList<>();
List<String> errorMessages = new ArrayList<>();
for (String jsonPath : jsonPathList) {
try {
if (attributeModel.isCompare()) {
String actualResultStr = StringEscapeUtils.unescapeJavaScript((String) executeResultModel.getValueByKey(CheckCaseInfoConst.ACTUAL_RESULT));
String expectResultStr = StringEscapeUtils.unescapeJavaScript((String) executeResultModel.getValueByKey(CheckCaseInfoConst.EXPECT_RESULT));
Object actualResult = null;
Object expectResult = null;
if (StringUtils.isNoneBlank(actualResultStr)) {
Object actualValueObject = JsonPath.read(actualResultStr, jsonPath);
String actualValue = JSON.toJSONString(actualValueObject);
if (JSON.isValidObject(actualValue)) {
actualResult = JSON.parseObject(actualValue);
} else if (JSON.isValidArray(actualValue)) {
actualResult = JSON.parseArray(actualValue);
} else {
actualResult = JSON.parse(actualValue);
}
}
if (StringUtils.isNoneBlank(expectResultStr)) {
Object expectValueObject = JsonPath.read(expectResultStr, jsonPath);
String expectValue = JSON.toJSONString(expectValueObject);
if (JSON.isValidObject(expectValue)) {
expectResult = JSON.parseObject(expectValue);
} else if (JSON.isValidArray(expectValue)) {
expectResult = JSON.parseArray(expectValue);
} else {
expectResult = JSON.parse(expectValue);
}
}
StringBuffer ignorBuffer = new StringBuffer();
ignorBuffer.append(node.getIgnorConfig());
List failInfo = QAssert.getReflectionDiffInfo("assert diff", expectResult, actualResult, ignorBuffer.toString(),
ReflectionComparatorMode.LENIENT_ORDER, ReflectionComparatorMode.LENIENT_DATES, ReflectionComparatorMode.IGNORE_DEFAULTS);
failInfo.forEach(i -> i.setNodeName(jsonPath + "---" + i.getNodeName()));
totalFailInfo.addAll(failInfo);
}
} catch (Exception e) {
String errorMessage = "Error with JSON path: " + jsonPath + " - " + e.getMessage();
errorMessages.add(errorMessage);
logger.error(errorMessage, e);
}
}
if (!totalFailInfo.isEmpty()||!errorMessages.isEmpty()) {
if(!totalFailInfo.isEmpty()){
errorMessages.add(0, "value not same");
}
String combinedErrorMessages = String.join("\n", errorMessages);
result.setSuccess(false);
result.setErrorCode(combinedErrorMessages);
result.setFailInfoList(totalFailInfo);
} else {
result.setSuccess(true);
}
}else {
result.setTraceId(EagleEye.getTraceId());
if(attributeModel.isCompare()) {
String actualResultStr = StringEscapeUtils.unescapeJavaScript((String) executeResultModel.getValueByKey(CheckCaseInfoConst.ACTUAL_RESULT));
String expectResultStr = StringEscapeUtils.unescapeJavaScript((String) executeResultModel.getValueByKey(CheckCaseInfoConst.EXPECT_RESULT));
Object actualResult = null;
Object expectResult = null;
if (StringUtils.isNoneBlank(actualResultStr)) {
if (JSON.isValidObject(actualResultStr)) {
actualResult = JSON.parseObject(actualResultStr);
} else if (JSON.isValidArray(actualResultStr)) {
actualResult = JSON.parseArray(actualResultStr);
} else {
actualResult = JSON.parse(actualResultStr);
}
}
if (StringUtils.isNoneBlank(expectResultStr)) {
if (JSON.isValidObject(expectResultStr)) {
expectResult = JSON.parseObject(expectResultStr);
} else if (JSON.isValidArray(expectResultStr)) {
expectResult = JSON
.parseArray(expectResultStr);
} else {
expectResult = JSON.parse(expectResultStr);
}
}
StringBuffer ignorBuffer = new StringBuffer();
ignorBuffer.append(node.getIgnorConfig());
List failInfo = QAssert.getReflectionDiffInfo("assert diff", expectResult, actualResult, ignorBuffer.toString(),
ReflectionComparatorMode.LENIENT_ORDER, ReflectionComparatorMode.LENIENT_DATES, ReflectionComparatorMode.IGNORE_DEFAULTS);
if (!failInfo.isEmpty()) {
result.setSuccess(false);
result.setErrorCode("value not same");
result.setFailInfoList(failInfo);
} else {
result.setSuccess(true);
}
}
}
JSONObject checkConfigStatusObject = JSON.parseObject(checkRecordModel.getTestsuiteDO().getCheckConfigStatus());
if ( checkConfigStatusObject == null ||checkConfigStatusObject.isEmpty() || checkConfigStatusObject.getBoolean("isCheck") == false ){
return result;
}else{
String assertActualStr = StringEscapeUtils.unescapeJavaScript((String)executeResultModel.getValueByKey(CheckCaseInfoConst.ACTUAL_RESULT));
String assertExpectStr = StringEscapeUtils.unescapeJavaScript((String)executeResultModel.getValueByKey(CheckCaseInfoConst.EXPECT_RESULT));
CheckDataCaseDO checkDataCaseDO = caseParam.getCaseDO();
if(assertExpectStr == null || !node.isCustom()){
boolean checkResult = caseConfigHandlerService.resultAssert(checkRecordModel.getTestsuiteDO(), checkDataCaseDO,assertActualStr);
if (!checkResult){
result.setSuccess(false);
return result;
}
CheckDataCaseQueryDO checkDataCaseQueryDO = new CheckDataCaseQueryDO();
checkDataCaseQueryDO.setId(checkDataCaseDO.getId());
List checkResultList = checkDataCaseService.queryCheckCaseResult(checkDataCaseQueryDO).getResult();
List checkCoonfigFailInfo = new ArrayList<>();
for (CheckCaseResult checkCaseResult : checkResultList) {
String checkConfigResult = checkCaseResult.getCheckConfigResult();
List
for (Map map : checkParse) {
CheckDiffModel checkDiffModel = new CheckDiffModel();
String checkConfig = String.valueOf(map.get("checkConfigResult"));
StringBuffer stringBuffer = new StringBuffer();
if(!StringUtils.equals(checkConfig,"true")){
stringBuffer.append((String)map.get("assertNode")+map.get("assertCondition")+map.get("assertErpect"));
checkDiffModel.setActualValue("false");
checkDiffModel.setNodeName(String.valueOf(stringBuffer));
checkCoonfigFailInfo.add(checkDiffModel);
}
}
}
if (checkCoonfigFailInfo.size() != 0) {
result.setSuccess(false);
result.setErrorCode("value not same");
result.setFailInfoList(checkCoonfigFailInfo);
} else{
result.setSuccess(true);
}
}else {
boolean checkResult = caseConfigHandlerService.resultAssertComp(checkRecordModel.getTestsuiteDO(), checkDataCaseDO, assertActualStr,assertExpectStr);
if (!checkResult){
result.setSuccess(false);
return result;
}
CheckDataCaseQueryDO checkDataCaseQueryDO = new CheckDataCaseQueryDO();
checkDataCaseQueryDO.setId(checkDataCaseDO.getId());
List checkResultList = checkDataCaseService.queryCheckCaseResult(checkDataCaseQueryDO).getResult();
List checkCoonfigFailInfo = new ArrayList<>();
for (CheckCaseResult checkCaseResult : checkResultList) {
String checkConfigResult = checkCaseResult.getCheckConfigResult();
List
CheckDiffModel checkDiffModel = new CheckDiffModel();
StringBuffer stringBuffer = new StringBuffer();
for (Map map : checkParse) {
Boolean checkConfig = (Boolean) map.get("checkConfigResult");
if(!checkConfig){
stringBuffer.append((String)map.get("assertNode")+map.get("assertCondition")+map.get("assertErpect"));
stringBuffer.append(",");
checkDiffModel.setActualValue("false");
}
}
checkDiffModel.setNodeName(String.valueOf(stringBuffer));
checkCoonfigFailInfo.add(checkDiffModel);
}
if (checkCoonfigFailInfo.get(0).getActualValue() != null) {
result.setSuccess(false);
result.setErrorCode("value not same");
result.setFailInfoList(checkCoonfigFailInfo);
} else{
result.setSuccess(true);
}
}
}
}catch(Exception e){
e.printStackTrace();
result.setSuccess(false);
result.setMsgInfo(e.getMessage());
}finally{
EagleEye.removeUserData("t");
}
return result;
}
}
1.
这段代码是由冗长的if-else分支判断组合起来的,且if-else的逻辑也比较混乱,然后这段代码把4种Hsf的接口检查都耦合在了一起,没有扩展性。
后续增加任何功能,都需要在原来耦合的代码里添加代码,有可能会影响原有功能。
2.
这段代码没有做到开闭原则,一段良好的代码需要做到对扩展开发,对修改关闭。
3.
所有实现Hsf校验的逻辑都在一个handler类中,导致这个类中的代码很多,从而影响了代码的可读性、可维护性。
4.
这段代码的if-else条件判断很难懂,无法判断某个条件中的校验到底是校验哪一种Hsf校验类型,每次查看这段代码都要研究好久。
可以使用策略工厂模式来解决以上问题,把每种Hsf校验的方式封装起来,然后通过策略工厂模式来路由下发,把冗长的代码解耦出来,形成了一套框架,并且保证了代码的扩展性。废话不多说,直接看代码。
public class CheckStrategyFactory {
private final Map strategyRegistry = new HashMap<>();
@Autowired
public CheckStrategyFactory(HsfAssertCheck hsfAssertCheck,
HsfCrossInterfaceAssertCompare hsfCrossInterfaceAssertCompare,
HsfFullCompareCheck hsfFullCompareCheck,
HsfMultipleJsonPathCompareCheck hsfMultipleJsonPathCompareCheck,
JsonPathCompareStrategySelector jsonPathCompareStrategySelector,
CrossInterfaceAssertCompareStrategySelector crossInterfaceAssertCompareStrategySelector,
FullCompareStrategySelector fullCompareStrategySelector,
AssertStrategySelector assertStrategySelector) {
strategyRegistry.put(assertStrategySelector, hsfAssertCheck);
strategyRegistry.put(crossInterfaceAssertCompareStrategySelector, hsfCrossInterfaceAssertCompare);
strategyRegistry.put(fullCompareStrategySelector, hsfFullCompareCheck);
strategyRegistry.put(jsonPathCompareStrategySelector, hsfMultipleJsonPathCompareCheck);
}
public HsfInterfaceCheck getStrategy(ThubNodeConfig node, JSONObject checkConfigStatusObject,TestsuiteAttributeModel attributeModel , ExecuteResultModel executeResultModel) {
for (Map.Entry entry : strategyRegistry.entrySet()) {
if (entry.getKey().matches(node, checkConfigStatusObject, attributeModel, executeResultModel)) {
return entry.getValue();
}
}
return null;
}
}
再创建2个接口,一个策略选择接口CheckStrategySelector,一个Hsf校验接口HsfInterfaceCheck。
public interface CheckStrategySelector {
boolean matches(ThubNodeConfig node, JSONObject checkConfigStatusObject , TestsuiteAttributeModel attributeModel , ExecuteResultModel executeResultModel);
}
public interface HsfInterfaceCheck {
CheckOutputModel check(CheckCaseModel caseParam, BuildTestsuiteModel checkRecordModel, ExecuteResultModel executeResultModel);
}
再创建4个策略类和4个Hsf校验类分别实现策略选择接口CheckStrategySelector和Hsf校验接口HsfInterfaceCheck。
以下是HsfCheck1和HsfCheck2策略选择类,省略其他2个。
public class AssertStrategySelector implements CheckStrategySelector {
@Override
public boolean matches(ThubNodeConfig node, JSONObject checkConfigStatusObject, TestsuiteAttributeModel attributeModel , ExecuteResultModel executeResultModel) {
String assertExpectStr = StringEscapeUtils.unescapeJavaScript((String)executeResultModel.getValueByKey(CheckCaseInfoConst.EXPECT_RESULT));
return !(checkConfigStatusObject == null ||checkConfigStatusObject.isEmpty() || checkConfigStatusObject.getBoolean("isCheck") == false) && (assertExpectStr == null || !node.isCustom());
}
}
public class FullCompareStrategySelector implements CheckStrategySelector {
@Override
public boolean matches(ThubNodeConfig node, JSONObject checkConfigStatusObject, TestsuiteAttributeModel attributeModel , ExecuteResultModel executeResultModel) {
return attributeModel.isCompare() && (node.getJsonPath() == null || node.getJsonPath().trim().isEmpty());
}
}
以下是HsfCheck1和HsfCheck2校验类,省略其他2个。
@Service("hsfAssertCheck")
public class HsfAssertCheck implements