[Mobile翻译]MobileAtScale-第二章节-应用程序复杂性带来的挑战

168 阅读38分钟

第二章节:应用程序复杂性带来的挑战

随着应用程序的发展,事情开始变得有趣。你不断为应用程序添加新的功能,同时也对现有的功能进行调整。很快,原来只有几个屏幕的应用程序变得如此复杂,如果你要把屏幕打印在导航流程图上,它将覆盖整面墙。

在处理一个大型复杂的应用程序时,你确实会遇到额外的挑战。你如何处理越来越复杂的导航模式?非决定性的事件组合怎么办?你如何在几种语言之间进行本地化,以及你如何扩展你的自动化和手动测试?

让我们跳进去吧。

13. 大型应用中的导航架构

移动应用中的导航和深层链接的挑战一样,是一个被低估的问题领域。当应用程序很小的时候,我们往往不会太注意它。随着应用的增长,我们意识到导航架构已经成为一头需要被驯服的野兽,因为屏幕和转换的数量在增长。

虽然iOS和Android都提供了基本的导航概念,但它们将导航架构的定义留给了工程师。反过来,我们倾向于重新发明导航的轮子。这主要是出于需要,因为iOS和Android都没有提供超出简单应用范围的导航方法。

拥有一个定义明确的应用导航策略,并对应用状态进行良好的分离,对于任何规模的应用都是关键。 是什么触发了点按和手势之间的动画?导航是否独立于应用状态?

许多团队只有在他们把自己编码在一个角落里的时候才会建立这个地图,发现他们已经建立了不一致的导航解决方案,当用户进入意想不到的状态时就会导致错误。

不一致的导航可以是应用程序不一致地使用弹出窗口、祝酒词、全屏模版或屏幕,这一点很明显。它可能意味着不同的屏幕之间使用不同的动画。它通常也意味着在指示应用程序进行导航时有大量的代码重复。

image.png

一个应用程序的导航结构。你的应用程序在iOS和Android上的导航结构是一样的吗?

异步导航是一个常见的问题,很少有工程师考虑提前交接。所谓异步导航,我指的是当某些事情需要在导航继续之前完成时。登录应用程序或提交表单就是这样的例子。在这个阶段,当用户试图导航离开时会发生什么?如果你不对这种情况进行计划和测试,应用程序会进入奇怪的状态。当使用RIBs时,工作流为这个问题提供了一个优雅的解决方案

导航框架或一致的导航方法是你会发现自己要么建立、执行,要么利用现有组件的东西,与更复杂的应用程序。

在iOS上,没有一个原生的导航组件可供你使用。虽然有几个开源项目提供了帮助,但在这个平台上,导航远不是一个已经解决的问题。正如John Sundell在Swift中的导航一文中总结的那样。"有一种很好的方式来执行导航,而且不会让你陷入困境,这真的很棘手。"

Android也许领先一步。Jetpack导航架构组件正在成为首选的开箱即用的导航库。除了缺乏对一些边缘案例的支持,它的工作效果足够好。Jetpack是在2018年发布的。在Activities和Fragments之上构建自己的解决方案的应用程序,往往需要决定是维护自己的堆栈,还是迁移过来。即使没有Jetpack,安卓在导航方式上也比较有主见,比如指南上的向上和向后键,并且有一个后端堆栈的位置。

手机和平板电脑的导航差异是一个有趣的边缘案例。如果你的应用程序有更大的屏幕和表格,移动设备可能有多个步骤或屏幕,而平板电脑版本使用一个。这种情况在iOS中更有可能发生,因为手机和平板电脑的尺寸都有明确的定义。支持这种情况并不难,但前提是你要做好计划。

14. 应用状态和事件驱动的变化

是什么驱动了你的移动应用的用户界面的变化?可能是用户点击它的某些部分,可能是来自后台的数据,也可能是一个计时功能。大多数的变化都是由事件驱动的,正如我们在状态管理章节中所讨论的。

image.png

事件驱动移动应用程序中的状态变化!

在大多数移动应用程序中, 事件驱动状态变化。

这些事件以异步方式触发,如应用程序状态变化、网络请求或用户输入。大多数bug和意外崩溃

