360Qtest团队 广告 sdk 自动化测试实践

willys · 2017年11月09日 · 最后由 ming亮 回复于 2020年06月01日 · 417 次阅读
本帖已被设为精华帖!

背景

广告 sdk 是什么

  • 广告 sdk,是为接入移动广告平台的开发者提供的应用程序接口 api 的程序集合。广告 sdk 支持如下功能:
    • 广告请求
    • 广告打点
    • 广告渲染
  • 第三方 app 可以嵌入广告 sdk,并且调用 sdk 的接口完成广告请求、广告展示、广告渲染、广告打点等逻辑。应用开发者可以关注自己的应用开发,而与广告相关的逻辑均交给广告 sdk 进行处理。

功能测试背景

  • 近半年来,广告 sdk 提测频繁,版本迭代快,导致测试排期紧张,功能测试同事压力大。
  • 广告 sdk 接入了多家广告平台,并且每一家广告平台都拥有十多种不同格式的协议,使得广告打点数据校验逻辑异常复杂。通过人工检查难以避免校验疏漏。
  • 大量的广告打点数据校验导致回归测试工作量大。

自动化测试目的

  • 通过自动化测试方案,提升测试效率,解放人力
  • 通过代码的严格检查保证打点数据的正确性
  • 形成 sdk 自动化测试的解决方案,为其他 sdk 业务测试团队提供借鉴思路

基本的测试场景

下图展示了 sdk 功能测试的基本场景:

最常见的测试场景步骤:

  • 使用嵌入了 sdk 的测试 demo,请求一次广告。sdk 是 jar 包形式,sdk 运行需要 Android 上下文支持,所以需要嵌入测试 demo 进行测试。
  • 使用 fiddler 抓取 sdk 广告请求,并且校验广告请求数据是否正确。广告请求数据异常会导致线上无法返回广告。例如,某个版本在测试环境中因为某个字段的类型不正确,导致测试环境中无法返回广告。
  • 依据测试 case 在 fiddler 中 mock 广告数据返回给 sdk。为什么需要 mock 服务端的广告数据呢?因为广告 sdk 针对不同类型的广告数据,会有不同的处理逻辑。例如,对于下载类广告和原生广告,同样是点击操作,而触发的广告行为完全不一致。所以功能测试过程中需要 mock 大量的不同类型的广告数据,来覆盖到 sdk 不同的功能逻辑,保障 sdk 的质量。
  • 展示 sdk 获取到的广告。对广告进行展示,覆盖用户场景。
  • 校验广告展示打点。打点数据涉及计费结算,数据正确性需要着重保障。
  • 依次进行重复曝光、点击广告、下载等场景测试

测试难点

虽然测试场景和测试步骤看似简单,但是功能测试依然面临如下几个难点:

  • 打点数据多样性
    • 广告 sdk 接入了多家广告平台,每个广告平台有 10 多种的广告协议。广告协议中携带的 json 数据中包含了 30-40 个数据字段。功能测试同事需要掌握大量的协议格式,才能保证打点数据的正确性。多样性的广告协议,给功能测试同事增加了非常大的工作量。自动化测试也同样面临打点数据类别区分以及正确性校验的难题。
  • 打点时序控制
    • 从前面的测试场景,我们知道各种类型的打点数据触发时机不一样,并且有严格的先后顺序。功能测试过程中,为了覆盖到所有的打点数据校验,测试同学需要设计各种操作场景来触发 sdk 打点行为。广告数据打点是在移动端进行触发,而抓包和 mock 在 pc 端进行。如何对两者进行协同,对打点时序的进行严格控制,也同样是自动化测试的难点。
  • mock 数据频繁
    • 服务端返回的广告数据直接影响 sdk 广告行为和打点数据。为了提高测试覆盖度,测试过程中往往需要构造各种类型的广告数据,达到覆盖各种测试场景的目的。自动化测试过程中,如何将设计的测试场景和 mock 数据进行关联,并且进行动态的变换同样是难点之所在。

自动化测试方案需要满足的需求

