复杂业务场景下如何进行iOS端自动化测试

去年写了一篇关于《容器化时代对测试的机遇》的文章,提到了一些分布式自动化测试和容器化技术结合的架构设想。但是目前来说,分布式运行并不是难点,亟需解决的问题是针对特殊平台和复杂场景下的测试,例如复杂业务场景下iOS平台的自动化测试。

移动应用特点是简单易用和UI简洁,以便用户在移动端完成一件事的路径尽可能短。所以一般情况下,我们遇到的iOS APP场景相对于Web应用要简单一些。所以一般情况下iOS自动化测试并不会遇见复杂场景,测试反馈时间短,效率相对较高。对运行环境来说,只需要相应版本macOS系统以及Xcode环境即可。

但是,对于大型企业的移动应用,例如电商平台、共享出行平台等,牵扯到的主要几个问题:

1. 大规模的测试用例导致测试反馈时间太长

说到这个问题,就要说到现在主流的移动端自动化测试框架AppiumCalabash。我所经历过的大部分项目,无外乎使用其一。

但在Xcode 7之后,iOS Simulator变得越来越慢(做iOS的同学们应该都有体会),更不幸的是,在iOS 10、Xcode 8之后,Apple弃用了UIAutomation,导致大量高效、常用的API无法使用。

并且迄今为止,Appium没有针对iOS 10平台发布一个正式版本的lib和APP,这就导致一些用户无法使用inspector定位元素(使用ARC的用户除外),虽然官方建议不要使XPath进行元素定位,但有的时候我们不得不这么做。最大杀器是iOS自动化受到Apple的单例限制(一台物理主机同一时间有且仅有一个Instrument)。

这些种种最终导致了iOS自动化测试时间太长,更不用谈及多种iOS设备的兼容性问题了,自动化实现过程成本过高,令大部分组织和团队食之无味、弃之可惜。

2. 复杂场景无法在一台机器上进行测试

对于复杂场景的应用来说,我们很难通过现有框架同时在一台物理机上控制多个不同的模拟器,也无法随意的切换到系统级控件去查看APP触发的通知等等。你可以通过一些合法途径使用虚拟化做iOS端的并发测试(切记合法途径)。

但这样还是逃不掉物理机庞大的开销以及虚拟机的性能损耗问题,抛开这个问题不讲,单从复杂场景来说,例如出行平台,你需要一台机器作为乘客发布订单,还需要多个拥有不同地址定位的车主来测试订单推送优先级等。对于这种复杂场景来说Appium控制起来就很难了。

3. 测试场景需要切换不同APP

如今很多的APP功能不单单是在应用本身,可能还需要跟系统应用以及其他应用进行交互,例如用户在被测APP中执行某个操作之后,需要检查notification,或者在测试的过程中需要切换无网络环境,从而测试APP的不同行为。

想到这些复杂场景和各种坑之后,估计打算做iOS测试的同学心里开始打退堂鼓了。下面我们来一步步逐一解决这些问题:

问题一:解决Instrument单例的限制

对于这个问题困扰了很久,那业界领先的互联网公司又是怎么做的呢?有一次看到Uber的Showcase,在一台机器上启动了5、6台模拟器,用不同类型的账号登录(乘客、车主)每个模拟器做不同的行为。由于是在物理机上的对iOS模拟器的操作,速度和性能都得到了很好的保证。他们是怎么解决Instrument的限制呢?

我们可以通过使用Apple私有API,同时操作不同型号的模拟器,对多个不同的Simulator进行批量化操作,例如启动、重置、安装、运行等操作:

问题二:解决复杂场景下控制不同iOS模拟器的不同行为

xcodebuild命令使我们可以把WebDriverAgent运行在我们想要的设备上,但如果使用Apple的命令,还是只能在单个设备上安装运行,之前运行的多台设备都会自动关掉,而只会保留命令中的destination,默认启动8100端口去检测这台设备:

如果这样的话,那我们之前做的所有工作不就没有任何意义了吗?别急,我们已然可以通过Apple提供的资源,对不同的设备启动不同的进程端口进行监听:

这时我们可以通过curl命令launch我们需要的进行测试的APP,可以轻而易举的拿到当前运行APP的session:

curl -X POST '-H "Content-Type: application/json"' -d "{\"desiredCapabilities\":{\"bundleId\":\"com.apple.Preferences\"}}" http://localhost:8101/session

response:
{
  "value" : {
    "sessionId" : "94A6580F-1F0F-4411-AC64-3E2525BBA5E1",
    "capabilities" : {
      "device" : "iphone",
      "browserName" : "Settings",
      "sdkVersion" : "10.1",
      "CFBundleIdentifier" : "com.apple.Preferences"
    }
  },
  "sessionId" : "94A6580F-1F0F-4411-AC64-3E2525BBA5E1",
  "status" : 0
}

同时,对于不同的设备,我们可以通过HTTP server启动inspector来帮助我们进行APP中的元素定位,即使是系统应用:

问题三:解决不同测试场景需要APP的切换

有了第二个问题的解决方案,只要执行相似的curl命令,就可以拿到不同的APP以及不同的sessionId.

是时候放弃Appium了?

通过Uber的Octopus框架以及Appium正在使用的WebDriverAgent, 不难发现此方案的推广速度以及乐观的前景。我们可以使用不同curl命令对不同的Simulator以及APP进行query、tap、typing以及touch id等操作,这与Appium提供的那些我们最常使用的API的等价的,并且由于不需要先去调Appium API 而直接去通过WebDriverAgent与元素进行交互,使得测试执行速度上有不同程度的提高,又由于自身强大的控制力以及灵活性,使其可以轻松进行并发操作和复杂业务场景支持,我们只需要把不同的curl命令进行封装,结合各自APP的业务场景便可以轻松完成。

带来的成本?

可以说大部分团队没有引入移动端自动化的原因,最主要的无外乎编写成本高,UI变化快。个人认为这个方案带来的价值比其带来的成本要大得多。不再需要QA再去学习新的语言来编写脚本,所有与APP元素的交互都可通过HTTP请求来完成,元素信息通过易读的JSON来呈现。我们可以通过任何语言和框架用编写后端自动化测试的方式完成iOS的自动化测试。

下面通过测试ThoughtWorks的StartKit做一个简单的登录页面的测试Demo,并且我们已经在超过三个项目中使用过该测试方案。

总结:

由于项目因素,我们实践的场景会相对受限,长时间如此可能会影响我们解决问题的思路,我们应该不时的跳出自己工作之外去思考,把简单的事情做的复杂,这样才可以在碰到复杂问题的时候,做的简单。


更多精彩洞见,请关注微信公众号:思特沃克

Share

一个遗留系统自动化测试的七年之痒

背景

项目从2009年开始启动,采用的是TDD开发方式。在这之后的过程中,团队做过各种尝试去调整自动化测试的策略去更好的适应不同阶段项目的特征,比如调整不同类型测试的比例,引入新的测试类型等。

七年之痒 – 痛点

随着项目走到了第七个年头,一系列的变化在不断发生,比如技术上引入了微服务、EventStore等,业务变得越来越复杂,子系统变得更多,更多的人员加入,开始实施按月发布等,这些因素交织在一起凸显出自动化测试的滞后。首先是从团队成员感知到的一些痛点开始的:

质量下降 – 这个体现在部署到测试环境的代码质量较差,常常就是新版本部署上去之后某个核心功能被破坏,要么是新功能破坏了老功能,要么是bug的修复把其他功能破坏。

测试不稳定 – QA有很长的时间在等待修复或新功能提交出包,而这个等待可能是几个小时也有可能是几天。除去网络问题、部署流水线的复杂性等因素,自动化测试的不稳定性也导致出包的速度也受到了影响。大家往往更关注于怎么能把测试通过了能够出一个包,却忽略了我们该怎么去处理一个不稳定的测试。下图的run2, run3正是大家在不断的尝试去rerun挂掉的测试。

1-rerun

团队越来越忙,开始陷入恶性循环 – 随着功能的逐渐增多,每个月上线的回归测试列表越来越长,QA需要花更多的时间去做重复的回归测试,新功能的测试和回归测试的压力都很大,甚至有的时候根本都没有时间去review下一个阶段的需求,更别提其他一些更有价值的事情。往往回归测试做不完就不得不往下一个阶段推,这种不断往复导致大家对发布的产品信心严重不足。

在这种情况下,自动化测试的有效性和完备性都受到了质疑。本来期望自动化测试能够帮助我们构建一张安全的防护网,保证主干业务不被破坏;随着pipeline频繁的去执行,及时反馈问题,不要等到测试环境才暴露出来;同时能够把QA重复的手工测试时间释放出来,去做一些更有价值的事。可是根据团队所感受到的痛点,我们觉得自动化测试不仅没有帮助到我们,反而在一定程度上给团队带来了干扰。

问题分析

自动化测试到底出了什么问题?我们从现有UI测试入手开始分析,发现了以下典型现象:

  1. 最有价值的场景没有被覆盖
    虽然有较多的测试场景,但是体现出核心业务价值的场景却稀少。我们都知道80/20原则,用户80%的时间在使用系统中20%的功能,如果大部分的UI测试是在测另外那80%的功能,这样一个覆盖给团队带来的安全指数是很低的。
  2. 失效的场景
    功能已经发生了变化,可是对应的UI测试并没有变,至于它为什么没有挂掉,可能有一些侥幸的因素。比如现在点了确认按钮之后新增了弹窗,而测试并没有关掉弹窗,而是通过URL跳转到了别的页面,也没有验证弹窗的新功能是否工作,既有的实现方式确实会使得测试一直通过,但是没有真的验证到正确的点。
  3. 重复的测试
    同样的测试在UI层跟API层的重合度较高,有的甚至是100%。比如搜索用户的功能,分别去按照姓、名、姓的一部分、名的一部分、姓+名等等各种组合去验证。我们不太清楚在当时是基于怎样的考虑留下了这么多跟UT/API测试重复的用例,但是在现阶段分析之后我们觉得这是一种没必要的浪费。 另外,不同的测试数据准备都是UI测试执行出来的,很多场景都用到了相同的步骤,我们觉得这也是一种重复,可以通过其他方式来实现。