通常是由一个意外的或未经测试的事件组合和应用程序的状态被破坏造成的。状态被破坏是应用程序的一个常见的问题领域,全局或局部状态被多个相互不认识的组件所操纵。遇到这个问题的团队开始尽可能地隔离组件和应用程序的状态,并倾向于迟早开始使用反应式状态管理。

image.png

复杂的应用程序中常见的外来bug的根本原因:非决定性的事件使应用程序的一部分处于无效状态

随着应用程序的复杂性增加,可能的状态数量也在增加。一些状态变化可能会触发其他状态变化。例如,一个组件在用户点击后改变其状态,可能会触发页面或应用程序的状态改变。

移动应用程序通常比网页或厚客户端应用程序有更多的状态。这是由于它们需要支持各种生命周期事件,如应用锁定、应用切换和后台模式,更不用说离线模式支持增加了其他几种状态。网络应用的生命周期事件较少,而且很少广泛支持离线模式,而厚客户端应用的生命周期事件较少,连接性下降比移动端要罕见得多。

应用程序越大、越复杂,错误就越有可能由非正常事件的组合引起。非正常事件的问题在于它们很难被计划或测试。例如,导致组件状态变化的后台推送通知可能在用户锁定应用屏幕后马上到达,导致不同的状态变化。一个

比较罕见的入站事件可能被独立的组件/团队所消耗,这种组合会导致异国的bug。

遵循状态管理最佳实践来保持因状态变化问题而发生的bug数量少。尽可能地保持状态不可变,并将模型存储为不可变的对象,发射状态变化。

记录无效的状态,并附上重放或调试的信息,这样你就有了出错的细节以及如何重现这个问题。使用一个错误报告供应商工具是最简单的开始,有很多可供选择的。一些崩溃报告工具也带有错误报告组件。

通常情况下,错误报告工具会显示给测试版用户提交问题。在提交错误报告时,你还需要包括导致问题发生的一系列事件,以及足够的信息,以便你能够调试发生了什么。大多数错误报告工具将允许你附上会话期间发出的日志 你的挑战将是发出足够的日志以使错误再现成为可能。

自动回放、暂停、倒带和调试用户的会话;这就是调试应用程序状态问题的理想工具所要做的。你希望有一个日志,不仅仅是用户事件,还有传入的网络通知。为了调试跨线程问题,能够在每个线程的基础上做到这一点是很重要的。虽然网络上有这样的会话回放工具,但不幸的是,在iOS和Android上,我们不得不建立自定义工具来完成上述工作,或者在可预见的未来使用调试日志和内存转储来工作。

15. 本地化

iOS和Android都提供了有意见的方法来实现本地化。iOS支持导出本地化的翻译,而Android建立在资源字符串。的工具略有不同,但概念是相似的。要对你的应用程序进行本地化并定义字符串,你要将本地化后的字符串作为二进制文件中的单独资源来运输。然而,对于大型应用程序和许多地区,你很快就会在这个工作流程中遇到挑战。

决定在应用程序中进行本地化还是在后端进行本地化是任何成长中的应用程序将面临的首要挑战之一。通过使用 "默认 "的iOS和Android本地化方法,你在应用程序中本地化的资源将被 "卡 "在二进制中:你不能改变短语或更新错误。如果你从供应商的解决方案,或你的后台实施服务本地化字符串,你在这方面有更多的灵活性,你只需要为iOS和Android做一个本地化通道。

关于移动应用程序应该有多 "聪明",以及哪些字符串应该在这里存在,而不是仅仅从后端获取它们,这个话题更深入。我们将在第三部分 "后端驱动的移动应用程序 "中详细介绍。后台的本地化工作越多越好。后台重度本地化使客户端逻辑保持在较低水平,并减少移动端本地化的资源数量。虽然将更多的本地化工作委托给后端通常需要工作,在iOS和Android应用程序的工作流程上都是如此,但从长远来看,它导致了更容易的维护性。

当支持大量地区性时,你需要确保所有的本地化翻译在运送到应用商店之前完成。你可能会使用第三方的本地化服务或内部团队来做这件事。假设你有一个构建火车,你希望只有在所有新资源都被本地化后,才允许火车继续进行。

