KIF-- iOS UI 自动化测试探索

KIF-- iOS UI 自动化测试探索

在我们探索自动化测试之前,我们先了解一下自动化测试的优缺点和还有,什么样的业务适合自动化测试。

自动化测试

自动化测试就是写一些测试代码,用代码代替人工去完成模块和业务的测试。

其实不管是开发还是测试,如果你在不断的做重复性工作的时候,就应该问自己一个问题:是不是有更高效的办法?

  • 自动化测试的优点:

    • 测试速度快,避免重复性工作
    • 避免regression(回退),让开发更有信心去修改,优化甚至重构代码。(有测试代码做依托,不怕业务逻辑丢失,混乱)
    • 测试结果一致性
    • 自动化测试的实现,有助于持续集成的可行性和可靠性提升
    • 强化开发人员编写高质量代码(自动化测试,不通过,不能提交合并)
  • 自动化测试的一些缺点:

    • 开发和维护成本高,需要专业的测试人员
    • 不能完全替代人工测试
    • 本身的测试代码的准确性,还有无法保证测试的准确性-让代码去判断一段逻辑是否正确还是可行的,但是要判断一个控件是否显示正确,代码很难实现
    • 团队的建设和方案的选取等一系列的问题

所以,在做自动化测试之前,我们首先要针对项目提出几个问题:

  1. 这个测试业务的变动是否频繁
  2. 这个测试业务是否属于核心功能
  3. 编写测试代码的成本是多少,是否划算
  4. 自动化测试能保证测试结果的准确么

通常我们只会选择那些业务稳定,需要频繁测试的部分来编写自动化脚本,其余的依然得人工测试,人工测试是 iOS App 开发中不可缺少的一部分。

测试种类

从是否接触源代码的角度来分类: 测试分为黑盒和白盒。

白盒测试的时候,测试人员是可以直接接触待测试App的源代码的。白盒测试更多的是单元测试,测试人员针对各个单元进行各种可能的输入分析,然后测试其输出。白盒测试的测试代码通常由iOS开发编写。

黑盒测试。黑盒测试的时候,测试人员不需要接触源代码。是从App层面对其行为以及UI的正确性进行验证,黑盒测试由iOS测试完成。

从业务层次来说 iOS 测试通常只有以下两个层次:
Unit,单元测试,保证每一个类都能够正常工作

UI,UI 测试,也叫做集成测试,从业务层的角度保证各个业务可以正常工作。

框架选择

测试框架五花八门,一定要选择适合自己团队的,测试效率,集成难易度,维护难易度等等,选择框架的时候我们要考虑一下几个方面:

  1. 测试代码编写的成本
  2. 是否可调式,调试是否便利
  3. 测试框架本身的稳定性
  4. 测试报告是否详细(截图,代码覆盖率...)
  5. WebView 的支持(H5混合的 App)
  6. 自定义控件的测试支持
  7. 是否需要源代码
  8. 是否需要连着电脑和设备
  9. 是否支持持续集成

其中单元测试,我们上一篇着重介绍了 BDD 的老牌测试框架 Kiwi ,就不多说了。

UI测试,UI 测试的框架有很多,有的是以 UI Automation 为基础,对其进行补充和优化,包括扩展型 UI Automation 和驱动型 UI Automation。
还有一些框架类型是私有 API 和注入编译型等。
在以上分类中挑选具有代表性的自动化框架:UI Automation、Appium、KIF、Frank、UI Testing 进行对比,下表是这几种测试框架的特点对比:


图片来源网络

结合上面我们选择框架要考虑的几个方面,KIF框架已经展现了它的优势,并且KIF使用XCTest框架,使得其测试流程iOS程序的单测无异,可完全复用单测的持续集成流程,维护持续集成的成本相对降低;另外,KIF是一个活跃的开源测试框架,可扩展性好,升级更新快,有活跃社区来探讨和解决使用过程中遇到的问题。就是今天我们要介绍的重点,KIF

KIF

KIF的全称是Keep it functional。利用 Apple 给所有控件提供的辅助属性 accessibility attributes来定位和获取元素,完成界面的交互操作;结合使用 Xcode 的 XCTest 测试框架,拥有 XCTest 测试框架的特性,使得测试用例能以 command line build 工具运行并获取测试报告。

