构建分布式Android远程测试平台

2020-12-07 04:46:01

当前有约25亿台Android设备-包括约1,300个离散品牌和约24,000个独特设备模型。我正在探索利用此潜在资源池,以使自动测试负担得起&解锁更全面的配置范围。

DART(分布式Android远程测试)的目标是使任何Android设备都能运行自动化测试,而无需ADB或复杂的设置/维护。商业产品可以按每小时使用设备的方式付费,从而使任何人都可以利用空闲的设备时间。每个设备都包含一个分布式测试平台。

DART似乎是利用设备空闲时间的最佳解决方案-验证应用程序在& Samsung S9"上运行的最佳方法。是通过在& Samsung S9"上执行它。将其与电话通常不是最佳选择的其他过程进行对比:服务器托管,远程计算等。

在下面的演示中,我在没有电缆/ ADB的Mozilla Focus隐私浏览器上运行了一些测试用例。

顶级设备场仅占Android设备型号总数的1%。非分布式设备场更青睐深度而不是广度-随着添加更多种类的设备,维护成本增加。

轶事:我曾经花了一整天的时间调试一小部分用户遇到的ANR。我发现它只影响在特定地区购买的手机。我从未能够在美国购买的同一部手机上重现该问题。

Headspin旨在缓解其中一些问题。他们声称在150个地点拥有22,000多种启用SIM(相似)设备的全球基础设施。传统服务(例如Firebase Testlabs)无法处理地理问题,这是一大优势。 Headspin的核心是DeviceFarmer STF开源库。但是像它的前辈一样,Headspin仅支持有限的一组设备模型。

真实设备测试场的初始成本很高,并且需要定期维护,因此为什么它们比虚拟设备场更昂贵。

每天在真实设备上运行10小时测试的费用为每月$ 1000(假设20个工作日的生产力水平相同)。

关键点:Uber通过不拥有其全球网络上的所有汽车,避免了巨大的维护成本。类似地,利用测试设备的分布式网络,可以以最小的维护成本实现广泛的设备覆盖。对于一两个位于同一位置的设备,维护不是很费力的。

来自DeviceFramer STF常见问题解答:"除了我们仅在几个月内就出现一次早期故障外,我们所有的设备在大约两年内都运行良好。但是,已经达到2-3年的标准,一些设备已经开始体验明显扩展的电池。根据我们的经验,该系统在大多数时候都运行良好,并且所有问题都与USB相关。您通常必须每周大约做某事。"

企业可以决定哪种测试类型最适合DART,以及网络是由单个节点还是由节点群集组成。下面的android测试简单分类法应该会有所帮助。 [1]

网络可以是组织内部的。例如:将X台设备存储在总部,每个员工都可以将其测试设备远程添加到系统中。

它也可以在组织外部。想象一下,每小时向用户支付1.5美元的设备时间。假设设备在一夜之间进行测试(由于时区不同),则估计每月收入为$ 240($ 1.5 * 8小时* 20个工作日)。

安全检查将保护设备免受恶意二进制文件的侵害。系统将关闭-仅允许经过验证的发布者。利用GooglePlay强制执行程序包名称和签名对应。

工作量证明消除了测试欺诈的动机。服务器可以使用设备日志,屏幕截图,具有UUID的视频以及过去的执行来验证提交的工作结果。由于这不是水密的,因此最好与其他策略结合使用:用户验证,设备证明,随机化和设备吞吐量限制。

防止产品泄漏是很难的-如果产品在电话上运行,那么任何有足够动力(和知识渊博)的人​​都可以找到一种访问对象的方法。三种适当的解决方案是:

可以使用简单的覆盖图遮盖被测应用程序。以下视频显示了Google IO 2019应用程序测试用例的执行情况。在右侧,应用程序交互是不可见的。根据策略,隐藏操作不会影响屏幕截图或视频。