确保iOS和Android使用相同的语言和本地化是另一个挑战。特别是对于大型品牌来说,iOS和Android应用程序使用相同--或至少相似--的语言是很重要的。一致的语言不仅对品牌有利,也有助于客户支持处理用户报告的问题。如果没有一些共享的本地化工具或iOS和Android团队的紧密合作,很难确保这种一致性。由相同的设计师和项目管理人员监督两个应用程序也有帮助。使用相同的本地化ID或密钥是减少重复的好方法,这需要通过iOS和Android团队商定的惯例来完成。

在Uber,我们在iOS、安卓、后端和网络上使用了一个内部本地化工具。这个工具被整合为生成iOS和Android资源,并跟踪本地化的完整性。建立一个专门用于本地化的工具是否过头了?对于Uber多年前的复杂用例,我不认为是这样。那时,在所有的堆栈中,很少甚至没有好的本地化工具。

不过,从那时起,这个领域已经发生了变化,在移动领域有许多本地化供应商。在建立你自己的工具之前,值得看看你是否能找到一个适合你的需求。

本地化定制字体是一个经常被忽视的领域。对于大公司和那些具有强烈品牌形象的公司来说,使用自定义字体是很常见的。然而,这种自定义字体并不总是支持一个应用程序所支持的语言集的每个字形。

要确认你的自定义字体缺少对哪些语言和字符的支持,这已经很棘手了。当使用这些字体来显示用户生成的内容时,这更是一个问题,因为你无法控制这种输入。一旦你弄清楚了你的使用情况和边缘情况,你要么需要添加这种支持,要么--更经常地--使用不同的字体来处理不支持的地区。根据每种字体所支持的语言,管理一致的、跨平台的字体和语言的映射就变得更加困难。

当使用自定义字体时,你需要在二进制文件中包含这些字体,从而增加应用程序的大小。我们将在本系列的第三部分中更详细地讨论应用程序的大小。

货币在某些地区的iOS和Android上格式不同是多语言、多平台的应用程序显示货币价值时遇到的另一个痛点。Web也有类似的不一致问题;印度卢比值就是一个很好的例子。当使用货币类型来增加或减少数值时,这个问题尤其明显。一个可能的解决方法是,后端将所有货币数据格式化为当地语言,而客户端不做任何数学运算,或格式化货币字符串。

日期和时间的格式化将面临与货币类似的挑战。不同的国家和地区会以不同的方式显示日期和时间。你需要确保应用程序考虑到这一点。

支持从右到左(RTL)的语言往往不仅仅是翻译。阅读本书的大多数人要么已经习惯了LTR和基于拉丁语的语言,要么说得足够流利。然而,在为阿拉伯文、希伯来文等RTL语言设计用户界面时,需要镜像的不仅仅是字符串,应用程序的布局可能也需要改变。"镜像 "布局是一种常见的方法,但它可能会给母语者和用户带来奇怪的用户界面。最好是让当地人参与这个过程,无论是开发还是测试。

独特的地区总是值得额外关注。根据我的经验,日语和德语是值得更多检查的地方,因为它们都比英语更啰嗦,而且你要确保布局、填充物和换行符在这些地方仍然有效。

image.png

Skyscanner应用程序具有英语、日语和德语语言。德语通常是对应用程序进行压力测试的一个好选择,因为语言比较冗长。

测试本地化是不小的努力。在一个理想的世界里,在每次本地化修改后,一个讲母语的人都会去看你的应用程序的每个流程。在现实中,这很少发生,因为这样做的成本太高。大多数大公司依靠beta测试者使用不同地区的应用程序来报告明显的不一致之处。例如,在Uber,我们可以依靠这种方法来尽早发现本地化问题。数以千计的Uber员工每周都会和beta测试者一起对应用程序进行检查。本地化问题几乎总是在应用被推送到应用商店之前被发现。

你可能需要定义一个工作流程来验证本地化的变化。当应用程序中的本地化字符串发生变化时,应该发生什么?这是否应该触发一个动作来手动检查变化?是否应该通知屏幕的开发者?即使没有其他变化,发布经理是否应该发送一个新版本的应用程序?这些可能看起来是小问题。其实不然。尤其是当翻译字符串的人独立于编写代码的工程师工作时,他们就更不是了。需要有人定义一个工作流程,不仅仅是添加,而是更新和测试本地化。

