今年苹果推出的iOS8和Swift的新功能让人兴奋。同时,苹果对于Xcode的测试工具的改进却也会影响深远。现在我们来看下XCTest,Xcode内置的测试框架。以及,Xcode6新增的XCTestExpectation和性能测试。

现在Xcode项目已经支持out-of-the-box的测试。比如,创建一个新的iOS应用项目后,项目会自动配置两个顶层的group:一个是“应用名称”的group,一个是“项目名称Test”group。对应于这两个顶层的group的是两个target。一个运行的target,一个测试的target。项目自动生成的scheme也允许用户用Command+R运行应用,Command+U编译运行测试的target。

在测试的target中,默认的情况下只有一个叫做“应用名称Test”的文件。这个文件里面会包含一个XCTextCase类,里面有对setUp方法和tearDown方法的调用,还有一个示例的测试方法和性能测试的test case。

XCTestCase

Xcode的单元测试包含在XCTestCase子类中。组织测试的时候需要尽量考虑实际的应用操作流程。

setUp & tearDown

setUp方法在XCTestCase的测试方法调用之前调用。当测试全部结束之后调用tearDown方法。

class MVVMTests: XCTestCase {
override func setUp() {
super.setUp()
} override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}
}

setUp方法可以在测试之前创建在test case方法中需要用到的一些对象等。tearDown方法则在全部的test case执行结束之后清理测试现场,释放资源删除不用的对象等。所以,setUp方法一般都是这么用的:

    var calendar: NSCalendar?
var locale: NSLocale? override func setUp() {
super.setUp() self.calendar = NSCalendar(identifier: NSGregorianCalendar)
self.locale = NSLocale(localeIdentifier: "en_US")
}

XCTestCase的初始化不是用户控制的,所以属性在setUp方法中初始化的属性只能被定义为optonal的。不定义成optional的话,就只能在定义属性的时候直接给出初始化。如:

var calendar: NSCalendar = NSCalendar(identifier: NSGregorianCalendar)
var locale: NSLocale = NSLocale(localeIdentifier: "en_US")

功能测试

test case中的每一个方法都是test开头,这样容易辨识。方法中会执行断言(assertion),来判断这个测试是否通过。

    func testExample() {
XCTAssertEqual( + , , "one plus one equals two")
}

常用的XCTest断言

XCTest会用到很多的断言,很多,但是只有一部分是常用到的。这里一一列出:

基本测试

所有的断言都是从最基本的这个断言演化出来的:

XCTAssert(expression, format...)

如果expression(表达式)执行的结果为true的话,这个测试通过。否则,测试失败,并在console中输出后面的format字符串。

后面基于XCTAssert演化出来的断言,不仅可以满足测试的需求而且可以更好更明确的表达出你要测试的是什么。最好是使用这些演化出来的断言,XCTestAssert不是必须最好不要用。

Bool测试

对于bool型的数据,或者只是简单的bool型的表达式,使用XCTestAssertTrue或者XCTestAssertFalse

XCTAssertTrue(expression, format...)
XCTAssertFalse(expression, format...)

相等测试

测试两个值是否相等使用XCTAssert[Not]Equal:

XCTAssertEqual(expression1, expression2, format...)
XCTAssertNotEqual(expression1, expression2, format...)

XCTAssertGreaterThan[OrEqual] & XCTAssertLessThan[OrEqual], 和下面的条件操作符比较的是一个意思 == with >, >=, <, 以及 <=

在Double、Float型数据的对比中使用XCTAssert[Not]EqualWithAccuracy来处理浮点精度的问题:

XCTAssertEqualWithAccuracy(expression1, expression2, accuracy, format...)
XCTAssertNotEqualWithAccuracy(expression1, expression2, accuracy, format...)

Nil测试

使用XCTAssert[Not]Nil断言判断给定的表达式值是否为nil:

XCTAssertNil(expression, format...)
XCTAssertNotNil(expression, format...)

无条件失败断言

最后,XCTFail提供的是无条件断言:

XCTFail(format...)

XCTFail,无条件的都是测试失败。这个东东有什么用处呢。在测试驱动里有这么个情况,你定义了测试方法,但是没有给出具体的实现。那么你不会希望这个测试能通过的。是的,XCTFail就是这么个用途。一般被用作一个占位断言。等你的测试方法完善好了之后再换成最贴近你的测试的断言。有或者,在某些情况下else了之后就是不应该出现的情况。那么这个时候可以把XCTFail放在这个else里面。

性能测试

在Xcode6中新增的测试代码性能的功能:

    func testPerformanceExample() {
let dateFormatter = NSDateFormatter()
dateFormatter.dateStyle = .LongStyle
dateFormatter.timeStyle = .ShortStyle let date = NSDate() self.measureBlock() {
let string = dateFormatter.stringFromDate(date)
}
}

测试结果:

Test Case '-[MVVMTests.MVVMTests testPerformanceExample]' started.
<unknown>::
Test Case '-[MVVMTests.MVVMTests testPerformanceExample]' measured [Time, seconds] average: 0.000, relative standard deviation: 257.209%, values: [0.000390, 0.000010, 0.000007, 0.000006, 0.000006, 0.000006, 0.000006, 0.000006, 0.000006, 0.000006], performanceMetricID:com.apple.XCTPerformanceMetric_WallClockTime, baselineName: "", baselineAverage: , maxPercentRegression: 10.000%, maxPercentRelativeStandardDeviation: 10.000%, maxRegression: 0.100, maxStandardDeviation: 0.100
Test Case '-[MVVMTests.MVVMTests testPerformanceExample]' passed (0.278 seconds).

性能测试可以帮助开发者建立一个主要功能的基本性能基线。确保这些主要的功能代码和算法能在这个性能基线内完成。

XCTestExpectation

Xcode单元测试中加入的最令人兴奋的功能也许就是类XCTestExpression类带入的异步测试了。现在测试可以等待指定长度的时间,一直到某些条件符合的时候在开始测试。而不用再写很多的GCD代码控制。

要使用异步测试,首先用方法expectationWithDescription创建一个expection。

let expectation = expectationWithDescription("...")

之后,在方法的最后添加方法waitForExpectationsWithTimeout,指定等待超时的时间和指定时间内条件无法满足时执行的closure。

waitForExpectationsWithTimeout() { (error) in
// ...
}

剩下的就是在异步测试剩下的回调函数中告诉expectation条件已经满足。

expectation.fulfill()

如果在测试中有多个expectation,则每个expectation都必须fulfill,否则测试不通过。

这里是一个测试异步网络访问的示例:

func testAsynchronousURLConnection(){
let URL = NSURL(string: "http://www.baidu.com")!
let expectation = expectationWithDescription("GET \(URL)") let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(URL, completionHandler: {(data, response, error) in
expectation.fulfill() // 告诉expectation满足测试了 XCTAssertNotNil(data, "返回数据不应该为空")
XCTAssertNil(error, "error应该为nil") if response != nil {
var httpResponse: NSHTTPURLResponse = response as NSHTTPURLResponse
XCTAssertEqual(httpResponse.URL!.absoluteString!, URL, "HTTPResponse的URL应该和请求URL一致")
XCTAssertEqual(httpResponse.statusCode, , "HTTPResponse状态码应该是200")
XCTAssertEqual(httpResponse.MIMEType as String, "text/html", "HTTPResponse内容应该是text/html")
}
else{
XCTFail("返回内容不是NSHTTPURLResponse类型")
}
}) task.resume() waitForExpectationsWithTimeout(task.originalRequest.timeoutInterval, handler: {error in
task.cancel()
})
}

使用Mock

有了异步测试的支持,Xcode快要把满足测试驱动开发的龙珠已经集齐了。但是,还差一个Mock

Mock是一个很有用的东西。使用这个技术可以有效的分离那些不利于测试的因素,比如:过得复杂、不确定、性能约束等。比如遇到交互的网络交互、高强度的数据库查询或者某些存在资源竞争的状态等。

有很多的开源库支持Mock和Stub。但是这些库都严重的依赖于Objective-C运行时,所以在Swift下某些功能无法使用。在Swift下,类可以定义在一个类的方法中。这一特点允许mock自包含(self-contain)的对象。只要定义一个mock类,然后override必要的方法:

    func testFetchRequestWithMockedManagedObjectContext() {
class MockNSManagedObjectContext: NSManagedObjectContext {
private override func executeFetchRequest(request: NSFetchRequest, error: NSErrorPointer) -> [AnyObject]? {
return [["name": "张三", "email": "zhangsan@apple.com"]]
}
} let mockContext = MockNSManagedObjectContext()
let fetchRequest = NSFetchRequest(entityName: "User")
fetchRequest.predicate = NSPredicate(format: "email ENDSWITH[cd] %@", "apple.com")
fetchRequest.resultType = NSFetchRequestResultType.DictionaryResultType var error: NSError?
let results = mockContext.executeFetchRequest(fetchRequest, error: &error) XCTAssertNil(error, "error应该为nil")
XCTAssertEqual(results!.count, , "fetch request应该只返回一个结构") let result = results![] as [String: String]
XCTAssertEqual(result["name"]! as String, "张三", "name应该是张三")
XCTAssertEqual(result["email"]! as String, "zhangsan@apple.com", "email应该是zhangsan@apple.com")
}

结论

Xcode6的内置工具终于足够的好了。也就是说即使是很大的APP也没有必要为了单元测试的代码覆盖率而排斥Xcode内置的测试工具。无论什么样的测试,XCTest的各种断言、expectation和性能测试都足够应对。但是无论多好的工具,都需要用好才行。

如果你在测试iOS或者OS X的APP,开始为自动添加的测试类添加一些断言并按下Command+U。你一定会发现感觉这些工具让你的测试方便不少!

示例代码在这里

参考文章:http://nshipster.com/xctestcase/