依据基本的测试场景和功能测试难点,广告 sdk 自动化测试需要满足如下需求:

  • 自动触发广告行为
    • 包括广告请求、曝光、点击等行为,还包括清理缓存,杀进程等行为
    • 只有自动触发广告行为,才能让自动化测试真正脱离人工干预
  • 动态 mock
    • 需要依据设计的自动化 case,动态的 mock 不同的广告数据,覆盖各种测试场景和 sdk 的广告处理逻辑
  • 数据校验
    • 自动化测试最重要的一个环节,需要依据期望结果,校验实际结果,从而判定该条 case 是否通过。从而发现 sdk 存在的缺陷。

方案框架示意图

整个自动化测试框架,主要分为测试 demo、mock 服务器和控制服务器三部分:

  • 测试 demo(sdk 宿主 app)
    • 测试 demo 嵌入了广告 sdk,是测试的主体程序。
  • mock 服务器
    • 同时也是代理服务器
    • 抓取广告请求数据、打点数据
    • 为 sdk mock 广告数据
  • 控制服务器
    • case 管理
    • 控制 case 执行
    • 协同 sdk 与 mock 服务器进行场景测试
    • 进行结果校验

分层设计

对整体架构进行了分层设计,主要分为框架层,逻辑层和数据层。

  • 框架层,主要采用开源或是第三方框架作为底层计算、调度支持。
  • 逻辑层,主要依据底层框架开放的接口,实现符合自身业务需求的代码逻辑。例如,控制服务器通过逻辑层对具体的测试场景进行实现。
  • 数据层,主要是测试过程需要用到的一些数据,包括测试 case、mock 的广告数据等。

  通过对架构进行分层设计,将框架、逻辑和数据做分离,使得架构的健壮性和可复用性更强。自动化测试面临的一个问题是,方案的可复用性是否足以应对业务逻辑的变更。在我们的业务中,即使服务端与 sdk 之间的交互协议发生了变化,只需要对数据层的数据做变更即可。而不需要对逻辑和架构做修改。

控制服务器

  控制服务器是整个自动化测试框架的大脑,控制 case 执行,协同测试 demo 和 mock 服务器工作,并且对测试结果数据进行校验。说到自动化测试,大家可能有疑问,如何书写测试 case 并对 case 进行管理,如何控制 case 执行?如何控制测试 demo 自动完成广告请求、曝光、点击等操作?答案就是两个测试框架:cucumberappium

cucumber

Cucumber 是一个能够理解用自然语言描述的测试用例的支持行为驱动开发(BDD)的自动化测试工具,用 Ruby 编写,支持 Java 和·Net 等多种开发语言。 ---百度百科

cucumber 有两个特性:

  • 理解自然语言描述的测试用例
  • 支持行为驱动开发(BDD)

下面详细了解一下控制服务器的数据层和逻辑层如何进行实现。

  • case 描述(数据层)

  我们将测试场景,抽象成一个个测试的执行步骤,然后用自然语言描述成 cucumber 支持的格式。最后 cucumber 通过 BDD 框架控制 case 执行。

自动化测试 case 示例:

场景:MAX原生广告"请求""请求打点"完整性校验
 假设 caseID"10001"
 假设 初始化mock文件为"360原生广告"
 假设 打开原生广告界面
 并且 填写广告条数"3"
 并且 填写广告位条数"1"
  点击按钮"获取广告"
 那么 请求的完整性如"juhe-adrequest-schema"描述
 那么 请求及字段值校验如下
 |count|        url          |   field     |  value |
 |  1  |https://domain?pver=2|  adtype     |    3   |
 |  1  |https://domain?pver=2| adspaceid   | 5a56tq | 
 那么 请求的完整性如"juhe-tk-request-schema"描述
 并且 聚合请求打点字段校验
 |count |type |   field  |    value     |
 |  1   |  0  |agspaceid | ag56q0xf |
 |  1   |  0  |adspaceid |  5a5q08xf  |
 |  1   |  0  |agappkey  |   ag318422   |
 |  1   |  0  |adappkey  |     318422   |
 |  1   |  0  |plid      |       1      |
 |  1   |  0  |channelid |       7      |
 |  1   |  0  |status    |       0      |
 |  1   |  0  |reqnum    |       3      |
 |  1   |  0  |resnum    |       1      | 

  上述的自动化测试 case 描述了对广告请求字段进行校验的测试场景。测试 case 采用自然语言对测试场景进行描述,包含测试过程中前置条件、测试步骤和测试结果校验等信息。通过 cucumber 支持的假设、当、并且、那么等关键字对测试 case 进行定义。通过该种方式整理 case,测试场景和校验逻辑一目了然。

  • case 执行(逻辑层)

  虽然 cucumber 支持的 BDD 框架控制 case 执行,但是需要我们自己对测试 case 描述的每个步骤逻辑进行实现。这些步骤中,需要实现 case 描述的具体逻辑。
  例如,case 中描述的步骤 “点击按钮”,步骤实现逻辑需要调用 appium 相关接口对测试 demo 按钮进行点击。