快照测试是一个被低估的本地化测试工具。通过快照测试,你可以快速而轻松地生成任何地区的屏幕快照,甚至是伪本地化。然后,工程师们可以更快地发现布局问题,并拥有展示问题的参考图片。除了帮助工程师之外,你还可以与做翻译的人分享快照测试屏幕截图,这样他们就能获得更多关于翻译后的文本如何出现的背景。

伪本地化是测试本地化工作的一个聪明方法,而不需要通过本地化练习。它意味着用一种开发人员可读的伪语言替换所有可本地化的元素,但它包含了其他语言的大部分 "棘手 "元素,如特殊字符或更长的字符串。

例如,"查找帮助 "的伪本地化版本可以显示为"[ƒîîîกกðð Ĥéééļþ]。微软在开发Windows Vista时使用了这种方法,Netflix在其产品开发周期中使用了这种方法。Shopify建立了一个Ruby工具来生成这样的字符串,你可以在Netflix找到一个受这种方法启发的npm包。

不应该被本地化的短语是一个最后的边缘案例。在Uber,我们决定不对某些品牌术语进行本地化,如Uber Cash或Uber Wallet。一些团队没有意识到这一要求,并在某些屏幕上翻译了这些字符串,这意味着拥有团队必须在所有地区进行测试以发现这些问题。一些测试人员也经常报告缺乏这些翻译。这是我们不得不处理的一点噪音。

有很多供应商可以帮助进行移动本地化。本地化很少只在iOS和Android上孤立地进行;它更常见的是作为一个整体进行,包括网络、电子邮件和其他面向客户的属性。本地化供应商包括POEditor、Loco、Transifex、Crowdin、Phrase、Lokalise、OneSky、Wordbee、Text United,以及其他一些供应商。

进一步阅读。

16. 模块化架构和依赖注入

随着应用程序变得越来越大,将应用程序的一部分作为可重用的组件或模块来构建往往是有意义的。 对于大公司来说,要么有几个应用程序,要么有几个移动团队,重用另一个团队拥有的代码就成了不二法则。例如,一个移动平台团队可能拥有网络和共享架构组件。一个货币团队将建立和拥有支付组件--这是我在Uber的团队!而地图团队将拥有所有的东西。- 而地图团队将拥有所有与地图有关的东西。应用程序中的组件和模块通常会映射到公司的团队结构,反映了被称为康威法则的观察。

有了多个模块,模块需要有一种方法来定义它们的依赖关系,无论是在模块还是在类的层面上。这个概念就是依赖性注入,这是一种控制反转的形式。这是一个简单的,但经常被低估的移动开发中的概念,也是一个在后端和Web项目中更为常见的概念。

依赖注入的一个主要挑战是,如果你不使用一个框架来解决这个问题,那么修改或更新依赖关系需要大量的工作。即使不使用框架,这种耗时的性质也是对更清晰的抽象和良好的可测试性的一种交换。

依赖注入是一个强大的工具,可以在整个代码库中保持可测试的代码一致。通过依赖注入,有多个依赖关系的类可以在实例化时通过模拟依赖类来进行单元测试。较大的、模块化的应用程序往往会以一种方式引入这个概念。

手动依赖注入--创建所有的接口,然后硬编码所有的依赖关系--在这种依赖关系很少的情况下效果很好。然而,随着组件的数量和依赖关系的增加,维护和更新依赖关系变得更加困难。发现诸如循环依赖的事情也变得很棘手,使用依赖注入框架开始变得更有意义。

Android有一个成熟的依赖注入框架,可以分析依赖的编译时间,称为Dagger2。谷歌最近推出了Hilt on Dagger;这是一个建立在Dagger之上的依赖性框架,而Koin在Kotlin中也变得越来越流行。

在iOS上,历来都没有类似的框架。在Uber,我们建立、使用并开源了基于类似概念的Needle。在没有依赖注入的情况下,如果有一百多个工程师在同一个代码库上工作,我们就很难扩展代码。我们使用这个工具来明确所有的类的依赖关系,使单元测试变得简单--对于大部分的代码来说是没有商量余地的--并且在不同的团队中重复使用组件。

17. 自动测试

如果你没有对一个大型应用进行适当的自动化测试,那么你就是在给自己挖坑。我所说的大型应用,是指一个复杂的代码库或大量的人对其作出贡献。

