算法自动化测试的挑战与思考

一、背景

从入职到现在已经有3个多月了,从最初的对于电商广告业务和算法测试的一头雾水,到现在的渐渐了解,基本上已经可以胜任项目的常规质量保障任务。然而,目前的质量保障手段依然停留在手工测试层面,自动化相对来说依然是比较空白。

关于持续集成和自动化,早已不是新鲜的话题。但是搜了下内网上关于自动化的一些话题,发现最近的一些文章还是有不少关于自动化价值的讨论,我觉得自动化测试其实跟任何一种其它测试类型(包括功能测试、异常测试、性能测试、稳定性测试等)一样,它本身属于测试范畴的一种。既然是测试,我们首先要明白的是“测试需求”是什么。盲目地为了自动化而自动化,导致自动化使用姿势不当或者目标不明确,甚至不知道痛点在哪,是无法将自动化做好的。

之前在TesterHome发表过一篇关于自动化的文章《我对自动化测试的一些认识》,可能随着当前行业测试技术的快速发展,有一些技术手段已经有些陈旧,但是其中的大部分观点至少到目前为止与当前自己的思想还是比较一致的。欢迎有兴趣的测试同学一起探讨,对不足之处进行指正批评。

二、算法测试的痛点

目前我负责的是广告算法的质量保障工作,测试开发比将近1:20,按目前的情况来看,开发数量还有增大的趋势。整个团队并行在推进的项目可能在10个左右,总体来说测试资源非常紧张。

一方面,作为一个测试开发同学,在兼顾业务保障的同时,往往还需要保障多个组内外平台及工具的开发或者其他流程沟通推动的相关工作。另一方面,我们的开发同学其实都有较高的质量意识,有强烈的自测诉求,但是缺乏高效的自测手段。

因此,如何将项目测试过程固化沉淀、形成自动化保障体系,赋能开发自测,释放测试自己的资源,是当前不得不进行的一个事情。

三、算法业务特点

1. 与网站应用/工程测试的比较

算法因为其自身的特殊性,大部分广告算法实际上是对已有算法的改进和结合,涉及的技术包括点击率预估、搜索、推荐、图、NLP、图像等。以搜索广告为例,在流量变现方面,它的主要优化方向集中在两个方面:排序(点击率预估、出价优化)和匹配(召回、相关性)。从工程的角度来看,大部分项目对工程结果的影响是广告商品的排序不同或是召回商品的内容和数量变化。不同于传统的网站应用/工程测试(可能会有不断的新功能的产出),相对来说,如果抛开算法效果测试来看,在功能层面,算法的工程结果是比较稳定的。

2. 与传统接口测试的比较

算法的开发主要包含两部分:离线任务开发和在线工程开发。离线任务开发包括数据分析、业务建模、特征提取、模型训练、模型调参等。在线工程开发主要是将算法模型服务化,以接口的形式提供给外部去调用。从集成测试的角度来看,算法的功能测试可以归为“接口测试”的范畴。

传统的接口测试,除了校验接口的返回状态码是否正确外,比较容易根据业务的处理逻辑对返回的结果字段进行校验。比如查询类的get接口,我们可以通过接口的返回结果与数据库的结果进行比较;比如提交类的post接口,我们也可以通过调用接口,根据传入的参数的不同,结合对系统的不同处理逻辑的了解以及数据库得到一个确定的预期返回。

与之相对的,算法的接口没有一个确定的预期。同一个接口,传入完全相同的参数,在不同的时间段调用可能会返回不同的结果。因为很多时候,算法的接口返回是一个概率值,如“某个商品被某个用户点击的概率”,影响算法返回结果的因素,除了服务代码之外,还包括索引的版本、特征词典的版本。特征词典的版本往往是日更新,而索引包含日级别的全量更新和实时的增量更新。虽然算法的功能相对稳定,但是算法的不断调整,实际上是对同一批接口的不断调整。

3. 当前的主要测试手段