@假设("^caseID\"([^\"]*)\"$")
public void getCaseID(String arg0) throws Throwable {
    // Write code here that turns the phrase above intoconcrete actions
    // throw new cucumber.api.PendingException();
}

@假设("^打开原生广告界面$")
public void openNativeActivity() throws IOException {

}

@并且("^填写广告条数\"([^\"]*)\"$")
public void fillInAdNum(String adnum) throws IOException {

}

@当("^点击按钮\"([^\"]*)\"$")
public void clickButton(String btnType){

}

@那么("^请求及字段值校验如下$")
public void checkUrlField(List<UrlRequestField> list){

}

demo 自动化方案

方案选型

  通过 demo 对广告 sdk 进行自动操作,是整个自动化测试最为关键的一个环节。只有测试 demo 可以 “自主” 请求广告、曝光、点击才能完全进行自动化测试。如果 sdk 不能自主触发广告行为,会直接导致测试 case 执行失败。在考虑 demo 自动化测试方案时,面临两种选择:接口调用方式以及 UI 自动化。

接口调用

  广告 sdk 的广告行为,都是通过宿主 app 调用 sdk 的各种 api 进行实现的。例如,宿主 app 的广告请求行为,也就是调用 sdk 的广告请求 api 完成。所以 demo 自动化方案,也可以通过 api 调用方式进行。该方案有如下特点:

  • 直接调用 sdk 接口
  • 实现简单
  • 传递参数复杂(需要特定的语义描述复杂的测试场景,各种接口调用顺序,以及各个接口的参数)
  • 不支持清空缓存、下载第三方 app 等操作(sdk 需要对一些复杂的操作场景进行测试)

UI 自动化

  UI 自动化通过操作测试 demo 完成对 sdk 的广告行为,该方案相当于模拟用户的操作行为,最接近真实的测试场景。该方案有如下特点:

  • 需要第三方 UI 自动化框架支持
  • 模拟用户操作,符合实际操作场景
  • 支持复杂的操作场景(杀进程、清空缓存等)
  • 需要进行机型适配(权限弹窗点击、安装弹窗等)

  考虑到 sdk 测试,很多时候需要对测试 demo 进行杀进程、清理缓存、安装第三方 app 等操作,故最终选择 UI 自动化测试实现 demo 自动化方案。

UI 框架选型

  前面提到了,我们为了覆盖复杂的测试场景选择 UI 自动化方案对广告 sdk 进行自动化操作。考虑到广告 sdk 有 Android 和 ios 两个版本,需要 ui 自动化框架具有跨平台特性。我们采用 appium 测试框架实现测试 demo 的自动化操作。

Appium 是一个开源的、跨平台的自动化测试工具。它适用于原生应用、混合应用和移动网页应用。该框架支持 iOS,Android 和 FirefoxOS 的模拟器和真机。

  我们通过 appium 对 demo 进行自动化操作,从而完成各种测试场景的操作行为。

UI 自动化方案要点

  UI 自动化方案的健壮性直接关系到自动化测试是否能够顺利完成。对测试 demo 进行自动化操作的过程中,如果出现获取控件元素失败等异常情况,则会导致该条 case 执行失败。UI 自动化方案实现,面临如下问题:

  • 权限弹窗问题
    • 广告 sdk 有一些测试场景,需要清理缓存,然后重新启动。这个时候在某些品牌手机上会有权限弹窗提示,影响测试 demo 按钮点击,从而影响 case 顺利执行。
  • 安装弹窗问题
    • 广告 sdk 的下载类广告,需要下载第三方 app 并且安装。安装过程中会有安装界面调起,这时候覆盖测试 demo 影响测试正常进行。