KIF 搭建

我们首先应该在工程项目中创建基于 Cocoa Touch Testing Bundle 模板的 Target ,并确保创建的 Target 的属性有如下设置:

“Build Phases”:设置Target Dependencies,UI自动化测试固然要依赖应用程序的App产物,所以需保证应用程序 Target 被添加在 Test Target 的 Target Dependencies 中。

“Build Settings”:设置 “Bundle loader”为:$(BUILT_PRODUCTS_DIR)/MyApp.app/MyApp;MyApp使你自己项目的路径

设置 “Test Host” 为:$(BUILT_PRODUCTS_DIR);

设置 “Wrapper Extensions” 为:xctest。

  • cocoaPods导入:
target 'Your Apps' do
  ...
end

target 'Acceptance Tests' do
  pod 'KIF', :configurations => ['Debug']
end
  • 手动导入,最新的framework 方式导入,非常头疼
    • 下载KIF源码,选择 KIFFramework这个 scheme编译,products 里面生成KIF.framework,show in finder 把它拷贝到我们需要测试的项目里面去
    • 打开 Xcode file new Add target 选择 iOS Unit Testing Bundle 或者 iOS UI Testing bundle 设置一个自己喜欢的target 名称
    • 选择我们新建的 target 点击Build Phases下的Link Binary With Libaries,添加我们刚拷贝过来的KIF.framework,系统依赖库QuartzCore.frameworkCoreGraphics.framework
    • 然后选择这个 target 的Build Settings,在Other Linker Flags里面添加 -framework IOKit-ObjC这两选项
    • 接着设置User Header Search PathsFramework Search Paths的路径为我们新建的 target
    • 最后,设置Bundle Loader为"$(BUILT_PRODUCTS_DIR)/MyApplication.app/MyApplication" 里面的 MyApplication是自己自己项目的名字
    • 最后一步,最重要的一部,先把项目跑一遍,生成MyApplication.app之后再执行command + U开始 testing
  • 现在可以开始写我们的测试用例了
    开始之前,我想来张图,KIF 基于苹果给所有控件添加的一个accessibility 属性来实现的,所以在 Storyboard 上我们有两种方式设置

还可以通过代码设置:

[alert setAccessibilityLabel:@"Label"];
[alert setAccessibilityValue:@"Value"];
[alert setAccessibilityTraits:UIAccessibilityTraitButton];

为了跟原业务代码隔离,我们在业务代码中应该建立宏来隔离我们设置 accessibility 属性的代码,如下面的例子:

#ifdef DEBUG
[tableView setAccessibilityValue:@"Main List Table"];
#endif

#ifdef KIF_TARGET (这个值需要在build settings里设置)
[tableView setAccessibilityValue:@"Main List Table"];
#endif

测试用例的编写和组织

  • accessibility属性设置

accessibility 属性是Apple给视觉障碍人群提供完全无障碍使用的基本属性,该属性表明了UI元素的可访问性、是什么、做什么以及会触发什么样的操作。原生的UIKit控件默认提供了这些信息,然而,自定义的控件则需要对该属性进行设置,设置方式可参考下面几点:

  1. 设置方式:storyboard 设置,代码设置
  2. 查看方式:Xcode打开Open Developer Tool开启模拟器的 Accessibility Inspector功能,即可看到控件的 accessibility 属性。
  3. 设置建议:设置的 AccessibilityLabel 属性值要有实际意义(用户可理解)因为设置这个属性后用户可以通过 VoiceOver访问;用户不可访问的控件,比如某些放置控件的容器等应该设置为 AccessibilityIdentifier 。
  • KIF 常用操作接口(KIFUITestActor.h里可查阅)
 tapThisView:- (void)tapViewWithAccessibilityLabel:(NSString *)label;
 
 waitForView:- (UIView *)waitForViewWithAccessibilityLabel:(NSString *)label;
//注意:函数返回了对应View的指针,可以对返回值取数据,从而进行一些判断

 enterTextIntoView: - (void)enterText:(NSString *)text intoViewWithAccessibilityLabel:(NSString *)label;
 
 tapRowOnTableView:- (void)tapRowAtIndexPath:(NSIndexPath *)indexPath inTableViewWithAccessibilityIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(5_0); 
 
 dismisses a system alert: - (void)acknowledgeSystemAlert;

