[关闭]
@liuhui0803 2016-09-02T01:45:53.000000Z 字数 7290 阅读 1563

设备自动化测试

Netflix 测试 体系结构 自动化


摘要:

本文是系列文章中的第1篇,主要介绍了Netflix在不同设备上针对Netflix SDK进行自动化的功能、性能,以及压力测试过程中所涉及的重要概念和所使用的基础架构。

正文:

作为Netflix SDK团队的一份子,我们需要负责确保新版Netflix应用程序经历彻底的测试,以最高运维质量部署到游戏主机平台,并以SDK(以及参考应用程序)的方式交付给Netflix设备合作伙伴,最终发布到数百万智能电视和机顶盒中。总的来说,我们的测试需要确保Netflix能够在数百万游戏主机和网络电视/机顶盒中流畅运行。

与服务器端的软件发布不同,针对这类设备发布会面临一些独特挑战,因为无法进行Red/black推送,出现故障也无法立刻回滚。在将代码发布到客户端之后,如果客户端存在Bug,修复成本将非常高。Netflix必须重新召集已获得Netflix认证的设备合作伙伴,并在Bug修复后重新进行整个认证过程以进行确认,这需要我们与合作伙伴公司重新投入大量工程时间。而在这个过程中,客户可能无法解决自己所遇到的问题,只能暂时忍受不甚理想的Netflix使用体验。为了避免出现这种问题,最适合的做法是确保对设备进行全面测试,在最终发布前找出应用程序中可能存在的问题。

本文是系列文章中的第1篇,主要介绍了我们在不同设备上针对Netflix SDK进行自动化的功能、性能,以及压力测试过程中所涉及的重要概念和所使用的基础架构。

期望达成的目标

过去多年来,我们在Netflix应用程序的测试过程中同时使用了手工和自动化的方式,并获得了不少经验教训。因此在重新设计自动化系统,以便迈上一个新台阶并实现更大规模时,我们也将这些经验教训视作自己的核心目标。

低配置成本/高测试“敏捷度”

使用自动化方法时,测试的创建和/或使用过程应该更为简单。尤其是原本就很简单的手工测试,使用自动化方式后也必须保持简单。这意味着自动化技术的运用,就算无法完全省略配置成本,也应让成本接近于零。因此我们必须确保新测试的创建和现有测试的调试过程足够快速便捷,同时这也确保了能尽量只关注于测试和功能本身。

对测试的结构无限制

使用自动化的系统不应对测试的具体形式产生限制,为了能在未来应用更为创新的测试,这一点至关重要。此外为了更好地满足不同团队(我们主要与负责平台、安全、播放/媒体/UI等的团队打交道)的需求,可能会通过不同方式设计自己的测试。将自动化系统与测试结构解耦有助于提高整个测试的可重用性。

减少测试中涉及的层面

在构建大规模系统时,很容易最终创建出大量抽象层。虽然从根本上来看,大部分情况下这样的结果并没什么不好,但为了将这些层面与自动化系统集成在一起,还需要对这些层面本身进行测试,这并不是我们希望的。实际上除了真正要测试的功能外,需要测试的其他内容越多,遇到问题后的调试就越困难:应用程序之外有那么多东西需要测试,很容易在测试中造成错误。

在我们的例子中,需要测试设备上运行的Netflix,因此要确保设备上所进行的测试对不同功能的调用能够与被测试的SDK功能尽可能保持接近。

为设备的重要功能提供支持

手工方法的测试中,设备管理工作花掉了大量时间,因此这一领域很适合使用自动化系统。由于我们测试的都是正在开发中的产品,需要能随时更改构建版本并将其部署到设备上。为了尽量简化测试过程中所遇到错误后的调试过程,还需要自动实现日志文件和崩溃转储文件的自动化提取。

自动化机制的设计

确立了这些目标后,毫无疑问我们的团队需要一种能提供必要自动化机制与设备服务,同时尽量不对测试过程产生干扰的系统。

这就需要重新思考现有的框架并创建出一个全新的自动化生态系统。为了通过自动化获得所需灵活性,需要让这个自动化系统足够精益,采用模块化设计,并且仅在功能测试非常必要的时候才使用外部服务,也就是说只有在功能无法直接通过设备上的应用程序实现(例如暂挂应用程序或操作网络)时才使用外部服务。

将所用外部服务数量降至最低还能提供下列收益:

从最简单的层面来看,我们需要有两套相互独立的实体:

构建即插即用的生态系统

在设计自动化服务过程中,我们仔细研究了对这些服务的具体需求。

考虑到上述设计选择,我们构建了下面这套自动化系统。

此处输入图片的描述

上述服务通过进一步完善即可满足我们的需求,并且各种服务尽可能保持独立,并为与测试框架进行捆绑。这些概念是按照下列方式执行的。

设备服务