不同类型的移动自动化测试是这样的。

  • 单元测试: 所有自动化测试中最简单的,测试一个孤立的组件,也称为 "单元"。对于iOS和Android,这通常意味着测试一个类上的方法的行为,或一个类的特定行为。这些测试的编写和理解都很简单,运行速度也很快。
  • 集成测试在复杂性上比单元测试更胜一筹。他们测试多个 "单元 "相互作用的行为。这些测试比单元测试更复杂,运行时间更长。他们可能使用或不使用模拟。这里是John Sundell对移动集成测试的一个很好的概述。
  • 快照测试: 将一个UI元素或页面的布局与参考图片进行比较。它是一种廉价而快速的方法,可以确保代码的改变不会导致UI的意外变化。流行的快照测试框架包括iOSSnapshotTestCase、SwiftSnapshotTesting和Android的屏幕截图测试。
  • UI测试: 一个锻炼UI的测试,并测试UI以特定的方式表现。输入是通过UI自动化来实现的,而UI被检查以验证假设。UI测试通常是最复杂的测试,需要最长的时间来运行。UI测试可能有他们的数据层--后端终端--被模拟。

单元测试是可持续工程的一个基本工具, 假设你有一个由几个工程师组成的团队和一个移动应用。

假设你有一个由几个工程师组成的团队和一个被认为是复杂的移动应用。单元测试业务逻辑,配合代码审查,可以降低错误率,保持代码的清洁,并增加团队内部的知识共享。它也是进行有意义的重构的安全网,能够不断清理技术债务。单元测试带来了金字塔式的好处。我在单元测试好处的金字塔一文中更详细地描述了这些复合的好处。

当你继承了一个几乎没有测试的应用程序,并且该应用程序的部分没有以可测试的方式构建,你通常没有什么选择,只能添加集成测试,并为新的代码部分慢慢添加单元测试。对应用程序的现有部分进行改造可能没有什么意义。

集成测试比单元测试更复杂。你要测试两个或更多的类,模块。

或其他单元如何一起工作。集成测试最常见的情况是确保库的集成按预期工作。集成测试对于你编写的、其他应用部分会重复使用的库和模块特别有用。集成测试将行使库/模块的公共API,并确认该组件按预期工作。

集成测试的一个特殊情况是对应用程序的部分进行UI测试,但不做这种端到端的测试。例如,测试一个按钮点击导航到一个特定的屏幕(iOS)/活动(Android)将是一个集成测试。

与单元测试相比,集成测试的编写和维护成本更高,尽管它们也可能更有价值,因为它们测试的面积更大。关于你是应该更多的依靠这些测试还是单元测试,并没有一个黄金规则。这完全取决于你的环境和项目。

快照测试是自动化测试的下一个环节。它们是一种特殊形式的集成或UI/端到端测试。一个页面被旋转起来,进行截图并与存储的图像进行比较。使用的数据通常是模拟的。如果不一样,测试就会失败,把新的图像作为失败的原因。

快照测试的构建成本很低,而且可以帮助工程师加快迭代周期。在你改变了用户界面中的某些东西后,你重新运行测试,并将结果与改变后的用户界面的样子进行比较。

在Uber,我们从Facebook接管了iOSSnapshotTestCase并大量使用这个工具,但只在iOS上使用。那时,安卓团队决定不进行快照测试,因为他们认为在这个平台上进行 "适当的 "快照测试的工作量太大,因为安卓设备的尺寸多种多样。

如果快照测试的参考图像存储在 repo 中,随着此类测试的增多,这就会成为一个问题。有了数以千计的测试案例,这些图像就会占用大量的空间,从而减慢 repo 的检查和更新。在Uber,我们最终将快照图像从 repo 中移出,创建了一个CI工作,跟踪快照的变化,因为PR是在main上合并的。如果检测到变化,CI会创建一个任务。如果变化是预期的,开发人员可以直接关闭票据。如果不是,他们可以创建一个后续diff来解决这个问题。这种方法涉及到建立自定义的工具,解决参考图片没有被检查到 repo 的问题。

