使用Xcode自带的单元测试

原文:http://www.cnblogs.com/sunshine-anycall/p/4155649.html

今年苹果推出的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)

     }

}

测试结果:

TestCase'-[MVVMTests.MVVMTests testPerformanceExample]'started.:0:TestCase'-[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.100TestCase'-[MVVMTests.MVVMTests testPerformanceExample]'passed(0.278seconds).

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

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)

        inexpectation.fulfill()// 告诉expectation满足测试了XCTAssertNotNil(data,"返回数据不应该为空")

       XCTAssertNil(error,"error应该为nil")

     if response!=nil {

                var httpResponse:NSHTTPURLResponse=responseasNSHTTPURLResponse

               XCTAssertEqual(httpResponse.URL!.absoluteString!,URL,"HTTPResponse的URL应该和请求URL一致")

              XCTAssertEqual(httpResponse.statusCode,200,"HTTPResponse状态码应该是200")

              XCTAssertEqual(httpResponse.MIMETypeasString,"text/html","HTTPResponse内容应该是text/html")

     }else{

              XCTFail("返回内容不是NSHTTPURLResponse类型")

    }

 })

task.resume()

 waitForExpectationsWithTimeout(task.originalRequest.timeoutInterval,handler:{

           errorintask.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()letfetchRequest=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"]!asString,"张三","name应该是张三")

  XCTAssertEqual(result["email"]!asString,"zhangsan@apple.com","email应该是zhangsan@apple.com")

}

结论

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

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

示例代码在这里

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

推荐阅读更多精彩内容