假设有測试大佬发现内容不正确。欢迎指正,我会及时改动。css
大多数的iOS App(没有持续集成)迭代流程是这种html
也就是说。測试是公布以前的最后一道关卡。假设bug不能在測试中发现,那么bug
就会抵达用户,因此測试的完整性和可靠性十分重要。android
眼下,大多数App还停留在人工測试阶段,人工測试投入的成本最低,可以保证核心功能的使用,并且測试人员不需要会写代码。ios
但是,在很是多測试场景下。人工測试的效率过低,easy出错。git
举两个常见的样例:github
上线了以后,后台配置数据的时候出了点小问题,致使大面积崩溃,boom~。web
而后,老板就要过来找你了sql
本文所解说的均是基于XCode 8.2.1,有些概念可能不适用于低版本号的XCode编程
本身主动化測试就是写一些測试代码。用代码取代人工去完毕模块和业务的測试。swift
事实上不管是开发仍是測试,假设你在不断的作反复性工做的时候。就应该问本身一个问题:是否是有更高效的办法?
本身主动化測试有很是多长处:
固然,本身主动化測试也有一些缺点。
因此。在作本身主动化測试以前,首先要问本身几个问题?
一般,咱们会选择那些业务稳定。需要频繁測试的部分来编写本身主动化測试脚本,其他的採用人工測试。人工測试仍然是iOS App开发中必不可少的一部分。
从是否接触源码的角度来分类:測试分为黑盒和白盒(灰盒就是黑盒白盒结合,这里不作讨论)。
白盒測试的时候,測试人员是可以直接接触待測试App的源码的。
白盒測试不少其它的是单元測试,測试人员针对各个单元进行各类可能的输入分析,而后測试其输出。白盒測试的測试代码一般由iOS开发编写。
黑盒測试。
黑盒測试的时候,測试人员不需要接触源码。
是从App层面对其行为以及UI的正确性进行验证。黑盒測试由iOS測试完毕。
从业务的层次上来讲,測试金字塔如图:
而iOS測试一般仅仅有下面两个层次:
啰里八嗦讲的这么多,本身主动化測试的效率怎么样。关键仍是在測试框架上。
那么,怎样选择測试框架呢?框架可以分为两大类:XCode内置的和三方库。
选择框架的时候有几个方面要考虑
咱们首先来看看XCode内置的框架:XCTest。XCTest又可以分为两部分:Unit Test 和 UI Test,分别相应单元測试和UI測试。有一些三方的測试库也是基于XCTest框架的,这个在后文会讲到。由于是Apple官方提供的。因此这个框架会不断无缺。
成熟的三方框架一般提供了很是多封装好的有好的接口。笔者综合对照了一些,推荐下面框架:
单元測试:
下面三个框架都是BDD(Behavior-driven development) - 行为驱动开发。行为驱动开发简单来讲就是先定义行为。而后定义測试用例,接着再编写代码。 实践中发现。一般没有那么多时间来先定义行为。只是BDD中的domain-specific language (DSL)可以很是好的描写叙述用例的行为。
UI測试
App至关于一个Server,測试代码至关于Client,经过发送JSON来操做APP,測试语言可以是随意的,支持android和iOS。
篇幅有限。本文会先介绍XCtest。接着三方的Unit框架会以Quick为例,UI Test框架側重分析KIF,appium仅仅作原理解说。
对于XCTest来讲。最后生成的是一个bundle。
bundle是不能直接运行的,必须依赖于一个宿主进程。关于XCTest进行单元測试的基础(XCode的使用,异步測试。性能測试,代码覆盖率等)。我在这篇文章里解说过,这里再也不具体解说。
比方,我有下面一个函数:
//验证一段Text是否有效。(不能以空字符开头,不能为空) - (BOOL)validText:(NSString *)text error:(NSError *__autoreleasing *)error{ }
那么。我该怎样为这个函数编写单元測试的代码?一般,需要考虑下面用例:
UI測试是模拟用户操做。进而从业务处层面測试。关于XCTest的UI測试,建议看看WWDC 2015的这个视频:
关于UI測试,有几个核心类需要掌握
UI測试另外一个核心功能是UI Recording。
选中一个UI測试用例,而后点击图中的小红点既可以開始UI Recoding。你会发现:
随着点击模拟器,本身主动合成了測试代码。(一般本身主动合成代码后。还需要手动的去调整)
在写UI測试用例的时候要注意:測试行为而不是測试代码。比方,咱们測试这样一个case
进入Todo首页,点击add,进入加入页面,输入文字,点击save。
測试效果例如如下:
相应測试代码:
- (void)testAddNewItems{
//获取app代理
XCUIApplication *app = [[XCUIApplication alloc] init];
//找到第一个tabeview,就是咱们想要的tableview
XCUIElement * table = [app.tables elementBoundByIndex:0];
//记录下来加入以前的数量
NSInteger oldCount = table.cells.count;
//点击Add
[app.navigationBars[@"ToDo"].buttons[@"Add"] tap];
//找到Textfield
XCUIElement *inputWhatYouWantTodoTextField = app.textFields[@"Input what you want todo"];
//点击Textfield
[inputWhatYouWantTodoTextField tap];
//输入字符
[inputWhatYouWantTodoTextField typeText:@"somethingtodo"];
//点击保存
[app.navigationBars[@"Add"].buttons[@"Save"] tap];
//获取当前的数量
NSInteger newCount = table.cells.count;
//假设cells的数量加一,则以为測试成功
XCTAssert(newCount == oldCount + 1);
}
这里是经过先后tableview的row数量来断言成功或者失败。
一般,在视图切换的时候有转场动画,咱们需要等待动画结束,而后才干继续。不然query的时候很是可能找不到咱们想要的控件。
比方。例如如下代码等待VC转场结束,当query仅仅有一个table的时候,才继续运行兴许的代码。
[self expectationForPredicate:[NSPredicate predicateWithFormat:@"self.count = 1"]
evaluatedWithObject:app.tables
handler:nil];
[self waitForExpectationsWithTimeout:2.0 handler:nil];
//兴许代码....
Tips: 当你的UI结构比較复杂的时候。比方各类嵌套childViewController,使用XCUIElementQuery的代码会很是长。也很差维护。
另外,UI測试还会在每一步操做的时候截图。方便对測试报告进行验证。
使用基于XCTest的框架,可以在XCode的report navigator中查看測试结果。
当中:
除了利用XCode的GUI,还可以经过后文提到的命令行工具来測试。查看结果。
首先解释两个术语:
一般,假设你採用纯存的XCTest。推荐採用OCMock来实现mock和stub,单元測试的三方库一般已集成了stub和mock。
那么,怎样使用mock呢?举个官方的样例:
//mock一个NSUserDefaults对象
id userDefaultsMock = OCMClassMock([NSUserDefaults class]);
//在调用stringForKey的时候,返回http://testurl
OCMStub([userDefaultsMock
stringForKey:@"MyAppURLKey"]).andReturn(@"http://testurl");
再比方,咱们要測试打开其它App,那么怎样推断确实打开了其它App呢?
id app = OCMClassMock([UIApplication class]);
OCMStub([app sharedInstance]).andReturn(app);
OCMVerify([app openURL:url]
使用Stub可以让咱们很是方便的实现这个。
关于OCMock的使用。推荐看看objc.io的这篇文章
Quick是创建在XCTestSuite上的框架,使用XCTestSuite赞成你动态建立測试用例。因此,使用Quick,你仍让可以使用XCode的測试相关GUI和命令行工具。
使用Quick编写的測试用例看起来是这样子的:
import Quick
import Nimble
class TableOfContentsSpec: QuickSpec {
override func spec() {
describe("the 'Documentation' directory") {
it("has everything you need to get started") {
let sections = Directory("Documentation").sections
expect(sections).to(contain("Organized Tests with Quick Examples and Example Groups"))
expect(sections).to(contain("Installing Quick"))
}
context("if it doesn't have what you're looking for") {
it("needs to be updated") {
let you = You(awesome: true)
expect{you.submittedAnIssue}.toEventually(beTruthy())
}
}
}
}
}
BDD的框架让測试用例的目的更加明白,測试是否经过更加清晰。使用Quick,測试用例分为两种:
it有两个參数。
比方,下面測试Dolphin行为。它具备行为is friendly
和is smart
//Swift代码
class DolphinSpec: QuickSpec {
override func spec() {
it("is friendly") {
expect(Dolphin().isFriendly).to(beTruthy())
}
it("is smart") {
expect(Dolphin().isSmart).to(beTruthy())
}
}
}
可以看到。BDD的核心是行为。也就是说,需要关注的是一个类提供哪些行为。
比方。验证dolphin的click行为的时候。咱们需要两个用例。一个是is loud
,一个是has a high frequency
,就可以用describe将用例组织起来。
class DolphinSpec: QuickSpec {
override func spec() {
describe("a dolphin") {
describe("its click") {
it("is loud") {
let click = Dolphin().click()
expect(click.isLoud).to(beTruthy())
}
it("has a high frequency") {
let click = Dolphin().click()
expect(click.hasHighFrequency).to(beTruthy())
}
}
}
}
}
context可以指定用例的条件:
比方
describe("its click") {
context("when the dolphin is not near anything interesting") {
it("is only emitted once") {
expect(dolphin!.click().count).to(equal(1))
}
}
}
除了这些以外,Quick也支持一些切入点。进行測试前的配置:
由于Quick是基于XCTest,开发人员固然可以收使用断言来定义測试用例成功或者失败。Quick提供了一个更有好的Framework来进行这种断言:Nimble
比方,一个常见的XCTest断言例如如下:
XCTAssertTrue(ConditionCode, "FailReason")
在出错的时候,会提示
XCAssertTrue failed, balabala
这时候,开发人员要打个断点。查看下上下文。看看具体失败的缘由在哪。
使用Nimble后,断言变成相似
expect(1 + 1).to(equal(2)) expect(3) > 2 expect("seahorse").to(contain("sea")) expect(["Atlantic", "Pacific"]).toNot(contain("Mississippi"))
并且。出错的时候,提示信息会带着上下文的值信息。让开发人员更easy的找到错误。
測试的准确性和工做量很是大程度上依赖于开发人员的代码质量。
一般。为了单元測试的准确性,咱们在写函数(方法)的时候会借鉴一些函数式编程的思想。
当中最重要的一个思想就是
何为Pure function?就是假设一个函数的输入同样,那么输出必定同样。
比方。这种一个函数就不是pure function。由于它依赖于外部变量value的值。
static NSInteger value = 0;
- (NSInteger)function_1{
value = value + 1;
return value;
}
而这个函数就是pure function,由于给定输入,输出必定一致。
- (NSInteger)function_2:(NSInteger)base{
NSInteger value = base + 1;
return value;
}
因此。假设你写了一个没有參数,或者没有返回值的方法,那么你要当心了。很是可能这种方法很是难測试。
在良好的MVC架构的App中。
因此,对Controller瘦身是iOS架构中比較重要的一环。一些通用的技巧包含:
逻辑抽离:
可以每一个网络请求以Command模式封装成一个对象,不要直接在Controller调用AFNetworking。
比方登陆状态等等,经过服务去訪问。这样服务提供者之需要处理服务的质量,服务使用者则信任服务提供者的结果。
Controller与View解耦合
Controller与Controller解耦合
假设你的App用Swift开发。那么面向协议编程和不可变的值类型会让你的代码更easy測试。
固然,iOS组建化对本身主动化測试的帮助也很是大,由于不管是基础组件仍是业务组件,都可以独立測试。组建化又是一个很是大的课题,这里不深刻解说了。
KIF的全称是Keep it functional。
它是一个创建在XCTest的UI測试框架,经过accessibility来定位具体的控件。再利用私有的API来操做UI。由于是创建在XCTest上的,因此你可以完美的借助XCode的測试相关工具(包含命令行脚本)。
> KIF是我的很是推荐的一个框架,简单易用。
使用KIF框架强制要求你的代码支持accessibility。假设你以前没接触过,可以看看Apple的文档
简单来讲。accessibility可以让视觉障碍人士使用你的App。
每一个控件都有一个描写叙述AccessibilityLabel。
在开启VoiceOver的时候,点击控件就可以选中并且听到相应的描写叙述。
一般UIKit的控件是支持accessibility的。自定定义控件可以经过代码或者Storyboard上设置。
在Storyboard上设置:
经过代码设置:
[alert setAccessibilityLabel:@"Label"];
[alert setAccessibilityValue:@"Value"];
[alert setAccessibilityTraits:UIAccessibilityTraitButton];
假设你有些Accessibility的经验。那么你确定知道。像TableView的这种不该该支持VoiceOver的。咱们可以用条件编译来仅仅对測试Target进行设置:
#ifdef DEBUG
[tableView setAccessibilityValue:@"Main List Table"];
#endif
#ifdef KIF_TARGET (这个值需要在build settings里设置)
[tableView setAccessibilityValue:@"Main List Table"];
#endif
使用KIF主要有两个核心类:
咱们用KIF来測试加入一个新的ToDo
- (void)testAddANewItem{
[tester tapViewWithAccessibilityLabel:@"Add"];
[tester enterText:@"Create a test to do item" intoViewWithAccessibilityLabel:@"Input what you want todo"]; [tester tapViewWithAccessibilityLabel:@"Save"]; [tester waitForTimeInterval:0.2]; [tester waitForViewWithAccessibilityLabel:@"Create a test to do item"]; }
本身主动化測试中,命令行工具可以facebook的开源项目:
这是一个基于xcodebuild
命令的扩展,在iOS本身主动化測试和持续集成领域很是实用,并且它支持-parallelize
并行測试多个bundle,大大提升測试效率。
安装XCTool,
brew install xctool
使用
path/to/xctool.sh \
-workspace YourWorkspace.xcworkspace \
-scheme YourScheme \
-reporter plain:/path/to/plain-output.txt \
run-test
并且。xctool对于持续集成很是实用,iOS常用的持续集成的server有两个:
一般,你的你的測试用例分为三部分:
当測试用例多了。你会发现測试代码编写和维护也是一个技术活。
一般。咱们会从几个角度考虑:
一个常见的測试代码组织例如如下:
appium採用了Client Server的模式。对于App来讲就是一个Server,基于WebDriver JSON wire protocol对实际的UI操做库进行了封装,并且暴露出RESTFUL的接口。
而后測试代码经过HTTP请求的方式,来进行实际的測试。
当中,实际驱动UI的框架依据系统版本号有所不一样:
缘由也比較简单:Apple在10.0以后。移除了UIAutomation的支持,仅仅支持XCUITest。
对照KIF。appium有它的长处:
固然,不论什么框架都有缺点:
由于我不是专业的iOS測试,关于測试的一点看法例如如下:
声明:本博客所有文章均为我的观点。与雇主没有不论什么关系。