扩展:我们还可以对 KIFUITestActor 类进行扩展,利用 KIFUITestActor 中的私有函数,使 AccessibilityIdentifier 代替 Label 识别元素,完成 tapThisView 、waitForView 等操作。

  • KIF测试用例集操作(KIFTestCase.h 中可查阅)
/*!
 * @abstract This method runs once before executing the first test in the class.
 * @discussion This should be used for navigating to the starting point in the app where all tests will start from.  Because this method is not guaranteed to run in the same instance as tests, it should not be used for setting up instance variables but can be used for setting up static variables.
 */
 /*
 在本类中第一个 test case 执行前执行一次,用来执行本类中各个测试函数的公共操作
 //注意:因为不能保证这个方法与 test case 是同一个类实例,所以不能用来设置实例变量的值,但是可以设置静态变量
 */
- (void)beforeAll;

/*!
 * @abstract This method runs before each test.
 * @discussion This should be used for any common tasks required before each test.  Because this method is guaranteed to run in the same instance as tests, it can be used for setting up instance variables.
 */
 //在每个具体 test case 执行前执行一次,用来执行各个函数需要的测试环境
 //注意因为确保这个方法与 test case 是同一个类实例,可以用来设置实例变量

- (void)beforeEach;

/*!
 * @abstract This method runs after each test.
 * @discussion This should be used for restoring the app to the state it was in before the test.  This could include conditional logic to recover from failed tests.
 */
 //在每个具体 test case 执行完之后执行一次,用来清除状态,恢复至 test 之前的状态,可以包含一些条件判断逻辑,从失败的 test case 中恢复,以确保不影响之后的测试
 
- (void)afterEach;

/*!
 * @abstract This method runs once after executing the last test in the class.
 * @discussion This should be used for navigating back to the initial state of the app, where it was before @c beforeAll.  This should also be used for tearing down any static methods created by @c beforeAll.
 
 */
 //执行完本类的最后一个 test case 之后执行一次,用于将 App 恢复至测试的初始状
- (void)afterAll;

/*!
 * @discussion When @c YES, rather than failing the test and advancing on the first failure, KIF will stop executing tests and begin spinning the run loop.  This provides an opportunity for inspecting the state of the app when the failure occurred.
 */
  • 系统的功能实现(KIFSystemTestActor.h中可查阅)
模拟用户旋转设备:- (void)simulateDeviceRotationToOrientation:(UIDeviceOrientation)orientation;
对当前屏幕截图并存储到硬盘中:- (void)captureScreenshotWithDescription:(NSString *)description;

用例组织

  • 设计单个测试用例
    • a.设置测试所需要的环境
    • b.测试用例的具体测试逻辑
    • c.恢复 App 至此次测试前的状态

a,c可用beforeEachalterEach来实现,这样保证了每个用例之间的独立性和稳定性

一般来说,可将用例按功能分成若干个用例集,每个用例集按校验点或者功能点分成若干个用例,这样方便测试用例的管理和维护。某些含有耗费时间多,耗费资源多的公共操作的用例可以集合成一个用例集,在用例集运行前统一执行。

  • 设计用例集
    • 1.设置用例集需要的环境,公共操作
    • 2.设计各个用例
    • 3.恢复 App 至用例集测试的初始状态
      1和3 步骤可以用beforeAllafterAll来实现。下面简单展示一个用例集的书写:
#import "CrazyTests.h"

#import <KIF/KIFUITestActor-IdentifierTests.h>
#import <KIF/KIFUITestActor-ConditionalTests.h>

@implementation CrazyTests
- (void)beforeAll
{
    [self setTestModel];
}

- (void)afterAll
{
    [self resetTestModel];
    [self cleanHistory];
}

- (void)beforeEach
{
    [self setTestModel];
}

- (void)afterEach
{
    [self cleanParams];
}

- (void)testNameTask
{
    [tester enterText:_pp.nickName intoViewWithAccessibilityLabel:@"name"];
    [tester enterText:_pp.realName intoViewWithAccessibilityLabel:@"password"];
    [tester tapViewWithAccessibilityLabel:@"login"];
}

