laitimes

Use the policy pattern to eliminate the lengthy if-else|, remember a smart-auto refactoring summary

author:Flash Gene
Use the policy pattern to eliminate the lengthy if-else|, remember a smart-auto refactoring summary

Guide

The author has refactored the core code related to smart-auto interface testing to make the code clearer and maintainable.

1. Background

After years of iteration and the development of several generations of test students, the smart-auto tool is now very powerful, supporting the comparison and assertion of the return value of various HSF interface calls, and the test pieces run daily have accounted for more than 55% of all automated test pieces on Taobao Buycai. However, as the number of functions continues to increase, the code becomes larger and larger, and the previously single function becomes more complex, and the maintainability of the code is constantly decreasing. Therefore, the core code related to the smart-auto interface test has been refactored to make the code clearer and maintainable.

2. Analysis of the current situation

You can take a look at the core code related to interface testing before optimization, and you can briefly express the meaning of this code as follows:

1.工具中针对,Hsf接口校验共有四种方式HsfCheck1、HsfCheck2、HsfCheck3、HsfCheck4;

2. All interface verification methods are contained in a Handler, and all different methods are judged by various complex if-else branches.

3. The overall code is about more than 200 lines;

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
             }
            }
 }           

The full code is as follows:

@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{
            //如果jsonPathList不为空,则执行jsonPath解析,执行jsonPath之后的对比
            if(!jsonPathList.isEmpty()){
                List<CheckDiffModel> 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<CheckDiffModel> 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);
                }
//如果jsonPathList为空,走正常对比逻辑
            }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<CheckDiffModel> 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<CheckCaseResult> checkResultList = checkDataCaseService.queryCheckCaseResult(checkDataCaseQueryDO).getResult();
                    List<CheckDiffModel> checkCoonfigFailInfo = new ArrayList<>();
                    for (CheckCaseResult checkCaseResult : checkResultList) {
                        String checkConfigResult = checkCaseResult.getCheckConfigResult();
                        List<Map> checkParse = JSONArray.parseArray(checkConfigResult, Map.class);
                        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<CheckCaseResult> checkResultList = checkDataCaseService.queryCheckCaseResult(checkDataCaseQueryDO).getResult();
                    List<CheckDiffModel> checkCoonfigFailInfo = new ArrayList<>();
                    for (CheckCaseResult checkCaseResult : checkResultList) {
                        String checkConfigResult = checkCaseResult.getCheckConfigResult();
                        List<Map> checkParse = JSONArray.parseArray(checkConfigResult, Map.class);
                        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;
    }
}           

The above code has the following problems:

1. This code is composed of a lengthy if-else branch, and the if-else logic is also confusing, and then this code couples the interface checks of the 4 Hsfs together, and there is no extensibility. Any subsequent addition of functions requires adding code to the original coupled code, which may affect the original functionality.

2. This code does not achieve the principle of open and closed, and a good piece of code needs to be developed for extensions and closed for modifications.

3. All the logic that implements the Hsf check is in a handler class, which leads to a lot of code in this class, which affects the readability and maintainability of the code.

4. It is difficult to understand the if-else condition judgment of this code, and it is impossible to determine which Hsf check type is the verification in a certain condition, and it takes a long time to study this code every time you look at it.

3. Solution

The policy factory pattern can be used to solve the above problems, encapsulating each HSF verification method, and then routing and delivering through the policy factory pattern, decoupling the lengthy code, forming a set of frameworks, and ensuring the extensibility of the code. Without further ado, look directly at the code.

Let's start by building a strategy factory class:

public class CheckStrategyFactory {
    private final Map<CheckStrategySelector, HsfInterfaceCheck> 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<CheckStrategySelector, HsfInterfaceCheck> entry : strategyRegistry.entrySet()) {
            if (entry.getKey().matches(node, checkConfigStatusObject, attributeModel, executeResultModel)) {
                return entry.getValue();
            }
        }


        return null; // 兜底检查策略返回null
    }
}           

Create two more APIs, one for CheckStrategySelector and one for Hsf check.

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。

The following are the HsfCheck1 and HsfCheck2 strategy selection classes, omitting the other 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());
    }
}           

The following are the HsfCheck1 and HsfCheck2 check classes, omitting the other two.

@Service("hsfAssertCheck")
public class HsfAssertCheck implements HsfInterfaceCheck {
    @Resource
    private CaseConfigHandlerService caseConfigHandlerService;
    @Resource
    private CheckDataCaseService checkDataCaseService;
    @Override
    public CheckOutputModel check(CheckCaseModel caseParam, BuildTestsuiteModel checkRecordModel, ExecuteResultModel executeResultModel) {


}           
@Service("hsfFullCompareCheck")
public class HsfFullCompareCheck implements HsfInterfaceCheck {


    @Override
    public CheckOutputModel check(CheckCaseModel caseParam, BuildTestsuiteModel checkRecordModel, ExecuteResultModel executeResultModel) {


    }
}           

Eventually, the Handler code was transformed into this segment.

@Service("commonCheckHandler")
public class CommonCheckHandler implements CheckHandler{
    private final CheckStrategyFactory factory;


    public CommonCheckHandler(CheckStrategyFactory factory) {
        this.factory = factory;
    }




    @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);
        JSONObject checkConfigStatusObject = JSON.parseObject(checkRecordModel.getTestsuiteDO().getCheckConfigStatus());
        CheckOutputModel result = new CheckOutputModel();
        if(node==null){
            result.setSuccess(false);
            return result;
        }


        HsfInterfaceCheck hsfInterfaceCheckStrategy = factory.getStrategy(node, checkConfigStatusObject, attributeModel, executeResultModel);
        if(hsfInterfaceCheckStrategy != null){
            return hsfInterfaceCheckStrategy.check(caseParam, checkRecordModel, executeResultModel);
        } else {
            result.setSuccess(false);
            result.setErrorCode("未找到对应的校验策略");
            return result;
        }
    }
}           
Use the policy pattern to eliminate the lengthy if-else|, remember a smart-auto refactoring summary

The above code is split into multiple files through the strategy factory pattern, and the lengthy if-else code is broken down through the strategy factory pattern, let's take a look at whether the refactored code is better. The following diagram illustrates the overall logic of the refactoring:

After the refactoring, the factory class is created, and the policy judgment logic in the factory class determines which policy type it is, dynamically determines which strategy to use during movement, and finally routes it to the corresponding check method.

1. The final code achieves the following points: the refactored code conforms to the principle of open and closed, and when adding new policies, the code changes are minimized and centralized, and the risk of introducing bugs is reduced.

2. The refactored code decouples the complexity of the previous code, decouples the definition, creation and use of policies, and controls the code complexity so that the code of each part is not too complex and the amount of code is too large. Now the code for each class is basically displayed on a single display.

3. Greatly increased the readability and maintainability of the code.

Use the policy pattern to eliminate the lengthy if-else|, remember a smart-auto refactoring summary

Fourth, summary

Of course, not all if-else branches are bad code, as long as the if-else branch is not complicated and there is not much code, this is no problem, as long as you follow the KISS principle, how simple and how to come, is the best design. However, once there are many if-else branches, and each branch contains a lot of complex logical judgments, you can consider whether the policy pattern can be used to sort out the code more clearly, making the code more maintainable.

Author: Wang Feng (Weifeng)

Source-WeChat public account: Alibaba Cloud Developer

Source: https://mp.weixin.qq.com/s/tg4vTL6_TI-tnxaMyVLhsA