考虑解决方案如下:

  • 轮询(等待)+ 重试机制
    • 针对第三方 app 下载时间不确定性,可能导致安装界面覆盖测试 demo。我们考虑的方案是,等待 app 下载完成,并且安装完成再进行 demo 上的下一步操作。
    • 对某些控件的点击增加重试机制,保障测试场景顺利进行。
    • 需要对轮询机制进行条件限制(轮询次数或者轮询时间进行判断)避免陷入死循环。
  • 兼容性考虑
    • 考虑多条件组合查询,利用 xpath、resourceid 或 text 对控件元素进行定位。
    • 权限弹窗上的按钮在不同的手机上可能有不同的 resource-id,这个时候可能需要借助 xpath 方式进行元素定位,甚至需要 text 进行辅助。
  • 执行步骤之间的停顿
    • 由于不同手机的性能不一样,处理 ui 操作的速度也不一样。很多时候 cucumber 控制步骤之间,需要一些停顿时间才能保证下一步骤执行前,上一个步骤已经执行完毕。
  • rerun 机制
    • 即使对兼容性方案进行了充分考虑,还是存在意外情况导致 ui 操作执行失败,导致该条测试 case 测试不通过。此时,通过 rerun 机制排除意外因素对测试结果干扰。
  • 效率和准确性权衡
    • 为了让 ui 自动化方案可以适配更多手机,可能增加更多的判断逻辑,这样兼容性会更强,但同时也会影响代码执行效率。
    • 元素定位不同的定位方式也有区别:xpath 定位控件效率低,但兼容性更强,定位元素准确。而 resource-id 定位控件快速,但是兼容性较弱。
    • 在实际项目中,我们需要依据实际项目情况对兼容性和效率进行权衡。如果需要适配大量的不同机型的手机,那么需要对兼容性考虑更全面一些。

控制服务器框架图

要点:

  • cucumber 是广告 sdk 自动化测试的控制框架。
    • cucumber 通过 mock 配置文件告知 mock 服务器(anyproxy),需要抓取什么样的数据包,以及需要如何 mock 数据。
  • cucumber 通过 appium 框架提供的 api,完成对 demo 的自动操作。
  • cucumber 需要从数据库中获取结果数据,并对结果数据进行校验,得出测试结论。

mock 服务器方案

anyproxy

mock 服务器主要采用阿里开源的代理服务器 anyproxy 进行实现。anyproxy 主要有如下特点:

  • 基于 nodejs
  • 提供 web 版界面,可以实时观测网络请求
  • 开放接口,允许用户自定义规则,易于二次开发

数据层

  前面提到控制服务器 cucumber 需要给 mock 服务器下发 mock 文件,对 mock 服务器的 mock 逻辑进行控制。主要涉及以下两个文件:

  • mock 配置文件

    • json 形式,以 key-value 形式指定 mock 规则。json 中 key 字段为需要抓包或 mock 的 url,value 是相应的 mock 数据文件地址。
    • 指定抓包的 url:由于在测试过程中,代理服务器捕获的网络请求不只有 sdk 发送的请求,也有 pc 端和手机端其他的数据流量。为了避免这些数据影响测试结果的校验,所以在 mock 服务器端指定需要捕获和 mock 的网络请求。
  • mock 的数据文件

    • 不同的测试场景,需要 mock 不同的广告数据,覆盖 sdk 不同的代码逻辑。针对每条测试 case,都需要指定该测试场景对应的 mock 数据。

逻辑层

  虽然数据层规定了 mock 规则以及给定了 mock 数据。但是还是需要在逻辑层对具体的 mock 逻辑进行实现。而 anyproxy 提供一系列接口供我们方便实现抓包和 mock 逻辑。mock 服务器在每一个 case 执行结束,都需要将抓取的广告打点数据存入数据库,供控制服务器进行结果校验。

mock 服务器数据交互图,如下:

动态 mock 实现

  前面已经提到自动化测试方案需要满足动态 mock 的测试需求。我们通过mock 配置文件 + 控制信号实现了动态 mock。
mock 服务器的 mock 规则是通过 mock 配置文件进行控制的。mock 配置文件指定了 mock 服务器需要抓取的数据包和对应的 mock 数据。而通过控制信号,控制服务器会依据测试场景动态更改 mock 配置文件。通过控制信号和 mock 配置文件的协同,实现了动态 mock。

数据流图

  为了保证每条测试 case 顺利执行,控制服务器通过控制协议协同测试 demo、mock 服务器一起工作。
控制协议数据流图如下所示:

包括三种控制协议:

每种控制信号都携带 caseID,将 sdk 打点数据与测试场景进行关联。

  • 测试开始信号:
    • 控制服务器通知测试 demo 和 mock 服务器新的一条测试 case 开始执行。
    • mock 服务器接收到测试开始信号,需要重新从服务器获取 mock 配置文件和 mock 数据文件。接下来依据 mock 配置文件的规则进行抓包和 mock。从而满足该条测试 case 的测试需求。
  • 测试结束信号
    • 控制服务器通知测试 demo 和 mock 服务器该条 case 执行结束。
    • mock 服务器接收该信号后,将抓到的数据包写入控制服务器数据库。
    • 控制服务器从数据库读取测试数据,并对测试数据进行校验,输出测试结果。
  • mock 文件更改信号
    • mock 服务器接收到该信号后,重新从控制服务器端获取 mock 配置文件,更改 mock 规则。

下面通过 “原生广告请求打点校验” 测试 case 为例,说明动态 mock 如何生效:

  • 控制服务器 cucumber 获取一条新的 case,准备该条 case 对应的 mock 配置文件和 mock 数据文件。
  • 控制服务器依据 caseID,在数据库中创建新的数据项,用于存储 sdk 打点数据。
  • cucumber 通过 appium 控制测试 demo 发送测试开始信号(控制信号)。
  • mock 服务器收到测试开始信号后,从控制服务器获取该测试场景的 mock 配置文件和 mock 数据文件。
  • 然后 cucumber 通过 appium 控制 sdk 执行一系列广告操作。
  • mock 服务器依据 mock 配置文件的规则进行抓包和 mock。
  • 控制服务器通过 appium 控制测试 demo 发送测试结束信号,mock 服务器收到该信号后,将捕获的打点数据写入控制服务器数据库。
  • 在复杂的测试场景中,如果需要对 mock 规则和 mock 数据进行更换,那么控制服务器通过mock 更改信号通知 mock 服务器,重新获取 mock 相关文件。
  • 最后控制服务器对数据库中的打点数据进行结果校验。

结果校验

  自动化测试的最后需要对测试数据进行校验,得出测试结论。前面已经提到,mock 服务器会将捕获的 sdk 请求和打点数据,并且写入数据库。为了达到校验准确性,需要进行如下三个方面校验:

  • 数据格式(json 字段类型、字段是否有缺失等)
  • 请求数量
  • 关键字段值

  在我们的方案中,主要采用 json-schema 协议对网络请求的 json 数据进行校验。json-schema 是一种基于 json 格式定义的 json 数据结构规范。围绕 json-schema 协议有丰富的开源实现。通过这些基于 json-schema 协议的开源实现可以实现 json-schema 自动生成,json 数据验证。
下面看一下具体的 json-schema 示例:

原始 json

{
    "id": 2,
    "name": "An ice sculpture",
    "price": 12.5
},
{
    "id": 3,
    "name": "A blue mouse",
    "price": 25.5
}

生成的 json-schema

 {
   "$schema": "http://json-schema.org/draft-04/schema#",
   "title": "Product",
   "type": "array",
   "items": {
       "type": "object",
       "properties": {
           "id": {
               "description": "The unique identifier for a product",
               "type": "integer"
           },
           "name": {
               "description": "Name of the product",
               "type": "string"
           },
           "price": {
               "type": "number",
               "minimum": 0,
               "exclusiveMinimum": true
           }
       },
   "required": [
   "id",
   "name",
   "price"
   ]
  }
}

  通过 json-schema 协议提供的语义,我们可以定义每个 json 字段类型,以及该字段的必要性等特性。与此同时,还可以对字段值进行一定的限定。int 类型的数据,可以指定范围,也可以依据业务场景限定为特殊的值。string 类型的值支持正则匹配。并且我们通过业务 json 模板文件可以自动生成校验数据需要的 json-schema。不需要我们进行手动构建,节省了大量的工作量。