虽然快照测试越来越多地被各种应用程序和团队采用,但它不是一个适合所有的工具。这些测试的有用性在很大程度上取决于你的应用程序。涵盖你不想在不知不觉中改变的 "核心 "流程,是这些测试的一个良好开端。

自动UI测试是本地移动工具在大规模使用时变得相当有限的地方。苹果公司提供了UI测试开箱即用,这是一个手动记录的过程,然后重新播放。这个工具对于一些测试案例来说效果不错。对于较大的应用程序,你必须设计一个更强大的解决方案,并使用机器人或页面对象等方法。Capital One展示了一个很好的机器人模式测试的参考实现,Wantedly分享了一个很好的页面对象模式方法的参考。使用机器人或页面对象构建自己的系统并不难,而且扩展性很好。

Android原生支持使用EspressoUI Automator测试框架进行UI测试。这两个都是强大的解决方案,对于大型的应用程序来说,扩展性很好。

一旦你遇到几十个或更多的测试案例,现有的工具将感到有些局限。首先,没有一个

这些测试框架都不支持对网络响应的嘲弄,你必须使用其他工具,如Mocker on iOS或在Android上使用OkHttp的MockWebServer功能。你必须选择一种嘲弄方法,并决定如何管理嘲弄测试数据。

任何编写超过20-30个UI测试的团队都会遇到这些测试需要运行多长时间的问题。以及是否在合并到主站之前执行这些测试,或者只是偶尔执行的两难问题。这个问题强调了两件事。

  • 在测试金字塔的背景下, 考虑你的测试工作重点在哪里。是的,UI测试很重要,但它们是编写和维护最昂贵的测试,也是运行最慢的。你对其他测试的投资水平是否合适?
  • 解决大规模运行UI测试的问题有很多好处。工程师们不必过度考虑添加新的UI测试,而且可以自信地这样做。讲座,两年的UI测试,概述了这样做的几个可能的解决方案。在Uber,我们花了相当多的时间和精力来并行化UI测试的执行,检测脆弱的测试,并跟踪/报告UI测试的成本和效益。

用于测试的模拟的实时数据是自动化测试的另一个复杂性,使用模拟数据有几个优点。

  • 速度。当预先模拟数据时,测试运行得更快,而不是实时获取数据。实时数据需要通过网络。
  • 边缘案例。设置模拟数据来代表边缘情况,要比用实时数据来做要容易得多。
  • 可靠性。当使用实时数据时,测试可能由于网络或后端问题而失败。虽然这在某些情况下可能是件好事--特别是如果后端应该一直在运行--但它也会产生噪音。
  • 频率。当测试速度快时,在每个变化上运行它们是有意义的。在本地运行测试,锻炼被修改的代码。这意味着对工程师的反馈接近于即时。当测试较慢时,通常只在合并前运行。反馈环路变得更长,使开发环路也更长。

使用模拟数据会带来一些你需要解决的问题,特别是随着测试案例数量的增加。

  • 实时数据和模拟数据不同步。如果测试通过了,但应用程序失败了,那么测试就没有什么价值了。你想以某种方式检测实时数据的变化,你需要更新你的模拟数据。这一点说起来容易做起来难。
  • 测试数据管理。 当你有很多使用模拟数据的测试时,你要想出一种结构化的方法,既要保证测试数据的质量,又要保证测试数据的安全性。

一旦你有许多使用模拟数据的测试,你就想用一种结构化的方式来存储这些数据,并标记它们所支持的用例。这方面没有

没有明确的解决方案。一些团队将数据保存在代码中,一些使用人类可读的格式,如JSON,还有一些将这些数据保存在文件层次结构中。选择一种解决方案,既能使理解和修改测试数据变得容易,又不会使添加新的模拟数据和测试案例变得太困难。

  • 动态数据体验。 对于更复杂的UI测试,你经常想设置一个数据动态变化的场景。例如,你可能想用一组属性来模拟一个用户,然后重置应用程序,用另一组属性进行模拟。这种情况是你管理测试数据方式的延伸。随着你的场景数量的增加,保持数据和测试都易于维护变得更加棘手。

