使用Xcode自带的单元测试

今年苹果推出的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(1 + 1, 2, "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>:0:
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(10) { (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, 200, "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": "[email protected]"]]
            }
        }

        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, 1, "fetch request应该只返回一个结构")

        let result = results![0] as [String: String]
        XCTAssertEqual(result["name"]! as String, "张三", "name应该是张三")
        XCTAssertEqual(result["email"]! as String, "[email protected]", "email应该是[email protected]")
    }

结论

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

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

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

欢迎加群互相学习,共同进步。QQ群:58099570 | 做人要厚道,转载请注明出处!

时间: 2024-10-12 09:02:51

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

IOS-使用XCODE自带的单元测试UnitTest

什么是单元测试? 一听到单元测试这个词感觉很高端,其实单元测试就是为你的方法多专门写一个测试函数.以保证你的方法在不停的修改开发中.保持正确.如果出错,第一时间让你知道,这样从最小单位开始监控来保证软件的质量. 什么时候用到单元测试: 1.写完代码以后:想要验证一下自己写的代码是否有问题. 2.写代码之前:就是写代码之前所有的功能分模块的设计好,测试通过了再写.(我反正是没用过). 3.修复某个bug后:一般修复完某个bug,为了确保修复是成功的,会写测试. 怎么写单元测试 好像废话有点多了,还

Xcode自带iOS测试方法

在说Xcode自带测试方法前先讲下程序在内存中的空间划分, 一般可分为5个部分: #1. BSS段, 存放未初始化的全局变量. BSS是英文Block Started by Symbol的简称.BSS段属于静态内存分配. #2. 数据段, 存放已初始化的全局变量和全局静态变量. 数据段属于静态内存分配 #3. 代码段, 存放程序执行代码 #4. 堆      , 存放进程中被动态分配的内存段. #5. 栈      , 存放函数的参数值和局部变量, 由编译器自动分配和释放. 常说的内存管理是对堆

[Xcode 自带svn的使用]

xcode自带svn的使用 1.代码中 某文件后面有 “M” 标记,表示该文件已被修改,需要commit.            (右键该文件 -> source control -> commitselected file...)  2.代码中 某文件后面有 “A” 标记,表示该文件是新添加的,已受SVN管理,需要commit.            (右键该文件 -> source control -> commitselected file...)  3.代码中 某文件后面有

xcode自带git身份不确认以及commit不能提交问题

废话少说直接上图 出现这个问题应该是本地库没有创建或者说是内容没有导入造成. 1方法:首次创建项目时勾选git 2打开终端,把该项目地址输入进去 cd 文件地址(可以通过直接托转文件夹来搞定) 3 ls -a查看目录下的文件.看是否有带.git的文件目录 4如果有,ls -l -a 应该能发现带.git的文件. 5 git init6 git add .7 git commit -m 'Initial commit' 8 终端会报错让你说如身份,就如图中run 下面的内容一样. 9 git co

XCode下的iOS单元测试

XCode 内置了 OCUnit 单元测试框架,但目前最好用的测试框架应该是 GHUnit.通过 GHUnit + OCMock 组合,我们可以在 iOS 下进行较强大的单元测试功能.本文将演示如何在 XCode 4.2 下使用 OCUnit, GHUnit 和 OCMock 进行单元测试. OCUnit 在 XCode 下新建一个 OCUnitProject 工程,选中 Include Unit Tests 选择框, OCUnit 框架则会为我们自动添加 Unit Test 框架代码: XCo

VS2013 单元测试(使用VS2013自带的单元测试)

本文是官方文档的学习笔记,官方文档在这里. 1.打开VS3013,随便建一个解决方案,比如叫:UnitTest,建一个类库项目UnitTest_Bank,该项目中添加一个BankAccount类,这个类及类中的方法就是我们要测试的对象. 2.给UnitTest添加一个测试项目:在解决方案名称上右键=>添加=>新建项目=>VisualC#=>测试=>单元测试项目,项目名称叫UnitTest_BankTest,将UnitTest_Bank添加为UnitTest_BankTest的

Xcode自带Reveal

1.Xcode运行项目 2.运行完成后 3.选择第三个 4.效果如图 5.程序停住了 相当于打了个断点 想继续运行 点击断点调试的下一步. PS:如果不想用系统的话,可以去下载Reveal http://blog.devzeng.com/blog/ios-reveal-integrating.html

Xcode 自带git的使用记录

一: 两种创建.git文件的方式 1.创建Project的时候,如果选择了 Create git repository,就可以看到隐藏的.git 文件. 查看隐藏文件的方法: (1)在终端中输入:defaults write com.apple.finder AppleShowAllFiles TRUE; (2) 接着输入: killall Finder 2.在Xcode中下载Command Line Tools ,这样你在创建 Project的时候,即使没有选择 Create git repo

XCode自带解析SAX解析

1 #import "XMLViewController.h" 2 #import "Student.h" 3 @interface XMLViewController ()<NSXMLParserDelegate> 4 @property (strong,nonatomic) NSMutableArray *stuArray; 5 @property (strong,nonatomic) Student *student; 6 @property (s