目前算法工程质量的保障,主要还是集中在在线部分的测试。离线部分因为其时效性,往往会有T-1、T-2的延迟,更多的采用监控保证其质量。而对于在线部分的测试,主要的保障方式包括:

  • CodeReview
  • 功能验证
  • 线上请求日志回放
  • 性能测试
  • 稳定性测试

线上请求日志回放主要是基于线上的access_log日志,分别在新版本的环境和base环境进行请求回放,比较返回结果各个字段值的差异。是一种对功能验证的补充,避免人为地构造数据可能无法覆盖线上的所有场景。

四、算法自动化测试的挑战

前面说了,当前算法自动化测试的目标主要是想解决工程测试方面的效率问题,释放测试在工程质量保障方面的人力投入,因此本文主要是针对算法的“工程”测试自动化方面的一些思考。

理想的自动化测试链路应该是:

  • 代码提交
  • 静态代码检查
  • 单元测试
  • 代码打包
  • 词典更新
  • 测试环境部署
  • 索引更新
  • 功能冒烟回归
  • 线上日志回放
  • 变更代码覆盖率统计
  • 模块压测
  • 集成压测
  • 测试报告

整条链路的回归时间控制在一个小时以内,过程中既有中间测试结果的即时透出,也有最终报告的展现。下面是对于自动化测试链路的各个环节存在的一些挑战的思考:

1. 代码提交和管理

目前的开发代码大部分都是用git去管理,但是并没有一个明确且严格遵守的代码分支管理策略,且没有打Tag的习惯。算法和引擎开发同学有不同的开发习惯:

  • 算法的同学开发习惯是在线上分支master上拉一个feature分支,用于新版本的开发,然后待feature全量发布上线后再将feature合回master。
  • 引擎的同学开发习惯是直接将代码合到master上提测。

从测试的角度来看,这两种都存在一些问题:

  • 第一种方式,虽然保证了master一直是稳定可用的版本,但是当有多个同学并行在此项目上开发多个feature时候,在多个feature前后相继需要发布的时候,可能都是基于旧的master分支拉出的代码,如何相互合并是一个问题。且当feature分支上线后,再合并到master,如果出现conflict,无法保证解决conflict后的master代码与线上的代码一致。
  • 第二种方式,能保证提测的代码是合并conflict后的最新代码,同时也是测试通过后上线的真实代码。但是会影响到master分支的稳定性,当线上出现一些问题,需要紧急回滚的时候可能会出现问题。

在分支管理上不妨可以参考一些优秀开源项目(如 apache/sparkapache/flinktensorflow/tensorflow等)的普遍做法:

  • 对于只有一个大版本的代码,只维护一条master分支。通常大部分项目只有一个大版本,一些特殊的项目可能会存在多个大版本,如SP有3.0版本和2.0版本在线上同时使用,则相应维护两个分支。
  • 开发时,从master拉代码到本地进行开发,本地自测完成后合代码进master,及时解决冲突。
  • 提测时,以tag的方式提测。比如目前要上线的RS服务版本为v1.0,则第一次提测在master分支上打tag名v1.0.1。如果测试不通过,修改代码后重新提测,打tag名v1.0.2,依次类推。
  • 最终测试通过后,打一个没有后缀的tag:v1.0,表明是测试通过的可用版本。
  • 最终上线基于tag:v1.0进行发布。
  • 当线上验证通过,且运行稳定后,可以在上线一周后将v1.0.1、v1.0.2……等过程中的提测tag删除,只保留最后上线的稳定版本tag:v1.0。
  • 用同样的办法上线v2.0时,如果发生线上问题,需要回滚,可以立即回滚到tag:v1.0。

2. 静态代码检查

静态代码检查是一项低投入高回报的工程,从集团推出Java开发规约可见其重要性。“低投入”是因为只要一次配置,可以无限次的检查。“高回报”是它不仅可以帮我们纠正代码书写规范的一些问题,还可以发现一些诸如“空指针异常”、“I/O及数据库连接资源使用后未关闭”、“冗余变量”等一些重大的工程风险。