公司和团队之间的测试策略差异很大。 每个小组将有不同的约束和目标,并利用不同类型的测试来实现。作为启发,以下是截至2021年某些公司如何对待测试的一些数字,基于这个移动原生基金会讨论

  • Spotify有~ 32,000个单元和集成测试,~ 1,600个快照测试和~ 500个UI测试。
  • Airbnb有~ 10,000个单元测试和~ 30,000个屏幕截图测试。
  • Robinhood有数以千计的单元测试,约400个快照测试和约15个封锁的E2E测试。
  • Nordstrom有~ 29800个单元测试,~ 1500个UI测试和少量的手动测试。
  • Shopify有~ 8400个单元测试,~ 2300个屏幕截图测试和~ 20个E2E测试。
  • 福特(Ford Pass / Lincoln Way)有~ 20000个单元测试,大量快照测试,没有UI测试和大量手动测试。
  • Target有~ 10,000个单元测试,有~10,000人进行了彻底的测试,还有额外的人工测试。
  • Uber有数以千计的单元测试,数以千计的快照测试,少量的UI测试和手动测试。
  • Lyft有数以千计的单元测试,一些快照测试,一些UI测试和手动测试。
  • Avito(俄罗斯的分类信息)有大约2000个单元测试,大约1000个组件测试,大约750个E2E测试和手动测试。

使用真实设备的自动测试基础设施是另一个挑战,每个公司都需要以适合自己的方式解决。较大的地方可能会投资建立他们自己的设备实验室,这是一个有大量前期和持续成本的方法。

对于许多团队来说,基于云的智能手机农场将更加实用。这方面的例子包括谷歌的Firebase测试实验室,或微软的应用中心测试,以及许多小型和大型供应商。

自动测试可以成为一篇文章--或一本书! - 自动测试可以成为一篇文章--或一本书!在它自己的权利中。这里有更多来自不同来源的思考。

18. 手动测试

当从一个小的应用程序开始时,手动测试每个版本是一个可行的途径。随着应用程序的增长,这种努力变得更加乏味。在规模上,对于一个每周发布的应用程序,有许多工程师在工作,这种方法将被打破。规模化的目标是始终有一个可发货的主程序,对人工测试的依赖性为零,或接近零。

即使在选择进行手工测试时,也有一些挑战,因为它不是(还)一个大的开销,或者因为没有足够的自动化测试来允许这种测试。

  • 谁来做手工测试? 当我开始在Uber工作时,工程团队拥有他们功能的所有手工测试。我们保留了一个简单的谷歌表格,有基本的说明,记录 "通过 "或 "失败"。我们每周都会重复这个检查表,作为构建训练的一部分。当然,随着我们的成长,这种方法不能很好地扩展,我们开始依赖一个发布/测试团队。
  • 你如何保持测试说明的更新? 无论谁做测试--一个工程师或一个专门的人--你都需要清晰和简单的说明。你还需要测试账户,他们的登录信息,以及每个测试步骤中要输入的数据。你将把这些存储在哪里?谁将保持它的更新?在Uber,平台团队建立了一个手动测试管理系统,工程团队用它来记录测试指令,测试人员每周将测试标记为执行或失败。
  • 将手工测试留在内部,外包,还是混合? 在Uber,我计算了我们工程师每周执行手工测试的成本。这个数字很高,足以证明为这项工作配备专门人员是合理的。在我工作期间,我们既使用第三方,如Applause,也使用专门的内部质量团队。内部团队在开始时比较省力,而且可以访问内部系统。另一方面,第三方可以更可靠,并且可以根据你需要的测试量扩大或缩小规模。
  • 你如何在你的构建训练和发布过程中整合人工测试? 你如何处理发现的问题?什么类型的问题应该阻止发布,什么是不会的事情?你需要将手动测试步骤纳入你的发布工作流程。

手动测试经常令人头痛的是如何在第11个小时,即在发布前发现回归。这仍然比带着新的退步进行发布要好。不过,这还是会给工程师带来压力,让他们迅速修复一个错误,以避免发布被推迟。错误的根本原因往往很难找到,因为自从违规代码被合并后,可能已经过了一周或更长时间。

如果这种 "最后一分钟报告错误 "的问题经常袭击你或你的团队,可以考虑缩短获得反馈的时间。例如,手动测试可以更早开始吗?能否与开发同步进行?它可以连续发生在关键场景中吗?工程师应该执行一些基本测试吗?自动化可以提供更多帮助吗?