使用框架UI组件包装器,可以从Layoutlib借鉴一些想法,以提供完整的解决方案。 Layoutlib是android View框架的自定义版本,旨在在Eclipse中运行。该库的目标是在Eclipse中提供与它们在设备上的渲染非常接近的布局渲染。

测试需要在隔离的环境中运行。可以构建类似VirtualAPK的插件框架,以在应用程序和操作系统之间提供隔离层。这可以使用DexClassLoader和一些反射技巧来修改框架组件。通过这种方法,可以无缝加载和加载应用程序。无需任何用户交互即可卸载。

GooglePlay动态功能交付系统使用其中的一些反射技术来支持旧版设备(Android 7.0之前的版本)🙈。例如,通过反射调用addAssetPath,以使未捆绑在APK中的资源可供使用。

Warning: Can only detect less than 5000 characters

stateDiagram-v2 [*]-> FindingWork:单击开始按钮,单击FindingWork-> [*]:停止按钮单击了FindingWork-> DownloadingPayload:找到工作DownloadingPayload-> FindingWork:有效负载下载失败DownloadingPayload-> UnpackingPayload:下载的Payload已下载UnpackingPayload-> FindingWork:有效载荷解包失败UnpackingPayload-> InstallationWork:有效载荷解压的InstallationWork-> FindingWork:安装失败InstallationWork-> PreparingRun:已安装有效负载PreparingRun-> FindingWork:运行准备失败PreparingRun-> RunningWork:运行准备成功RunningWork-> FindingWork:运行失败状态RunningWork {[*]-> RunningTest RunningTest-> RunningTest:重试测试RunningTest-> [*]} RunningWork-> FinalizingRun:运行完成的FinalizingRun-> PackingResults:运行最终的PackingResults-> FindingWork:结果打包失败PackingResults-> UploadingResults:结果打包的UploadingResults-> FindingWork:上传结果失败UploadingResults-> CleaningUpWork:结果已上传CleaningUpWork-> FindingWork:清理完成

接下来的几节介绍各个系统组件。为了简洁起见,我排除了与当前主题无关的代码。对于每个组件,我仅关注一些关键思想。您可以随时浏览Github存储库以获取完整的源代码。

如果传统上不运行测试,则不能使用该框架的UiAutomation。幸运的是,可以使用平台可访问性API和某些其他功能等效的替代品来复制UiAutomation的功能。从文档中可以明显看出,UiAutomation可以被视为具有其他功能的AccessiblityService。

4 * API用于内省屏幕并在远程视图上执行一些操作

8 *不提供生命周期的挂钩,而暴露其他

15 *的触碰和触碰事件必须由以下人员传送到系统

使用平台可访问性API并非所有功能都直接可用。例如,DevicePolicyManager用于授予运行时权限& MediaProjector用于设备范围的屏幕截图。

具有确切功能集的自定义类将用作UiAutomation的直接替代。客户端测试代码要么直接使用此自定义类,要么使用Gradle插件通过字节码操作更改导入。

AccessibilityService在专用过程中运行,以将其与工作运行器应用隔离。另外,由于onBind是最终的,所以我们使用代理服务与AccessibilityService进行通信。

一旦安装了AppUnderTest.apk和Test.apk,TestServer就会调用Context.startInstrumentation函数来运行测试用例。

日志和其他工件需要进行流处理。到TestServer。此数据需要存储在服务器的私有目录中,但是Android安全模型不允许外部应用直接将其写入应用的私有目录中。可以使用ContentProvider和FileDescriptor规避此限制。

TestClient包含在Test.apk中,并控制测试的执行。它是" com.fluentbuild.apollo:client:version"的一部分。神器。

TestClient与Instrumentation实例协同工作。需要自定义实例才能拦截Instrumentation回调。在" com.fluentbuild.apollo:client:version"中工件,提供了对AndroidJUnitRunner进行子类化的实现。反射黑客用于在AndroidJUnitRunner中注册org.junit.runner.notification.RunListener。

