今年苹果推出的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. DevExpressXtraReport—制作雷达图

    存储过程: SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO -- ========================================== ...

  2. VMWare Workstation虚拟机网卡工作模式及配置方法

    打开VMware→虚拟机→设置.如下图 一. 桥接模式(Bridge) 可将虚拟系统IP与本地系统设在同一网段,此时虚拟机相当于一台网络中与本机公用一个HUB的独立设备,网络中其他机器与虚拟机器.本地 ...

  3. animation过渡效果

    References: http://developer.android.com/training/animation/index.html http://developer.android.com/ ...

  4. 关于eclipse导工程或移植工程常碰到的错误汇总

      在开发过程中,eclipse是使用得最多的IDE,但由于其开源且免费的性质决定了其不然有很多的BUG,在项目很赶的时期碰到某些很恶的错误很浪费时间,也很让人郁闷,现我总结一下我碰到的错误并总结下对 ...

  5. Java如何使用线程异常?

    在Java编程中,如何使用线程异常? 此示例显示如何在处理线程时处理异常. package com.yiibai; class MyThread extends Thread { public voi ...

  6. Java如何以不同国家的格式显示时间?

    在Java中,如何以不同国家的格式显示时间? 以下示例使用Locale类和DateFormat类来显示不同国家格式的日期. package com.yiibai; import java.text.D ...

  7. Xianfeng轻量级Java中间件平台:流水号管理、组织机构管理

    流水号管理:现实中,经常都会和流水号打交道,至于什么是流水号,简而言之,就是按照特定格式要求产生的一个号码,并且总是按照递增的规则生成的,对于要求比较高的业务,需要流水号是连续的,比如移动营业厅排号小 ...

  8. unity3d 资源打包加密 整理

    资源打包脚本,放到Assets\Editor 文件夹下 using UnityEngine; using System.Collections; using UnityEditor; using Sy ...

  9. memcached能获取所有的key吗

    memcached能获取所有的key吗 Memcache 查看列出所有key方法 Memcached中获取所有的key 特别要注意:memcached保存的值需要序列化,否则是无法保存的,而且是不会报 ...

  10. 8.4 Java 命名规范

    Class: 所有单词的首字母大写, TestJava Method: 第一个单词的首字母小写,之后每个单词的首字母大写, getAddressForMe() Property: 第一个单词的首字母小 ...