当你有一个人工测试过程时,确保留出足够的时间,不仅用于测试,也用于修复任何高影响的错误。做到这一点,要么尽早进行手动测试,并为修复留出缓冲时间,要么在发现回归时灵活地推后应用程序的发布时间表。

在少数情况下,手动测试对移动应用程序仍然是必不可少的,即使是那些投资于一流的自动化并不遗余力的公司也同意。

  • 与物理世界的互动。 当依靠相机输入来识别二维码等模式,做文档扫描或AR时,你可以将大部分测试自动化,但仍需要做人工验证。这同样适用于利用NFC的应用。
  • 支付系统的端到端测试。我在Uber工作了四年,从事支付工作。支付测试的自动化有一个问题:支付欺诈系统已经足够成熟,可以检测并禁止可疑的模式。

检测和禁止可疑的模式,例如看起来是自动化的东西。这意味着他们很快就会禁止自动化测试。你可以针对支付供应商的测试线束进行测试。

但这样你就不能针对生产进行测试。决定你是投资于解决支付欺诈系统,还是投资于监控、警报和分阶段推出支付变化,将取决于你的情况。在Uber,我们转向了前者。

  • 探索性测试。优秀的QA团队和供应商擅长于这种情况。他们试图用工程师没有想到的创造性步骤来 "破坏 "应用程序,但无论如何,终端用户都会使用这些步骤。只是,与用户不同的是--他们经常悄悄地搅动,不报告任何事情--这些测试人员会提供详细的步骤来重现所有问题。对于拥有数百万用户的应用程序,探索性测试是一个需要投资的领域。唯一的问题是多长时间做一次,花多少预算或时间在上面。

关于测试,要谈的远不止这些,我推荐Daniel KnottHands-On Mobile App Testing一书,以深入了解自动化和手动测试。这本书涵盖了测试策略、工具、快速移动发布周期、整个应用生命周期的测试等等。

章节摘要

由Bitrise的Kevin Toms撰写,作为本书的补充

这些章节研究了应用开发的复杂性,这些复杂性是由以下原因造成的

移动应用的构建和发展方式所带来的复杂性。与网络应用相比,移动应用有一个更多的导航架构。这就为变化提供了空间,可能会导致对各种可用的UI部件的使用不一致。移动应用的导航也可以因用户切换应用或不等待异步事件的完成而改变--这是开发者需要考虑的另一件事。

移动应用程序还必须应对操作过程中的各种事件。例如,用户可能会做出点击动作,应用程序可能会被放入后台,或者发生网络事件。由于应用程序不能控制这些事件,开发人员在设计架构时需要采取一些措施,以防止这些冲突的事件集压倒应用程序的完整性。

与部署在服务器上的网络应用不同,移动应用被部署到应用商店并安装在用户设备上。这意味着,如果不对应用程序进行全面的App Store更新,就不能对本地化进行更新,除非从后台服务中收集本地化的字符串。这也依赖于连接,而应用程序并不总是连接。

随着应用程序的规模越来越大,使应用程序更加模块化,并从单独测试和信任的组件中构建应用程序的需求变得更加重要。这就需要有一个处理依赖关系的系统。依赖注入解决了一些问题,但在管理依赖关系方面会增加另一个层次的复杂性,因为它需要适当的工具。然而,它确实改善了使用模拟对象进行单元测试的过程。

自动测试提高了应用程序的稳健性和可靠性,通过自动重复和保持代码覆盖率,这有助于防止回归领域。

对于移动应用程序,有许多类型的测试是相关的,例如。

  • 单元测试:测试孤立的软件组件。
  • 集成测试:测试多个组件之间的互动。
  • 快照测试:将UI的图像作为快照进行测试,以帮助确保一致性。
  • UI测试:自动测试应用程序的UI。这可能是最复杂的自动化。

实施自动化测试和其他各种类型的测试是一种有价值的方式,可以在应用程序变得更加复杂时管理健壮性。有许多工具可以使这个过程更容易。许多应用程序只从手动测试开始。它需要更多的个人人员来做,不能很好地扩展,而且容易出现不一致的情况。然而,即使有广泛的自动化测试,它也可以是一个有价值的额外检查,特别是对于复杂的环境情况,如难以自动化的可变网络连接。


www.deepl.com 翻译