使用Xcode自带的单元测试的更多相关文章

  1. 关于使用Xcode自带的单元测试UnitTest的介绍

    什么是单元测试? 单元测试就是为你的方法专门多写一个测试函数.以保证你的方法在不停的修改开发中.保持正确.如果出错,第一时间让你知道,这样从最小单位开始监控来保证软件的质量. 什么时候用到单元测试: ...

  2. [Xcode 自带svn的使用]

    xcode自带svn的使用 1.代码中 某文件后面有 “M” 标记,表示该文件已被修改,需要commit.            (右键该文件 -> source control -> c ...

  3. Xcode自带iOS测试方法

    在说Xcode自带测试方法前先讲下程序在内存中的空间划分, 一般可分为5个部分: #1. BSS段, 存放未初始化的全局变量. BSS是英文Block Started by Symbol的简称.BSS ...

  4. iOS-使用Xcode自带单元测试UnitTest

    ![Uploading QQ20160129-3_262826.png . . .]####什么是单元测试?一听到单元测试这个词感觉很高端,其实单元测试就是为你的方法多专门写一个测试函数.以保证你的方 ...

  5. Xcode 5 下的单元测试

    新版Xcode 5和Server发布以后,apple对单元测试的支持是越来越好了.从这一点看出apple对单元测试的也是越来越重视了. 这篇Blog就简单的介绍这集成化测试功能. Server更新后是 ...

  6. [iOS翻译]《iOS7 by Tutorials》系列:在Xcode 5里使用单元测试(下)

    4.测试失败的调试 是时候追踪之前测试失败的问题了.打开GameBoard.m,找到cellStateAtColumn:andRow: 和 setCellState:forColumn:andRow: ...

  7. [iOS翻译]《iOS7 by Tutorials》系列:在Xcode 5里使用单元测试(上)

    简介: 单元测试是软件开发的一个重要方面.毕竟,单元测试可以帮你找到bug和崩溃原因,而程序崩溃是Apple在审查时拒绝app上架的首要原因. 单元测试不是万能的,但Apple把它作为开发工具包的一部 ...

  8. XCode下的iOS单元测试

    XCode 内置了 OCUnit 单元测试框架,但目前最好用的测试框架应该是 GHUnit.通过 GHUnit + OCMock 组合,我们可以在 iOS 下进行较强大的单元测试功能.本文将演示如何在 ...

  9. 手机抓包xcode自带命令行工具配合wireshark实现

    三.最佳方式:rvictl命令 优点:简单,而且可以抓所有网络接口的数据: 缺点:似乎没有,要求手机iOS5以上不算要求吧?如果说缺点,就是这个命令是Xcode的Command Line Tools ...

随机推荐

  1. Android studio 配置file encoding 无效,中文乱码解决办法

    通过配置Android studio 配置file encoding 无效,中文乱码,问题出现在java编译的时候jack采用了默认编码(中文windows默认的GBK编码)而乱码,所以不管更改bui ...

  2. 关于源码输出,浏览器不解析Html标签

    有时候根据需要我们需要看到浏览器上源码效果如: 但是我如果在html中输入 <a href = 'http://www.baidu.com'>百度</a>那么问题来了,总是显示 ...

  3. 【转】]Android实现开机自动运行程序

    有些时候,应用需要在开机时就自动运行,例如某个自动从网上更新内容的后台service.怎样实现开机自动运行的应用?在撰写本文时,联想到高焕堂先生以“Don't call me, I'll call y ...

  4. oauth2.0服务端与客户端搭建

    oauth2.0服务端与客户端搭建 - 推酷 今天搭建了oauth2.0服务端与客户端.把搭建的过程记录一下.具体实现的功能是:client.ruanwenwu.cn的用户能够通过 server.ru ...

  5. IDEA中maven项目导jar包太慢

    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/PROGRAM_anywhere/article/details/53842058参考了网上的一些教程 ...

  6. Java获取正在执行的函数名

    利用StackTrace堆栈轨迹获取某个时间的调用堆栈状态. package com.dsp.demo; public class TechDemo { public static void main ...

  7. Java对象序列化与RMI

    对于一个存在于Java虚拟机中的对象来说,其内部的状态只保持在内存中.JVM停止之后,这些状态就丢失了.在很多情况下,对象的内部状态是需要被持久化下来的.提到持久化,最直接的做法是保存到文件系统或是数 ...

  8. XML中二进制数据的处理方法

    原文链接:http://www.west263.com/www/info/22308-1.htm 在xml中,所有的数据都是以文本的形式来显示,但是二进制数据不能直接以文本格式来表示,那xml又是怎么 ...

  9. 每天一个linux命令:traceroute命令

    通过traceroute我们可以知道信息从你的计算机到互联网另一端的主机是走的什么路径.当然每次数据包由某一同样的出发点(source)到达某一同样的目的地(destination)走的路径可能会不一 ...

  10. OpenGL 遮挡查询

    原文地址:http://www.linuxidc.com/Linux/2015-02/114036.htm 在一个场景中,如果有有些物体被其他物体遮住了不可见.那么我们就不需要绘制它.在复杂的场景中, ...