#pragma mark-- setting
- (void)setTestModel
{
    _pp = [Person new];
    _pp.age = 20;
    _pp.nickName = @"crazy";
    _pp.realName = @"hey";
    _pp.cardId = @"123456789";
    
}
- (void)resetTestModel
{
    _pp = nil;
}
- (void)cleanHistory
{
    _pp = nil;
}
- (void)cleanParams
{
    _pp = nil;
}

上述例子,只是简单说明。我们书写用例集应该遵循如下规则:

  1. 将页面上对元素的发现,操作处理抽象为相应的类,返回操作结果
  2. 封装尽可能多的工具类
  3. 测试用例只关注用例逻辑,步骤尽量简洁

我们可以利用 KIF 的私有 api 封装我们的工具类。

用例的独立运行和 retry 机制

失败用例是不可避免的,上述用例的组织方式,降低了用例间的依赖性,但是并不能完全消除失败用例对后续用例执行的影响。如果能让每个用例独立启动 App 执行 case,则能保证后面执行用例不受执行失败用例的影响。如果在 case 运行失败后,还可以进行 retry 重试,能提高用例运行的稳定性。xctool这个工具能给我们带来这样的功能,我们用 xctool 命令先 build-tests 构建 App,然后循环启动 App 来 run-tests用例,用例失败后,重新执行。下面是是一个 xctool 独立运行用例的简单示例:

xctool build-tests -workspace myApp.xcworkspace -scheme myKIFTestScheme -sdk iphonesimulator -configuration Debug -destination platform='iOS Simulator',OS=8.3,name='iPhone 6 Plus'

array=( TimerTests HistoryTests )

for data in ${array[@]}
do 
        xctool  -reporter pretty -reporter junit:tmp/test-report-tmp.xml -workspace myApp.xcworkspace -scheme myKIFTestScheme run-tests -only myKIFTestTarget:${data}  -sdk iphonesimulator -configuration Debug -destination platform='iOS Simulator',OS=8.3,name='iPhone 6 Plus'
done

一般我们测试,团队大了或者分工特别细的话,就需要接入自动化持续集成。后续大家可以自行了解,主要框架有Jenkins Fastlane等。

优化测试用例

当测试用例写多了,我们也会重构我们的测试用例代码。通常,我们应该从几个角度去考虑:

  • 不要测试私有方法(封装是 OOP 的核心思想之一,不要为了测试破坏封装)
  • 对测试用例分组(功能,业务相似)
  • 对单个用例保证测试独立(不受之前测试的影响,不影响之后的测试),这是测试是否准确的核心
  • 提取公共的代码和操作,减少 copy/paste这样的工作,测试用例是上层调用,只关心业务逻辑,不关心内部代码实现

总结

KIF 因为是建立在 XCTest 框架之上的,所以非常适合我们开发者上手,而且利用私有 API,我们可以很方便的测试 UI 层面和单元测试等

参考:

iOS自动化测试的那些干货

基于 KIF 的 iOS UI 自动化测试和持续集成

解放你的双手—iOS自动测试基础

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容

  • 前言 如果有测试大佬发现内容不对,欢迎指正,我会及时修改。 大多数的iOS App(没有持续集成)迭代流程是这样的...
    默默_David阅读 1,644评论 0 4
  • 大多数的iOS App (没有持续集成)迭代流程是这样的: 也就是说,测试是发布之前的最后一道关卡。如果bug不能...
    伯牙呀阅读 4,862评论 1 22
  • 前文:根据Martin Fowler 的测试理论,测试应该遵循如下测试金字塔组合,测试金字塔最底层是单元测试,然后...
    小小小蚍蜉阅读 1,369评论 0 2
  • 0.小目标 关于UI自动化的定义,我想要的是自动地按照流程去点击页面、输入数据,不需要人去参与,节省人工时间。比如...
    孢子菌阅读 15,552评论 10 47
  • 从儿童进学校的第一天起,就要善于看到并不断巩固和发展他们身上所有好的东西。 今天峻峻、恒恒、延延也有自己穿袜子呢,...
    红黄蓝塔塔班2阅读 193评论 0 0