设备服务可对测试自始至终全程管理设备所需的技术细节进行抽象。通过对所有类型的设备提供一个简单、统一的RESTful接口,无须对特定设备有具体了解即可直接通过该服务使用不同的设备:可以像对待完全相同的设备那样直接使用全部或任何一种设备。

管理每类设备所需的逻辑并非直接在设备服务自身中直接实现的,而是会委派给名为Device handler的独立微服务。

这样既可灵活增添对新类型设备的支持,因为Device handler可以通过任何编程语言用相应的REST API编写,现有Handler也可以轻松集成到设备服务中。一些Handler有时候可能需要与设备建立物理连接,因此将设备服务与Device handler解耦即可忽略设备位置获得更大灵活性。

对于收到的每个请求,设备服务将负责确定要联系的Device handler,并在针对所使用的Device handler接口进行适配后,以代理的方式将请求发送给不同Handler。

一起用一个具体的例子看看… 举例来说,为PS4安装某一构建版本的操作与为Roku安装的过程就有很大不同。前者(PlayStation)需要使用C#编写的代码与Windows平台上的ProDG Target Manager进行交互,后者则需要在Linux上运行使用Node.js编写的代码。PS4和Roku的Device handler分别实施了特定于具体设备的安装程序。

此处输入图片的描述

如果设备服务需要与某个设备通信,必须首先知道该设备的具体信息。每个设备都有自己的唯一标识符,设备服务将其以设备映射对象(Map object)的形式存储和访问,其中包含了Handler所需的设备信息,例如:

在将某个设备首次加入自动化系统时,需要填写设备映射信息。

当需要对某个新类型设备进行测试时,要针对该设备实现一个专门的Handler,并通过设备服务暴露。设备服务支持下列常用的设备方法:

- -
POST /device/install 安装Netflix应用程序
POST /device/start 使用一系列特定启动参数启动Netflix应用程序
POST /device/stop 停止Netflix应用程序
POST /device/restart 重启动Netflix应用程序(其实就是停止 + 启动)
POST /device/powercycle 让设备完成一个通电周期。可直接进行或通过远程电源装置进行
GET /device/status 获取有关设备的信息(例如运行中、已停止等…)
GET /device/crash 收集Netflix应用程序崩溃报告
GET /device/screenshot 针对活动界面抓取全屏截图
GET /device/debug 收集设备生成的调试文件

请注意,针对上述每个端点发送请求时都需要提供一个唯一标识符。这个标识符(类似于序列号)会和要操作的设备绑定。

保持这套服务尽量简单,也能尽量提高其扩展性。我们可以轻松地为不同设备增加其他能力,如果某个设备不能支持这些能力,将其视作空指令(NOOP)即可。

设备服务还可以用作设备池:

- -
POST /device/reserve 预留某个设备并租用一段时间
PUT /device/reserve 续订之前预留设备的租约
GET /device/reserve 列出当前预留的所有设备
POST /device/release 释放原本预留的设备
POST /device/disable 将不允许使用的设备加入临时黑名单(例如设备不可用或运行状况异常时)
GET /device/disable 列出当前被禁用的设备

下图是我们在实验室中进行自动化测试时用到的部分设备。请留意Xbox 360电源按钮旁边添加的手动机械开关。这是我们为Xbox 360量身打造的自定义解决方案。该设备需要手工按下按钮才能重启动,我们决定设计一套通过树莓派(Raspberry Pi)连接的机械臂让这一过程实现自动化,通过发送信号即可让机械臂按下电源按钮。这一操作已添加至Xbox 360的Device handler中。设备服务的电源周期(Powercycle)端点可以调用Xbox 360的电源周期Handler。PS3和PS4无须这一操作,因此未将其实施到它们的Handler中。

此处输入图片的描述

此处输入图片的描述

测试服务

测试服务负责对测试案例的运行进行记录。其用途在于标记测试案例的开始,并在测试结束前持续记录状态的变化,用日志保存相关信息、元数据、文件链接(测试过程中收集的日志/小型崩溃转储文件),以及测试案例所生成的所有数据序列。该服务暴露的简单端点可被运行测试案例所需的测试框架所引用:

- -
POST /tests/start 将测试标记为已启动
POST /tests/end 将测试标记为已结束
POST /tests/configuration 发布版本、设备型号等设备配置信息
POST /tests/keepalive 设备不响应后进行TTL运行状况检查
/tests/details 发布测试数据/结果

测试框架内部通常会调用下列端点:

网络服务 — Bifröst Bridge

为了与设备通信,以及进行流量塑形和DNS控制,我们开发了一个名为Bifröst Bridge的网络系统。我们并没有更改网络拓扑,而是直接将设备连接至主网络。Bifröst Bridge并非运行测试所必须的,只有测试需要对网络进行操控,例如更改DNS记录时才会可选使用。

此处输入图片的描述

文件服务

运行测试的过程中,我们可以收集测试生成的文件并通过文件服务将其上传至存储仓库。收集的内容包括设备日志文件、崩溃报告、屏幕截图等。从面向消费者的客户端角度来看,这个服务的使用非常简单:

- -
POST /file 不指定名称的情况下上传文件,这样最终文件中会包含一个唯一标识符,并可通过该标识符下载文件
GET /file/:id 下载包含特定标识符的文件

文件服务基于云存储平台,为了快速检索,还通过Varnish Cache对资源创建了缓存。

数据库

我们选择使用MongoDB作为测试服务所用的数据库,因为这种数据库可支持JSON格式和“无架构(Schema-less)特性。通过开放式JSON文档存储解决方案获得的灵活性是这套系统的关键需求,因为测试结果和元数据的存储会不断变化,不应受到结构的限制。虽然从数据库管理的角度来看,使用关系性数据库也很合理,但我们以“即插即用”为基本原则,因此无论测试需求如何,数据库的架构都必须用手工的方式保持最新。

通过使用CI模式运行,每次测试可以记录一个唯一的运行ID,并借此收集有关构建配置、设备配置、测试详情等信息。日志文件在文件服务中的下载链接也会存储在数据库的测试项中。

测试运行器 — Maze Runner

为了减轻每个测试发起人分别使用不同服务运行不同测试所面临的负担,我们开发了一个名为Maze Runner的控制器,该控制器可以对测试的运行进行编排,并按需调用不同的服务。

测试套件的所有者可以通过创建脚本指定用于运行测试的设备(或设备类型),并结合测试套件的名称和测试案例组成一个测试套件,随后由Maze Runner(并行)执行该测试。

Maze Runner可执行的操作如下:

  1. 根据请求找到可用于运行的一个或多个设备。
  2. 调用设备服务安装所需构建。
  3. 调用设备服务启动测试。
  4. 等待测试在测试服务中被标记为“已结束”。
  5. 显示通过测试服务获取的测试结果。
  6. 使用设备服务收集日志文件。
  7. 如果测试未能启动或未能终止(超时),Maze Runner会使用设备服务检查应用程序是否已经崩溃。
  8. 如果检测到崩溃,则会收集核心转储(Coredump),生成调用栈(Call stack),通过一个专有的调用栈分类器进行检查,找出崩溃的特征签名。
  9. 通知测试服务是否发生崩溃或超时。
  10. 在上述过程的任何一刻,如果Maze Runner检测到设备存在问题(例如因为丢失网络连接导致构建未能安装或设备未能启动),它将会释放该设备,让设备服务将其禁用一段时间,并通过另一个全新的设备开始运行测试。这样做是因为纯粹的设备故障不应对测试产生影响。

测试框架

测试框架完全独立于自动化服务,因为测试框架只是用于在设备上运行测试所用。大部分测试可以无须自动化服务手工运行,而这也是设计这套系统时的一个核心原则。这种情况下可以手工启动测试,并在测试完成后的手工获得并查阅结果。

然而测试框架也可以配合自动化服务使用(例如用测试服务存储测试进度和结果)。如果通过运行器在CI中运行测试,我们需要将其与自动化服务相集成。

为了用灵活的方式实现这一目标,我们创建了一个在内部被称之为TPL(Test Portability Layer)的抽象层。测试和测试框架通过调用这个抽象层可以为每个自动化服务定义一个简单的接口。每个自动化服务可提供这些接口所需的实现。

针对该系统所实现的服务,这个抽象层可以让原本需要自动运行的测试能够通过TPL接口提供的截然不同的自动化系统来运行。这样就可以让其他团队(使用其他自动化系统)编写的测试案例不经改动直接运行。如果测试无须改动,设备上测试运行失败后就无须由测试的所有者进行排错,这正是我们希望实现的。

进展

通过让测试框架与自动化服务保持独立,按需使用自动化服务并增添所需的设备功能,我们实现了:

  1. 将自动化测试机制扩展至更大范围的游戏主机和参考应用程序。
  2. 将这一基础结构扩展至移动设备(Android、iOS,以及Windows Mobile)。
  3. 让其他QA部门借此执行自己的测试,并将自动化框架应用给我们的设备基础结构。

从最新的测试执行覆盖图中可以看到,仅针对参考应用程序,每个构建版本已执行了大概1500次测试。从全局的角度来看,开发团队每天会为每个分支生成大约10-15个构建版本,每个版本包含5个不同用途(例如调试、发布、AddressSanitizer等)的参考应用程序。对于游戏主机,每天每个用途会生成大约3-4个构建版本。总的来说,针对单一用途的构建版本,我们的生态系统每天可以运行大约1500*10 + 1500*3,即大约2万个测试案例。

新的挑战

考虑到每天要运行大量测试,我们又遇到了两个重要的挑战:

  1. 设备和生态系统的缩放性和弹性
  2. 测试结果造成繁重的遥测分析负担

在后续文章中,我们将深入介绍为了解决上述两大挑战,目前我们所采取的一系列创新措施。

作者Benoit FontaineJanaki RamachandranTim KaddouraGustavo Branco阅读英文原文Automated testing on devices

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注