TestClient与Instrumentation实例协同工作。需要自定义实例才能拦截Instrumentation回调。在" com.fluentbuild.apollo:client:version"中工件,提供了对AndroidJUnitRunner进行子类化的实现。在此实现中,我使用了一个反射技巧,将其插入InstrumentationResultPrinter中。传递TestObserver,它捕获测试状态并最终调用真实打印机。

对于不使用AndroidJunitRunner的发布者,他们仍然可以通过调用相关功能来使用客户端工件。 UiAutomation可用于请求运行时权限。 可以创建一个类似于androidx.test.runner.permission包中的图层,以简化此流程。 2 *在运行Android M(API 23)及更高版本的设备上请求运行时权限。 4 *此类通常用于授予运行时权限,以避免来自 5 *显示并阻止该应用的用户界面。 这对于Ui-Testing避免松动特别有用 8 *请求的权限将授予测试类中的所有测试方法。 使用[addPermissions]将权限添加到权限列表。 要求全部 15 *将权限添加到调用[.requestPermissions]时将请求的权限列表。

17 *前提条件:在低于[Build.VERSION_CODES.M]的API级别上调用时,此方法不执行任何操作。 26 *前提条件:在低于[] [Build.VERSION_CODES.M]的API级别上调用时,此方法不执行任何操作。 AppUnderTest的组件受到密切监视,以支持某些遥测功能。 对于每种类型的组件,我们都会创建Monitor接口的实现。 ActivityMonitor监视AppUnderTest中所有活动的生命周期。 当呼叫者(仪器)可以将事件传递到下游时,动作功能允许监视器进行控制。 AIDL IPC呼叫中只能使用Parcelable类。 由于此约束,因此创建了以下模型类,以将通知从JUnit传递到服务器。 11 //由于我们通过绑定程序IPC将故障报告回运行时,因此我们需要确保 12 //我们不超过Binder交易限制-每个进程1MB。

TestObserver是org.junit.runner.notification.RunListener的一个实例,并在测试运行期间被通知发生的事件。这些事件传递到TestClient,然后将其传递到TestServer。

OverlayView是一个简单的自定义全屏不透明视图,用于在请求时遮盖“活动”。创建活动后,叠加层将立即附加到窗口。我还没有注意到叠加层和测试UI交互之间的任何干扰。

任何外部交互都应立即停止测试执行以防止干扰。 NavigationInteractionObserver检测用户何时按下按钮。

客户端可以利用服务器的ContentProvider将文件保存在服务器的私有目录中。

运行所有测试之后或当AppUnderTest崩溃时,所有其他工件都将移至TestServer。

当在传输测试状态时发生错误时,或者在服务器连接断开时,客户端将自动取消所有执行。服务器还可以使用Finisher界面取消客户端。

断开与客户端的连接后,服务器会在最后的努力中使用CancelSignalObserver终止客户端。 CancelSignalObserver是一个BroadcastReceiver,与测试过程的生命周期相关联。

在测试运行期间,性能和 设备运行状况数据会定期整理和存储。 协议缓冲区用于数据序列化。 3 / ** dalvik堆的比例集大小。 (不包括其他Dalvik开销。)* / 该存储器的40 *部分实际上已在使用中,总体上需要 41 *用于测量移动无线电控制器所用毫秒数的键 47 *用于测量此uid使用的估计mA * ms数量的键 wifi为48 *,即wifi活动的毫秒数 54 *用于测量此uid使用的估计mA * ms数量的键

61 *用于测量此uid使用的估计mA * ms数量的键 62 *对于移动数据,即活动的毫秒数 74 *此uid保持完全wifi锁定的毫秒数的度量键。 25 *线程状态。 线程可以处于以下状态之一: 51 *在给定的时间点,线程只能处于一种状态。 84 *由于调用其中一个线程,线程处于等待状态 105 *线程由于调用了以下之一而处于定时等待状态

25 *该过程所产生的,不需要加载存储器的次要故障的数量 36 *流程造成的重大故障数量 ......