社区项目迁移 Diff 自动化测试实践

技术

得物社区项目中包括内容搜索、穿搭精选、无结果推荐、搜索debug等功能。所有功能都在一个单体应用,各种本地缓存、配置、代码交织不利于维护;当某些功能有问题之后也会影响其他接口,不利于项目稳定性。本次需求RD打算将各类功能拆分到不同项目中。

我在社区搜索迁移项目中首次落地了 Diff 自动化测试,从测试到上线的过程中,累计发现很多处bug,发现了很多潜在的、不易发现的问题;最终该项目上线后流量从5%提升到100%,仅用一周完成全量的切换,且过程中指标无异常。

什么是Diff测试

Diff测试,从字面意思上理解,就是对比测试。深入到项目中理解的话,比如某一个工程比较陈旧,代码过于冗余和复杂,维护成本较高,为了解决这些问题,会进行项目重构、框架升级、代码解耦等等。

这样的项目有个特点:纯粹的代码优化、影响范围甚广、业务上有强烈需求要保证与之前的一致性。为了验证新的代码和老的代码达到的功能和效果是一致的,采用的结果比对的方式,这样的过程我们称之为Diff测试。

新老项目迁移测试痛点

  1. 老项目的业务逻辑复杂,场景较多,梳理困难;
  2. 返回的结果数据字段也众多,甚至冗余,但属于对外协议的一部分的内容,又必须要逐个去校验,导致效率低下。

我们接下来看看传统的Diff解决方案是怎么做的,对比传统的思路,再看我是怎么做的。

传统的Diff解决方案

picture.image

因为业务差异性,传统解决方案并不能在我们公司很好地落地,主要有以下两点原因:

  • 公司当前RPC框架不支持泳道机制,需要额外搭建一套环境;而搜索的opensearch等组件外购于阿里云,额外部署的这部分成本会相对较高;
  • 还有环境相关问题,经团队内部讨论,也找了开发RD始终无法绕过,于是只能 利用开发的AB机制,使用两组用户画像一致的uid,请求同一套环境,命中不同的AB新老框架,进行结果比对。
新的Diff解决方案

经过内部讨论以及测试,我们决定用新的Diff解决方案,如下图所示:

picture.image

当新的Diff方案能适应业务逻辑的时候,我们还要考虑一些其他的问题,如下:

1.业务逻辑复杂场景多--如何尽可能的命中到场景?

>>流量日志解析,根据场景筛选分布

a.获取线上热词100条,遍历验证Diff结果

b.少结果、无结果推荐

c.验证热搜置顶位、运营位、人为强干预

d.接口支持的常规筛选参数

e.App版本兼容性——478、479

f.不同平台兼容性——ios、android、h5

g.翻页

h.线上实验组遍历

>>随机流量

a.线上随机抽取真实用户画像

2.字段对比的实现思路

  1. 多种对比模式:仅字段数据结构,仅判断不为空
  2. 统一格式转换:白名单额外信息字段补进
  3. 对比结果自动入库

3.如何做到迅速分析归因?

当有bug导致不一致时,比如某个字段丢失,会大量的失败,如何确定还有无其他的bug?

a. 黑名单过滤机制

总结与反思

诚然本次Diff发挥了很好的效果,但是Diff测试并不是完美无缺的,项目结束,和项目成员内部对齐了下,至少还有以下两个可以持续改进的点:

  1. 覆盖率无法完全保证 ,仅能从流量场景入口尽可能覆盖,但是数据场景无法构造是天然的缺陷;

  2. 发现问题的时机 只有新老对比这一次,错过会一直存在,所以对问题归因务必要仔细;

当然这两个问题虽然完全解决比较困难,也有尽量减少影响的方案,我对Diff测试保持信心!

补充阅读-自动化工程代码实现

代码逻辑流程图

picture.image

表结构设计

picture.image

项目工程相关类

picture.image

IDiffFactoryService

/**               
 * @author Chris               
 * @version 1.0  
 * @date 2021/10/8 11:32 上午   
 */
public interface IDiffFactoryService {               
    /**
     * 根据env环境动态获取域名           
     * @param path 
     * @return
     */    
    String getPath(String path);
                           
    /**
     * 发送接口请求           
     * @param path  
     * @param param
     * @param method
     * @return
     */    
    JSONObject getResponseHttp(String path, Object param, String method);
                  
    /** 
     * 过滤部分字段,返回response           
     * @param jsonObject 
     * @param filterList
     * @return
     */    
    JSONObject getFilterRespone(JSONObject jsonObject, List<String> filterList);
                            
    /**
     * 对比结果           
     * @param oldJSONObject     
     * @param newJSONObject
     * @param keyList  (keyList为空,对比过滤后的字段,keyList不为空,则仅对比指定key的返回结果)     
     * @return
     */    
    AlgDiffResult getDiffResult(JSONObject oldJSONObject, JSONObject newJSONObject, List<String> keyList);
                             
}           

IDiffService

/**               
 * @author Chris               
 * @version 1.0  
 * @date 2021/10/8 11:47 上午   
 */
public interface IDiffService {
                                 
    /**
     * 整合并实现数据的请求及结果对比           
     *
     * @param diffParamRequest
     * @return
     */    
    AlgDiffResult diffCompare(DiffParamRequest diffParamRequest);
                             
    /**
     * 造数工具——批量生成用例           
     *
     * @param bodyRequest            
     * @return
     */    
    List<JSONObject> getOrAddCases(BodyRequest bodyRequest);
                         
    /**
     * 执行单条用例对比           
     * @param caseId
     */
    void diffCompareCase(int caseId);
                             
    /**
     * 遍历执行一个接口下的所有用例           
     * @param paramId
     */
    void diffCompareParam(int paramId);
                             
    /**
     * 遍历执行一个项目下的所有用例           
     * @param projectId
     */
    void diffCompareProject(int projectId);           
}

CommunityDiffTest

  @DataProvider(name = "query")               
    public Iterator<Object> dataQuery() {
        List<Object> listObj = ListUtil.toList();
        String content = iAlgTestDataService.getById(1).getContent();
        String[] strings = StrUtil.split(content, ",");
        for (String str : strings) {
            listObj.add(str.trim());
        }
        return listObj.iterator();         
    }
                  
    @Test(dataProvider = "query", testName = "热词")
    public void test(String data) {            
        ContentSearchParam contentSearchParam = new ContentSearchParam();
        contentSearchParam.setQuery(data);
        diffTest(contentSearchParam, ListUtil.toList("productMenu", "tags"));
    }

*文:胡朝阳

46
0
0
0
关于作者
相关产品
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论