一、背景
自动化测试一直是测试中非常重要的一环,好的自动化测试方案,不仅能够节省测试人力,而且能够发现很多人工测试不能发现的问题。
传统的iOS自动化测试方案大多基于UI自动化或是基于MOCK的单元测试,但是两者的缺点也是显而易见的,对于UI自动化测试,存在运行不稳定与维护成本高的问题,对于单元测试的MOCK编写用例成本又太高,投入产出比较低。
基于以上背景,本文提供了一个新的iOS自动化测试的方案,该方案的主要实现是在APP运行是获取APP的内存数据,能够直接调用对应的方法,而不需要通过控件树或者MOCK假数据来执行用例,降低了编写用例的成本,除此之外,本方案集成了OCMock、HOOK的能力,大大扩展了自动化测试的能力。
二、新自动化方案
2.1 方案效果演示
下面以一个简单直观的例子展示本方案的实现。
在自动化测试中,在有些场景下我们需要验证数据的展示是否正常,如在微视的推荐页播放视频时需要展示该视频的评论数,其中当评论数大于10000时,需要展示成"x.x万"形式。显然,该测试用例无法通过UI自动化实现,但是通过我们新的自动化方案可以轻易通过几行代码就做到(1)修改数据,(2)更新UI界面,从而达到验证展示效果的目的。具体效果展示如下:
那么,写此用例的代码会不会很复杂呢?然而并不会,而且比写MOCK实现更简单。
通过调用[NTElementInstance chooseWithName:@"RecommendController"][0],获取当前推荐页ViewController的实例vcRecommendController ,该实例可以调用RecommendController 类的所有实例方法和属性,然后就可以像写业务代码一样写测试代码了。下面简单说明下为什么从推荐页的ViewController可以获取到显示评论的FeedDetailPanelView。我们结合微视APP代码可以知道:
- 推荐页的VC为RecommendController,其父类为ListBaseController
- 其父类中提供了获取当前FeedDetailCell的方法currentVisiableFeedDetailCell
- 而FeedDetailPanelView是FeedDetailCell的一个属性
所以,我们可以通过RecommendController获取FeedDetailPanelView的实例,并修改和展示评论数。
2.2 MonkeyDev
为了说明整个方案的代码结构,首先有个开源工具必须了解下,那就是MonkeyDev。
简单来说,就是只需要一个IPA的安装包,就可以在非越狱机器上使用HOOK的功能。
2.3 新方案代码结构
这套方案就是使用MonkeyDev创建了一个MonkeyApp工程,命名为NextTest.xcodeproj。所以,这个方案是独立于APP原工程的,而我们只需要一个APP的IPA安装包,就可以做到测试APP的功能。这样,也方便其他业务的接入。下面以NextTest 微视APP为例,说明一下整个代码结构和运行流程。
代码结构说明:NextTest.xcodeproj工程有3个Target,NextTest、NextTestDylib和NextTestTests
- NextTest:MonkeyApp工程自动创建,有个TargetApp文件夹,里面放入一个微视的IPA安装包,就可以测试微视APP;
- NextTestDylib:MonkeyApp工程自动创建,有个NextTestDylib.xm文件,可以用于HOOK微视;
- NextTestTests:创建的一个UnitTest,微视的测试用例都在此;
- NTMicrovisionTestCase:微视测试用例的父类,是XCTestCase的子类;
- NTElement:用于在内存中查找指定对象的实例;
- NTThread:线程运行相关.
用例代码说明:该方案写用例都有固定的流程,我们还是以修改评论数的用例代码为例,主要有以下几个步骤:
- 初始化owner、desc和timeout用例属性
- 进入APP对应的场景,调用NTElement的方法,获取需要的实例对象
- 调用获取的实例中的方法,做用例需要做的事
- 断言
NTElement说明:如何在内存中找到需要的实例对象呢?其实NTElement并没有什么高深的黑科技在里面,只是提供了两个方法,一个获取ViewController,一个获取View,一般来说,通过ViewController或者View作为入口,都可以找到需要的对象。下面是找ViewController的实现,找View的实现和该方法十分类似。
三、集成的能力
该方案不仅可以获取运行时的内存数据,也可以修改内存数据。同时,还集成了OCMOCK和HOOK的能力。下面再上一个视频,展示一个该方案提供的各种能力。3个用例运行视频:
- 第一个用例:修改数据,修改当前视频有危险视频标记
2. 第二个用例:使用HOOK,hook网络为大王卡
3. 第三个用例:使用OCmock,mock网络请求返回网络有问题
四、用例编写技巧
用例的编写与断言是自动化测试的一块核心内容,如何快速地编写又快又好的用例,这其中也有很多技巧。
首先对于iOS而言,大前提肯定是对Objective-C语言有足够的熟悉,并且对APP的代码逻辑有一定的熟悉程度,才能够编写接口测试或者功能测试用例。
这里我们尽量结合实际的例子给大家讲解一些写用例的一些技巧和方法,让大家能够更快地进行用例编写。
4.1断点获取调用参数
在微视中,是没有入口进入“轻触拍照,长按摄像”的录制页界面的,只有从微信朋友圈跳转过来有这个入口,那么如果我们想测试这个功能,需要从微信朋友圈再跳转过来吗?答案当然是不需要,我们知道,iOS中APP的schema跳转调用以下函数:
我们在源APP(微视)中对朋友圈跳转微视的此行函数进行断点,查看参数:
这里很容易就获取了我们所需要的openURL和sourceApplication的参数。
4.2 构造对象调用函数
4.2.1 直接使用单例
我们知道,在测试过程中,常常会有一些不容易构造或者不容易获取的对象,这时候我们用一个虚拟的对象来使我们的测试更加方便。
继续以朋友圈跳转微视为例:对于上述的获取了朋友圈跳转微视的参数之后,我们可以自己实例化一个UIApplication的对象来进行调用,代码如下:
这样我们便轻松地模拟了从微信跳转到微视的操作。
4.2.2 创建并初始化类
还有些时候我们需要自己创建并初始化一个类来测试代码中的一些功能,比如在测试下载功能的时候会用到"MaterialManager"这个类,但是这个类在我们的测试工程NextTest中并没有实现,只在微视的工程中实现了,但我们想创建它并对这个类的方法进行测试,那该怎么办呢?
其中“NSClassFromString”方法是封装好的可以根据字符串获取原工程中对应类的方法,在编写用例时可以直接使用。
4.2.3 修改参数的值
还有一种情况,我们需要更改一些值以便我们更好的测试,举个例子,在微视拍摄中,有一些提示语只有第一次进入才会出现,比如APP第一次进入编辑页会有"长按屏幕拖动进度条"的提示,但是我们不可能每次都卸载重装,然后再构造第一次进入的场景,这时候我们只需要知道对应的参数或者标志位,直接进行更改:
通过以上几种方式便能够测试一些平常的手工测试或者UI测试难以做到的,比如:1.需要构造大量的操作,如点10000个赞等2.手工难以反复触发的操作,如首次进入等3.平常难以复现的场景,如点赞或者评论失败4.测试底层方法的功能是否正常,如下载组件的方法5.快速测试复杂场景,如反复测试视频的合成是否成功6.等等……
这样便能反复模拟第一次进入该界面的场景。
通过以上几种方式便能够测试一些平常的手工测试或者UI测试难以做到的,比如:
1.需要构造大量的操作,如点10000个赞等
2.手工难以反复触发的操作,如首次进入等
3.平常难以复现的场景,如点赞或者评论失败
4.测试底层方法的功能是否正常,如下载组件的方法
5.快速测试复杂场景,如反复测试视频的合成是否成功
6.等等……
4.3 hook函数
有时候,原工程的一些函数不能满足我们测试的要求,或者我们希望在原方法中增加一些我们需要的功能,这时候我们就需要用到hook技术,我们的自动化测试方案也能够很好的支持hook技术。
本自动化测试方案中使用的是Logos语法进行hook,它能够让hook代码非常的简单明了,能够替换、修改、新增方法等,简单举个最常用的修改某个类ClassName中的MethodA的方法的函数.
如上面所示很简单的我们就hook了某个方法,并且改变了这个方法的具体实现,当然我们还可以给类增加新的方法等,一般我们还会加个宏进行控制,防止影响平时的执行:
通过hook方法,我们可以做的事情便大大增加,比如:
1.输出测试需要的log,能够计算启动耗时或者下载耗时等
2.增加相关的测试方法,比如我们可能在拍摄时需要获取摄像头的参数(对于共有的api方法,甚至可以获取竞品的一些参数)
3.判断逻辑是否正确,有一些分支我们需要知道函数的执行逻辑是否正确,能够知道程序是否运行到我们想要的函数
4.修改逻辑,构造异常场景,我们可以让某些方法返回错误码或者一些异常值,从而构造平时难以触发的异常场景/逻辑
5.等等……
4.4 序列化与反序列化
有一些类我们通过断言方式发现其太多参数,或者发现初始化的参数太多,我们很难自己创建一个类,这时候我们就可以用到本框架提供的序列化与反序列化的功能。
序列化:(Serialization)将对象的状态信息转换为可以存储或传输的形式的过程。我们实现序列化其实很简单,比如我们有一个对象:
对这个对象进行序列化只需要简单的一句:
这个封装好的方法会将序列化后的对象保存到手机的沙箱目录中,其中"serial_A"就是保存用于识别的名字。我们需要将序列化的文件放入工程中,然后通过以下代码就可以反序列化,获取到这个对象,而不需要自己进行复杂的构造类或者对象操作:
4.5 编写UI操作
大多数情况我们可以直接对函数或者类方法直接进行测试,但是有些操作却离不开一些UI操作,比如微视的视频编辑过程,很多都是通过UI触发的,大多数情况下我们不建议进行UI操作,直接进行底层函数操作,但是,当然了,我们的自动化测试方案也具备了支持界面UI操作的能力。
4.5.1 UI操作编写介绍
UI操作使用了一个开源的用户界面UI测试框架KIF,能够编写模拟用户输入,诸如点击,触摸和文本输入等等。
这里的UI操作都是针对UIView的,所有的UIView或者集成UIView的一些空间如UIButton等都可以使用以下方法(简要介绍几个常用操作)。
tap点击操作:举个例子,我们获取到了某个VC即XViewController中的一个UIButton即AButton,我们想在当前页面对齐进行点击,那么只需要简单的两行,便可以实现这个Button的点击操作:
其中chooseWithName方法是我们测试架构中封装好的一个根据VC或者UIView名字获取当前VC或者UIView的一个方法,返回的是一个数组。
longPress长按操作:有时候我们需要长按一个View的某个点(如:点60,60),并且需要持续一定的时间(如2秒),这时候可以使用我们封装好的"longPressAtPointX:Y:duration:"方法。
drag拖动操作:当我们需要操作从一个点(a,b)拖动到另一个点(c,d)的操作时,我们也可以很容易实现:
除了以上操作之外,还有"多点点击"之类的操作就不赘述了。
4.5.2 关键字定位UI界面
先说简单的,有时候我们需要定位一个UI界面,可以通过简单的关键字来进行定位,比如从微信朋友圈跳转到微视,在录制按钮上方会有关键字"轻触拍照,长按摄像"字样,我们只需要在源代码工程中对齐进行搜索,就能够发现,这个字样属于MCCameraBottomBar这个View的,而MCCameraBottomBar又是属于MCCameraViewController这个VC的,VC,然后再调用VC中的方法,或者进行前面说的UI操作。
当然,这个方法并不是万能的,适用于一些关键字明显的界面,很多交互界面甚至都没有字,这时候我们可以通过以下介绍的其他方法来进行。
4.5.3 Cycript库协作定位
Cycript是Objective-C、Java等语法的混合物,它其中一个功能是可以用来探索、修改、调试正在运行的MaciOSAPP。
官网:http://www.cycript.org/ 使用文档:http://www.cycript.org/manual/
使用Cycript中的mjcript与手机连接之后,我们可以进行简单快速的调试,比如我们可以直接看到当前界面的UI组成:
当前页面我们可以看到最顶层的VC是CameraViewController,所以我们也找到了我们需要进行操作或者调用方法的VC,还能看到其内存地址,我们可以通过内存地址直接调用。比如0x10c5ed200内存地址即CameraViewController这个VC有个Amethod方法,我们可以直接通过
进行调用,当然直观一点,我们可以通过以下进行调用
除此之外,我们还封装了一些方法,如MJFrontVc()能够直接看到当前VC:
MJChoose()方法可以直接获取我们某个VC或者某个View的地址,返回是一个数组
此处的优点是我们可以快速进行简单调试,并且我们可以看到这里是不需要显式声明变量的类型的,可以直接使用。通过以上方法我们就可以在命令行里进行简单的调试和调用函数(不限于UI操作),或者打印参数,以便我们更好的编写用例。
4.5.4 使用Reveal协助
使用Reveal软件,可以更加直观地看到整个页面的布局,点击某个按钮 Button,我们可以看到它属于哪个类(包括地址),并且这个类属于哪个VC(包括地址)的,左边还有整个UI界面的树状结构帮助我们快速定位代码。
4.6 断言
一个用例最终是否通过,还是需要通过断言进行判断。本自动化测试方案目前是使用XCTest框架,所以自然可以使用XCTest的断言,这里列出网上查找到的XCTest的一些断言方法:
当然,其实我们最常用的还是XCTAssertTrue方法。
五、总结
5.1 用例稳定性对比
- 新方案在自动化测试过程中有着良好的稳定性,我们的测试方案能够调用底层函数而不需要频繁的UI操作,大大提高了测试的稳定性,从而保证了测试的质量。
- UI自动化的稳定性差,是由于经常会出现空间查找失败或者UI变更导致用例失败。
- OCMock单元测试都是基于底层接口的测试,稳定性高。
5.2 执行效率
- 新方案由于涉及的测试场景较多,能够取到特定场景的内存数据进行测试,并且可以执行UI操作,所以会牺牲一些运行效率,但是当只进行底层接口测试时,仍然能够有很高的执行效率。
- UI自动化每一步都需要查找控件,再进行UI操作,所以效率最低。
- OCMock不依赖UI,所以执行效率最高。
5.3编写用例效率
- 新方案通过上述的编写用例例子与技巧,可以发现可以通过少量的代码就实现我们的测试逻辑,并且随着熟练度的提高,编写用例效率会越来越高,容易上手。
- UI自动化需要频繁定位控件,写起用例来很繁琐,而且存在控件不好定位的问题。
- OCMock单元测试需要MOCK数据,构造起来很复杂,而且有些数据还不好MOCK,编写难度较大,效率较低。
5.4维护成本
- 新方案基于变动较小的APP源码的接口写用例,所以维护成本很低。
- UI自动化强依赖于UI,但是APP的UI基本每个版本都会有变化,所以要时长进行用例维护,消耗大量人力。
- OCMock单元测试也是基于接口的,维护成本低。
5.5 发现问题能力
- 新方案不紧可以使用HOOK、MOCK和修改内存数据,测试APP内部接口或者逻辑,也具备验证UI层的变化,发现问题的能力,是UI自动化和OCMock单元测试的综合,具有很强的发现问题的能力。
- ** UI自动化**只能验证UI层的表现。
- ** OCMock单元测试**做不到验证UI效果的能力,一般都是作为接口测试。
5.6 源码依赖
- 新方案是一个独立的测试工程,能够做到调用APP内部接口的同时不依赖被测APP的源码进行测试,只需要一个IPA包就可以测试APP的功能(所以甚至能够做到对竞品APP的通用接口测试)。
- UI自动化根据不同的方案对源码的依赖是不同的,如果用的是APPIUM,就不依赖源码,如果用的是KIF,那就需要集成到源码中了。
- OCMock单元测试需要集成到源码。
后期我们会根据每个维度陆续写相关的测试文章,如果你有兴趣,请关注我们哦。
长按指纹识别图中的二维码,获取更多测试干货分享!
将我们公众号置顶
不会漏掉我们的原创干货哦!