|
原创声明:转载请附上原文出处链接章节一、业务背景二、思考 1、如何区分结果为有效或无效? 2、如何判断异常结果? 3、结果对比到底对比什么?三、探索相似度模型 1、参考算法 2、参考数学公式 3、设计思路 4、流程设计四、相似度算法试验 1、测试数据 2、校验结果值的差异 3、校验key的差异 4、校验数据结构完整性的差异 5、测试结果五、回放结果降噪六、总结 1、验证结论 2、优点与不足一、业务背景流量回放时出现大量diff失败的数据,排查和分析时,发现大量diff失败并非真正业务场景触发的失败,异常数据的干扰影响非常大,无法直接判断结果正确与否,对业务使用的体验性比较差如何做到diff失败的数据不会有非失败数据的干扰?diff失败数据无效diff失败数据二、思考1、如何区分结果为有效或无效?有效结果:属于业务服务相关的异常问题,为有效结果无效结果:非业务服务相关的异常问题,为无效结果先把已知道的异常类型全部列举出来:1、执行回放的服务造成目标服务报错2、回放的目标服务报错3、回放的目标服务依赖的服务报错4、回放结果数据转换报错5、回放结果的响应报文存在异常6、录制的流量响应结果存在异常7、回放结果响应报文与录制的响应报文存在数据差异8、回放环境服务不存在9、回放结果超时……异常类型很多,要一个分析很浪费时间和精力,从列举的异常类型分析,整体可以划分为2种:业务异常和非业务异常非业务异常:作为重点排除对象,把这类异常问题全部排除掉,对回放结果的干扰降到最低,甚至为0,例如下图,是repeater服务调用目标回放服务引发的异常问题,并非业务本身报错异常,判断为异常是无效异常无效异常数据业务异常:需要区分出真的异常和“假的异常”,过滤掉大量“假的异常”,需要做排除处理,控制这类异常问题干扰在个位数以内有效diff失败数据录制结果响应异常,回放结果响应正常,diff失败结果有效有效diff失败数据无效结果:例如下图,并非真正的业务异常,只是查询结果返回不一致,属于正常业务响应结果,判断为diff失败是无效异常无效diff失败数据有效结果:例如下图,录制正常返回数据,回放时结果为空,diff失败结果有效2、如何判断异常结果?人工一条条数据分析查看,得出结果成功与否回放结果列表根据类型拉取diff失败和异常的数据查看服务日志是否有异常报错查看业务场景回放数据是否正确拿请求参数单接口重新测试,再对比结果……以上做法,不论是采用哪种,都不是高效且又快速能得出可靠结果的方式,每个任务排期都是相对固定的,再投入多余时间去分析很多无差别的数据,脑壳不够用,完全不够用!相比之下,可能人工测试的结果来得更可靠和直观些,至少能直接知道结果有效与否不禁陷入深思,流量回放的能力本来是要提高工作效率的,最终却因结果分析反而成为负担 …… …… …… 疯狂抓头发!!!有画面感吧,虽然离秃还差那么3000发丝3、结果对比到底对比什么?结果状态?结果值的差异?参数key的差异?响应文本的差异?感觉只对比哪个结果也不一定对,又感觉几项都缺一不可……三、探索相似度模型基于思考的方向,探索比对两个结果的模型,查阅大量文献之后,初步设计相似度模型:1、参考算法杰卡德相似系数两个集合A和B交集元素的个数在A、B并集中所占的比例,称为这两个集合的杰卡德系数,用符号J(A,B)表示。杰卡德相似系数是衡量两个集合相似度的一种指标(余弦距离也可以用来衡量两个集合的相似度)。杰卡德系数杰卡德距离杰卡德距离(JaccardDistance)是用来衡量两个集合差异性的一种指标,它是杰卡德相似系数的补集,被定义为1减去Jaccard相似系数。杰卡德距离杰卡德距离用两个集合中不同元素占所有元素的比例来衡量两个集合的区分度。2、参考数学公式及其原理编辑距离(Levenshtein距离):公式:计算将一个字符串转换成另一个字符串所需的最少编辑操作次数。原理:通过比较两个字符串(在这里是JSON字符串),编辑距离公式可以度量从一个字符串变为另一个字符串所需要的最小修改次数。这些修改可能包括添加、删除或替换一个字符。Jaccard相似度:公式:Jaccard相似度=(两个集合的交集大小)/(两个集合的并集大小)。原理:Jaccard相似度是一个衡量两个集合相似度的指标。对于JSON对象,我们可以将其视为键值对的集合,Jaccard相似度可以帮助我们理解两个JSON对象在键值对集合方面的相似性。余弦相似度:公式:余弦相似度=两个向量的夹角的余弦值。原理:余弦相似度通常用于衡量两个向量之间的相似性。在比较JSON对象时,我们可以将每个对象转换为一个向量,其中每个维度的值对应一个键值对的值。然后,我们可以使用余弦相似度来比较这两个向量,从而理解两个JSON对象在值方面的相似性。这些公式和原理都是为了帮助我们更准确地比较和评估两个JSON对象的相似性或差异性。3、设计思路参考以上的算法和数学公式,主要有三个方向的规则设计:1.value--value:校验结果值的差异,得出百分比原理:这种方法基于计算两个JSON对象中对应值的差异,然后根据这些差异计算出一个百分比。这个百分比表示了两个对象在值上的相似度。公式:使用差异度量公式,差异百分比=(差异的总数/总数)*100%应用场景:当你需要比较两个JSON对象的值时,例如在数据质量检查、数据审计或数据同步的场景中。解决什么问题:这种校验可以快速了解两个对象在值上的差异程度,从而可以决定是否接受、拒绝或进一步处理这些差异。2.key--key:校验key的差异,只要相同key,且结果值不为空,则理解为相同,得出最终百分比原理:这种方法关注于JSON对象的键(key)。如果两个对象有相同的键,并且对应值不为空,那么它们被视为相同。基于这个信息,可以计算出一个百分比来表示键的相似度。公式:键的相似度百分比=(相同键的数量/总键数)*100%应用场景:当你的关注点是确保JSON对象中有关键的数据元素,并且这些元素在两个对象中都存在时。解决什么问题:这种校验可以发现丢失或新增的键,以及那些键对应的值是否为空。这有助于确保数据的完整性。3.structure--structure:校验json结构完整性,结构相同则为相似,得出百分比原理:这种方法评估两个JSON对象的结构是否相似或相同。如果结构相同,那么可以认为这两个对象是相似的。公式:使用二元判断,结构相似度百分比=100%-(结构差异的数量/总结构数量)*100%应用场景:当你需要确保两个JSON对象的结构保持一致时,例如在数据交换、数据集成或数据转换的场景中。解决什么问题:这种校验可以发现结构上的差异,从而帮助你识别可能的格式问题、不一致性或数据错误。4、流程设计1.分析和计算规则最终相似度计算公式:(值相似度*0.2+键相似度*0.4+结构完整性相似度*0.4)*100%=最终相似度为什么采用此计算公式?1)值相似度系数0.2:因为录制数据存在不同来源环境,且数据变化比较大,而回放环境只是测试环境,相同请求参数的条件下,最终响应结果可能存在不同结果值比较大,为兼容多种条件,故调整结果值的系数占比为最低2)键相似度系数0.4:评估同一个接口录制前端响应数据的key差异性,若前后变化很大,则说明业务有变化或者接口结构有变更3)结构完整性相似度系数0.4:同上系统diff:只要存在结果不一致则为失败,相等则为成功相似度算法:校验结果值差异,参数key差异,数据结构完整性差异,三者计算结果乘以系数得出最终百分比,按照百分比分段分析数据结果,小于80%(大于等于60%小于80%,为疑似异常,小于60%为异常)则为失败,大于或等于80%(大于等于80%小于90%,为疑似异常,大于90%为正常)则为成功2.diff逻辑diff逻辑3.diff结果分析diff分析四、相似度算法试验1、测试数据需要对比差异的两个json文本String jsonString1 = "{\"result\":{\"code\":0,\"data\":[{\"activityId\":\"123\",\"activityLevelDesc\":\"C\",\"activityStatusDesc\":\"未开始\",\"activityTypeDesc\":\"大促\",\"allActivityTime\":null,\"activityName\":\"活动N数不清了\",\"cateInfo\":\"手机、其他网络设备\",\"activityStatus\":0,\"canSignUp\":1,\"activityTime\":[\"2024年01月23日 15:43-2024年01月25日 10:38\",\"2024年01月26日 15:43-2024年01月27日 10:38\"],\"activityType\":1,\"activityLevel\":4,\"activity\":4},{\"activityId\":\"124\",\"activityLevelDesc\":\"B-\",\"activityStatusDesc\":\"进行中\",\"activityTypeDesc\":\"日常活动\",\"allActivityTime\":null,\"activityName\":\"kftest0116\",\"cateInfo\":\"手机\",\"activityStatus\":1,\"canSignUp\":1,\"activityTime\":[\"2024年01月16日 14:04-2024年01月31日 14:04\"],\"activityType\":0,\"activityLevel\":4},{\"activityId\":\"125\",\"activityLevelDesc\":\"A\",\"activityStatusDesc\":\"进行中\",\"activityTypeDesc\":\"日常活动\",\"allActivityTime\":null,\"activityName\":\"kftest01221720\",\"cateInfo\":\"笔记本、手机\",\"activityStatus\":1,\"canSignUp\":1,\"activityTime\":[\"2024年01月22日 17:21-2024年01月24日 17:21\"],\"activityType\":0,\"activityLevel\":2}],\"success\":true,\"cookieValue\":null,\"raw\":false,\"errorMsg\":null,\"errorJsonMsg\":\"\"},\"exception\":null}";String jsonString2 = "{\"result\":{\"code\":0,\"data\":[{\"activityId\":\"123\",\"activityLevelDesc1\":\"C\",\"activityStatusDesc\":\"未开始\",\"activityTypeDesc\":\"大促\",\"allActivityTime\":null,\"activityName\":\"活动N数不清了\",\"cateInfo\":\"手机、其他网络设备\",\"activityStatus\":0,\"canSignUp\":1,\"activityTime\":[\"2024年01月23日 15:43-2024年01月25日 10:38\",\"2024年01月26日 15:43-2024年01月27日 10:38\"],\"activityType\":1,\"activityLevel\":6},{\"activityId\":\"124\",\"activityLevelDesc\":\"B-\",\"activityStatusDesc\":\"进行中\",\"activityTypeDesc\":\"日常活动\",\"allActivityTime\":null,\"activityName\":\"kftest0116\",\"cateInfo\":\"手机\",\"activityStatus\":1,\"canSignUp\":1,\"activityTime\":[\"2024年01月16日 14:04-2024年01月31日 14:04\"],\"activityType\":0,\"activityLevel\":5},{\"activityId\":\"125\",\"activityLevelDesc\":\"A\",\"activityStatusDesc\":\"进行中\",\"activityTypeDesc\":\"日常活动\",\"allActivityTime\":null,\"activityName\":\"kftest01221720\",\"cateInfo\":\"笔记本、手机\",\"activityStatus\":1,\"canSignUp\":1,\"activityTime\":[\"2024年01月22日 17:21-2024年01月24日 17:21\"],\"activityType\":0,\"activityLevel\":2}],\"success\":true,\"cookieValue\":null,\"raw\":false,\"errorMsg\":null,\"errorJsonMsg\":\"{\\\"errorMsg\\\":null}\"},\"exception\":null}";两个字符串转为JSONObject对象 public boolean resultOfContrast(String recordResponse, String replayResponse){ JSONObject json1 = new JSONObject(recordResponse); JSONObject json2 = new JSONObject(replayResponse); …… 以录制的响应结果为作为基准(json1),对比回放的响应结果(json2),得出两者最终相似度结果。2、校验结果值的差异判断两个json对象所有结果值,首先把两个json对象进行序列化处理,取出所有值,返回两个map对象再一一对比,得出最终值的相似度百分比 /** * 对比两个json字符串的值是否相等,并计算出两个json字符串的相似度 * @param json1 * @param json2 * @return */ public static BigDecimal compareResultValues(JSONObject json1, JSONObject json2) { Map allValues1 = findAllValues(json1); Map allValues2 = findAllValues(json2); // int totalKeys = allValues1.size() + allValues2.size(); int totalKeys = allValues1.size(); int matchedKeys = 0; for (Map.Entry entry : allValues1.entrySet()) { if (allValues2.containsKey(entry.getKey())) { Object value1 = entry.getValue(); Object value2 = allValues2.get(entry.getKey()); if (value1 != null & value2 != null & isValueEqual(value1, value2)) { matchedKeys++; } } } double similarityPercentage = (double) matchedKeys / totalKeys; return BigDecimal.valueOf(similarityPercentage).setScale(4, RoundingMode.HALF_UP); } private static boolean isValueEqual(Object value1, Object value2) { if (value1 instanceof JSONObject & value2 instanceof JSONObject) { return compareJsonObjects((JSONObject) value1, (JSONObject) value2); } else if (value1 instanceof JSONArray & value2 instanceof JSONArray) { return compareJsonArrays((JSONArray) value1, (JSONArray) value2); } else { return value1.equals(value2); } } private static boolean compareJsonObjects(JSONObject json1, JSONObject json2) { if (json1.length() != json2.length()) { return false; } for (String key : json1.keySet()) { if (!json2.has(key)) { return false; } Object value1 = json1.get(key); Object value2 = json2.get(key); if (!isValueEqual(value1, value2)) { return false; } } return true; } private static boolean compareJsonArrays(JSONArray array1, JSONArray array2) { if (array1.length() != array2.length()) { return false; } for (int i = 0; i findAllValues(JSONObject json) { Map values = new HashMap(); findAllValues("", json, values); return values; } private static void findAllValues(String prefix, JSONObject json, Map values) { for (String key : json.keySet()) { String path = prefix.isEmpty() ? key : prefix + "." + key; Object value = json.get(key); if (value instanceof JSONObject) { findAllValues(path, (JSONObject) value, values); } else if (value instanceof JSONArray) { JSONArray array = (JSONArray) value; for (int i = 0; i > nonEmptyKeyPaths1 = findNonEmptyKeyPaths(json1); Map> nonEmptyKeyPaths2 = findNonEmptyKeyPaths(json2); // 使用流和 Collectors.toSet() 来获取所有键的集合 Set keysInBothMaps = nonEmptyKeyPaths1.keySet().stream() .filter(nonEmptyKeyPaths2::containsKey) .collect(Collectors.toSet()); int totalNonEmptyKeys = nonEmptyKeyPaths1.size(); int matchedNonEmptyKeys = (int) keysInBothMaps.stream() .filter(key -> nonEmptyKeyPaths1.get(key).equals(nonEmptyKeyPaths2.get(key))) .count(); double similarityPercentage = (double) matchedNonEmptyKeys / totalNonEmptyKeys; return BigDecimal.valueOf(similarityPercentage).setScale(4, RoundingMode.HALF_UP); }4、校验数据结构完整性的差异采用的是二元判断的方式,先把两个json对象进行序列化解析,JSONObject对比JSONObject,JSONArray对比JSONArray,key对key,返回两个map对象,再计算最终结构相似度,结构相同则为100%,否则对比两个json对象结构相似度的百分比,最终取两者交集的百分比作为结构完整性的相似度结果。 /** * 对比两个json字符串的结构完整度,并计算出相似度 * @param json1 * @param json2 * @return */ public static BigDecimal compareStructuralIntegrity(JSONObject json1, JSONObject json2) { Map> structure1 = flattenJsonStructure(json1); Map> structure2 = flattenJsonStructure(json2); int forwardTotalKeys = structure1.size(); int forwardCount = 0; for (Map.Entry> entry : structure1.entrySet()) { if (structure2.containsKey(entry.getKey())) { forwardCount++; } } int reverseTotalKeys = structure2.size(); int reverseCount = 0; for (Map.Entry> entry : structure2.entrySet()) { if (structure1.containsKey(entry.getKey())) { reverseCount++; } } // 正向百分比 double forwardPercentage = (double) forwardCount / forwardTotalKeys; // 反向百分比 double reversePercentage = (double) reverseCount / reverseTotalKeys; // 比较大小,取交集的结果,交集即是小的值 double smallerPercentage = Math.min(forwardPercentage, reversePercentage); return BigDecimal.valueOf(smallerPercentage).setScale(4, RoundingMode.HALF_UP); }5、测试结果测试结果五、回放结果降噪对比系统diff和相似度算法,结合实际回放数据进行分析,以下数据为某一案例分析结果:对比项回放总case数diff失败总数diff失败实际数diff失败中判断正确的比例diff结果正确率系统diif66732818857.32%53.35%相似度算法66721818886.24%86.89%人工判断结果667188188100%100%【结果分析】总共328条diff失败数据,相似度算法降噪之后还剩下218条diff失败,其中有43条分析结果不准确,通过相似度算法328-218=110个回放结果被正确识别为成功(系统diff为失败),且与人工判断一致系统diff正确率:(328-(110+43))/328=53.35%相似度算法正确率:(328-43)/328=86.89%【业务应用示例】成功列表和diff失败列表成功结果失败结果疑似异常:失败成功六、总结1、验证结论【算法实现】成功实现了系统diff对比相似度算法,能够准确比较两个系统之间的差异并输出最终相似度【测试验证】通过大量测试用例验证了算法的准确性和稳定性,证明了其在实际应用中的可行性。【应用场景实践】当大批流量回放结果,业务逻辑是正确的,但结果又是Diff失败的Case,这些Case从业务角度分析不应该作为失败的Case。这样的结果导致整体的成功率较低,同时加大了失败Case的排查难度。(比如,单据状态变更了,表单信息被修改了等等)基于以上问题,经相似度算法分析之后,对干扰数据做降噪处理,减少无效diff失败的Case,同时降低失败Case的排查难度,提高回放成功率,最终体现回放结果价值。2、优点和不足【优点】1、结果数据对比可以根据不同维度的差异,进行分析和计算,得出最终有效结果,具备一定的筛查能力。2、对比系统diff能力,准确率更高,且正确率高出30%以上。3、可动态配置计算比例,更灵活校验结果有效性。【不足】目前只判断结果值、参数key、结构完整性,无法判断结果数据是属于什么业务场景,以及业务场景是否属于正常响应已知不足的两个问题点:录制响应结果的整体结构与回放响应结果的整体结构不一致,且结果值也存在不一致,分析结果不一定准确相同参数,因业务场景流转,前后返回结果不一样,影响校验规则计算,分析结果不一定准确后续……探索更多应用场景,将算法应用于更多领域,发挥其更大的价值。虽然算法能够校准确分析两个json差异,但在处理大规模数据时效率还是会比较低,需要进一步优化算法以提高性能。目前的相似度分析机制还较为简单,仅基于两个json对象差异进行计算,偏程序化,不能动态判断业务场景结果,未来会考虑引入更多特征以提高算法分析的准确性。流量回放平台外传…… 老板:快速用1万条数据去覆盖业务场景,下班前做完 你:人工点点点………………何时休? 流量回放平台:要不来我这试试 关于作者庄锦弟,负责测试平台一体化能力基建想了解更多转转公司的业务实践,欢迎点击关注下方公众号:
|
|