方案运行情况

  目前已经梳理了 75 条自动化测试 case,占到 case 总量的 25%,预计将来可以达到 40%。

  目前自动化测试方案,已经部署到持续集成环境中,开发提测以及上线前都可以直接运行自动化测试方案对 sdk 进行自动化测试。自动化测试使得回归测试耗时从 10 小时/人次减少到 0.5 小时/人次。自动化测试方案提升了测试效率,解放了人力。并且通过严格的打点数据校验,避免了人工疏漏造成线上问题。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 31 条回复 时间 点赞
026 将本帖设为了精华贴 11月09日 13:49
026 将本帖设为了精华贴 11月09日 13:49

很完整,今天朋友圈也转发了,点赞~

支持一下

学习下,APP 这块是我的弱项。

陈恒捷 回复

哈哈哈,谢谢~~😊 😊

赞!~

写的很棒,我去找找作者

不错的设计思路,然后 我现在才知道你想要确定 json-schema 的字段是必选或非必选😂 😂 ,一般都是在请求的时候会有说明是 必选还是非必选;我们这边的业务场景比较少 说明 response 返回的数据要求 必选或非必选

willys #14 · 2017年11月10日 Author
CC 回复

哈哈哈

这个有开源吗

非常完整的一套方案,特别是 mock 的实现,原来还可以这么用。学习了!点个赞!

willys #17 · 2017年11月12日 Author
吴逸明 回复

目前没有。并且有些部分代码与业务逻辑紧密结合。

@willys 写的很好,我想把这个帖子转到我们公司内网论坛,并注明出处,可以么?

前年测广告 SDK 也想了很久的 UI 自动化~ 各种局限,今天涨姿势了。

willys #20 · 2017年11月14日 Author
YangClyde 回复

可以的。

willys #22 · 2017年11月14日 Author
YangClyde 回复

请问贵司是??

willys 回复

洛阳艾克科技

请问,anyproxy 如何动态的切换不同的 mock 数据文件

willys #25 · 2018年05月08日 Author
王超 回复

我们是通过测试 demo 事先发送控制信号,告诉 anyproxy 切换 mock 文件。收到控制信号的 anyproxy,重新读取新的 mock 文件,进行 mock。

willys 回复

嗯,这个我知道,但是貌似 anyproxy 不支持 reload 加载机制,我每次切换 rule 文件都得用anyproxy --rule demo.js重新启动 anyproxy;
请问你们是对 anyproxy 进行二次开发了吗?

willys #27 · 2018年05月09日 Author
王超 回复

规则文件,动态读取文件。规则文件里面的逻辑可以自己去实现,动态读取文件。

willys 回复

了解了 我去学习下 js 的语法,谢谢!😀

willys #29 · 2018年05月09日 Author
王超 回复

加油~~~

只验证 demo 上 ui 展示还是也包括了一些非 ui 上可体现的输出,不如点击、曝光的上报等

willys #31 · 2018年05月23日 Author
minizuo 回复

主要是校验 sdk 打点数据,没有 ui 校验。

simple 专栏文章:[精华帖] 社区历年精华帖分类归总 中提及了此贴 12月13日 06:44
simple [精彩盘点] TesterHome 社区 2018 年 度精华帖 中提及了此贴 01月07日 04:08

个人觉得本文只是对接口进行数据的正确性验证。无法验证其稳定性,兼容性,性能,安全性。对于接口的验证,本人推荐两种方式:1、广播方式触发 sdk 内部接口,2,instrumented 单元测试接口方式 ;不建议使用 ui 自动化形式。那样需要其他框架支持以及需要些一些 UI 界面,适配量比较大,不稳定。另外,sdk 在稳定性,以及性能上是非常重要的,尤其是 TO-B 的项目。

厉害,点赞

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册