解决问题

这些问题从某种角度上都暴露出了UI测试年久失修,没有得到好的维护,而新功能的自动化测试又在不断重蹈覆辙,问题积攒到一起暴发出来使得大家开始重视自动化测试。 有痛点并且找到问题了,下一步就是解决问题。我们分了两步来走,第一个就是对已有UI测试的优化,第二个是对新功能的自动化测试策略的调整。

已有UI测试的优化

针对已有的自动化测试,再回过头逐个去审查UT/API/UI测试代价太高,我们只能是从UI测试优化入手。针对前面提到的3个问题我们逐一去攻克:

  • 识别系统关键业务场景
    我们首先挖掘出系统中用户要达到的各个业务目标,根据不同的业务目标梳理出不同的业务场景,然后将这些发给客户去评审。客户对我们总结出的这些场景很认可,只是把每个业务目标都赋予了一个优先级,为后续的编码实现给予了参考。
  • 重新设计测试场景
    UI测试不是着重去测试某个功能是否工作,更关注的是用户在使用系统时能否顺利实现某个业务目标,因此我们需要知道用户是怎么使用系统的。同样的目标,可能会有多个途径来完成,通过跟客户的访谈以及观察产品环境下页面的访问频度,我们重新规划了测试场景,期待能更贴近用户的真实行为,及时防御可能会导致用户不能顺利完成业务目标的问题。
  • 优化测试数据准备,删除重复的测试
    对于UI层的过度测试,直接删除和API/UT层重复的测试,保留一条主干路径用来测试系统连通性。 对于不同的业务场景可能需要准备的数据,我们舍弃了之前通过UI执行测试这种成本高的方式,转而以发API请求的方式来准备,这样降低了测试执行的时间,也使得测试更加的稳定。 重新梳理的测试场景帮我们构建了一张较为全面的安全防护网,覆盖了绝大部分的用户使用场景,大家的信心显著提高。如果核心业务受到破坏,立马就可以通过UI测试反馈给相关的人。执行更加稳定的测试也减少了大家对UI测试是否能真的发现问题的质疑,因为随机挂的频率大大的降低,一旦挂可能就是真的有bug了。

新功能自动化测试策略的调整

我们一般会以测试金字塔作为自动化测试的指导策略,下图是我们项目的测试金字塔。

2-test-pyramid

为了避免新功能步旧功能的后尘,我们对自动化测试策略进行了调整。除去以不同层的数量分布来判定策略是否合理外,我们也更看重在这个数量下关键业务场景是否能被有效地覆盖,主要通过下面两种方式来保证:

  1. QA及早介入自动化测试
    质量是需要内建的,不是测出来的。QA从一开始就介入整个流程之中,在story启动的时候会和DEV一起准备任务拆分。在后期验收story的同时也会验收单元测试,确保能在UT/API/Contract层实现的测试都在这些层面覆盖,不仅保证了底层测试的数量要够多,也确保了这么多测试覆盖的点都是合理有效的。在这个过程中,QA把更多的测试思路传递给团队成员,引发大家更多的从质量角度去思考。
  2. QA与DEV结对写UI测试
    最后在整个功能做完的时候,QA也会和DEV结对实现UI测试,涉及到现有测试场景的维护与更新。基于前面对底层测试的review,大家对于整个功能的测试覆盖都有了一定程度的了解,对于UI测试要测得点也会较快的达成一致。另外,QA在与DEV结对实现UI测试的时候,编码能力也得到了提高。

在推动QA更多参与底层测试的过程中,我们更多的从测试角度去影响团队,增加了团队的质量意识。QA的时间被释放出来了,去做了更多有价值的事,比如探索性测试,Log监控与分析,安全测试,产品环境下用户行为分析等。这一些列活动的影响就是产品的质量顺便得到了提升。

总结

等我们把已有功能UI测试优化完,新功能的自动化测试策略开始落实到全组,已经是半年以后的事了。我们慢慢的感受到一切都在回归正轨,之前的痛点在逐步消去,团队交付的节奏也越来越顺畅,对发布产品的信心也更强了。

回顾这个遗留系统的自动化测试优化过程,我们有一些收获:

  1. 大家说到UI测试往往更倾向于如何编码实现,但我们希望开始UI测试的时候能多关注下测试用例的设计是否合理,是不是能够体现出业务价值。
  2. UI测试的用例和代码都是测试资产,需要跟产品代码等同对待,不能写出来就不管不顾,没有维护是不可取的。
  3. 自动化测试不仅仅是UI测试,需要和UT/API等其他底层测试一起分工合作,作为测试策略的一部分来为产品质量保驾护航。
Share