对于Java,有集团规约插件、PMD、CheckStyle、FindBugs等可以很方便地使用,而算法的开发则大多是使用C++,相应的静态代码检查工具还需要调研。目前了解到的有cppcheck、clint等。

3. 单元测试

目前RS服务已经使用了Google C++单元测试框架“Gtest”进行单元测试的编写,但是还处于刚刚起步状态,这部分需要去推动开发好好地补充测试用例,给出单元测试的覆盖情况统计,不让单测仅仅是流于形式。后续需要将单测的通过率和覆盖率数据透出到报告中。

4. 算法打包部署

不同于Java工程,基于Maven或Gradle,可以通过命令行很方便地实现整个工程“一键编译打包”,而且目前集团的Java应用大都已经可以基于Aone平台很方便地发布。而算法的发布因为其索引和词典的特殊存在,无法通过Aone很好地集成。算法包的发布包含三块内容:so文件(代码编译后的结果)、conf(配置)、data(词典),过程通常包含RPM打包和词典更新两个步骤,其中RPM包会包含so文件、conf和部分data。目前不同算法模块发布的平台各异,包括suez、drogo、autoweb等等,如何去规整并实现测试环境的自动发布是一项挑战。同时,在环境部署完毕后,我们还要保证新版本环境和base环境索引的一致性,或者当索引的构建也发生改变时,需要保证构建索引的商品库的一致性。

5. 功能冒烟回归

这一块需要对每个算法服务的使用场景进行具体的分析和细致的梳理,比如RS服务,它有来自bp、rsproxy、dump、rspe等不同的调用方的请求,每个调用方的可能的使用场景都需要一一梳理,从而提取出必要的冒烟接口测试用例。前面提到过算法接口每次调用的返回值可能会变化,
因此这部分用例的意义在于检测服务接口是否针对各个场景有正常的返回,返回结果的各个字段的值是否在一个合理范围内。

6. 线上日志回放

相比人为地构造数据,和AB切流实验,线上日志回放是一项相对覆盖面广且高效低成本的方式,通过自动化地回放大量的日志请求,我们可以发现新的算法版本是否对某一些不该产生变化的结果字段产生了影响,也可以发现一部分算法的效果问题:如出价优化时,整体价格是调高了还是调低了。

7. (变更)代码覆盖率统计

Java中有成熟的Jacoco可以用于工程覆盖率(深入到行、方法、类等不同级别)的统计,结合git的diff日志,还可以去挖掘工程两个不同版本间变化的代码的覆盖情况。而C++的覆盖率统计方式仍需要调研。

8. 性能压测

性能压测包括模块压测和集成压测。模块压测指的是如RS这样单个服务的压测,而集成压测是从ha3端或者SP端去看整体性能的情况。通常,性能的测试都是需要有一定经验的测试人员基于测试分析来制定详细的压测方案并予以执行和结构分析。但是,之前提过,算法的项目发布有很大的共性,正是基于这些共性,使得自动化压测成为了可能。要实现自动化地压测,除了要编写通用的压测脚本,还要有稳定的施压环境作为持续集成的节点机器。同时还需要去实现自动化地采集被压环境的系统性能变化数据(如cpu、内存、磁盘i/o、网卡性能等)。

9. 报告生成

要实现最终报告的自动化生成,需要涉及到整条链路的各个环节数据的自动采集,以及各项数据呈现和报表的展示,透出和提炼出对研发过程有帮助的数据,给出相应地测试结论。

五、总结

以上是个人对于算法工程质量保障的一些小小的想法,目的是将自己从“项目轮流转”的现状中解放出来。整条链路看似冗长,但是罗马不是一天建成的,我相信只要朝着正确的方向前进,自动化的效果会一步步地显现出来。

原文地址:https://www.cnblogs.com/znicy/p/9958235.html