异步单元测试

在Xcode 6之前的版本里面并没有内置XCTest,只能是在主线程的RunLoop里面使用一个while循环,然后一直等待响应或者直到timeout.

在Xcode 6里,苹果以XCTestExpection类的方式向XCTest框架里添加了测试期望(test expection)。 XCTest框架中相关的方法:设置期望,实现期望,通知期望,谓词计算期望等,以及期望的传递。

常规实现方式

在主线程里,使用while循环每隔10毫秒会执行一次,直到有响应或者5秒之后超出响应时间限制才会跳出:

{% codeblock lang:swift %} func testAsyncTheOldWay() { let timeoutDate = Date.init(timeIntervalSinceNow: 5.0) var responseHasArrived = false Alamofire.request(“https://www.baidu.com”).responseData{response in print(“获取到的数据长度:(String(data: response.data!, encoding:String.Encoding.utf8)!)”) responseHasArrived = true XCTAssert((response.data?.count)! > 0) }

while (responseHasArrived == false 
        && (timeoutDate.timeIntervalSinceNow > 0)) 
{
    CFRunLoopRunInMode(CFRunLoopMode.defaultMode, 0.01, false)
}

if (responseHasArrived == false)
{
    XCTFail("Test timed out")
}

} {% endcodeblock %}

XCTest相关API

在Xcode 6里,苹果以XCTestExpection类的方式向XCTest框架里添加了测试期望(test expection)。

XCTestExpection:高期望(High Expectations)的实现和使用

设置期望,实现期望,通知期望,谓词计算期望等,以及期望的传递。

expectation(description:):为XCTest测试单元设置期望

为XCTest单元测试,设置一个测试期望以及错误信息描述,并在某一时刻fulfill实现该期望对象 {% codeblock lang:swift %} //expectation(description: String) -> XCTestExpectation let expection = expectation(description: “失败时显示原因”) {% endcodeblock %}

fulfill():调用它表示测试达到期望值

一般在单元测试通过时调用,告知测试已达期望,这一方法替代了responseHasArrived作为Flag的方式 {% codeblock lang:swift %} //- (void)fulfill; expection.fulfill() {% endcodeblock %}

waitForExpectations(timeout:handler:):在方法底部设置测试期望的时效

在方法底部指定一个超时,如果测试条件不适合时间范围便会结束执行 {% codeblock lang:swift %} // open func waitForExpectations(timeout: TimeInterval, handler: XCTest.XCWaitCompletionHandler? = nil) waitForExpectations(timeout: 5) { error in print(“错误信息:(error?.localizedDescription)”) } {% endcodeblock %} 如果完成处理的代码在指定时限里执行并调用了fulfill()方法,那么就说明所有的测试期望在此期间都已经被实现。否则就测试就被打断不再执行

expectation(forNotification:object:handler:):通知期望

该方法监听一个通知,如果在规定时间内正确收到通知则测试通过 {% codeblock lang:swift %} //expectation(forNotification notificationName: String, object objectToObserve: Any?, handler: XCTest.XCNotificationExpectationHandler? = nil) -> XCTestExpectation //设置一个测试通知期望 expectation(forNotification: “BLDownloadImageNotification”, object: nil) {(notification) -> Bool in let userInfo = notification.userInfo as! [String:String] let name = userInfo[“name”] print(“name:(name)”) return true } 来定义一个通知并发送通知,来测试: let notif = Notification.Name(rawValue: “BLDownloadImageNotification”) NotificationCenter.default.post(name: notif, object: self, userInfo: [“name”:“iTBoyer”,“sex”:“man”])

//设置延迟多少秒后,如果没有满足测试条件就报错 waitForExpectations(timeout: 3, handler: nil) {% endcodeblock %}

使用expectation(description:)实现

帮助理解 expectation(forNotification:object:handler:) 方法和 expectation(description:) 的区别 {% codeblock lang:swift %} func testAsynForNotificationWithExpectation() { let expectation = self.expectation(description: “BLDownloadImageNotification”) let notif = NSNotification.Name(rawValue: “BLDownloadImageNotification”) let sub = NotificationCenter.default.addObserver(forName: notif, object: nil, queue: nil) { (notification) -> Void in expectation.fulfill() } //发送一个通知 NotificationCenter.default.post(name: notif, object: nil)

//waitForExpectations
waitForExpectations(timeout: 1, handler: nil)

//移除通知
NotificationCenter.default.removeObserver(sub)

}

{% endcodeblock %}

expectation(for:evaluatedWith:handler:):谓词计算测试法

利用谓词计算,判断buttonbackgroundImageForState方法,是否正确的获得了backgroundImage,如果20秒内正确获得则通过测试,否则失败 {% codeblock lang:swift %} //open func expectation(for predicate: NSPredicate, evaluatedWith object: Any, handler: XCTest.XCPredicateExpectationHandler? = nil) -> XCTestExpectation func testThatBackgroundImageChanges() { let viewController = OnclickLikeViewController() //viewController.loadView() //不执行viewDidload方法 let _ = viewController.view let button = viewController.button let img = button.backgroundImage(for: .normal) XCTAssertNil(img,“此时img不为nil,中止执行”) //当img不是nil时,执行断言 let predicate = NSPredicate.init { (anyobject, bindings) -> Bool in // let button = anyobject as! UIButton return button.backgroundImage(for: UIControlState()) != nil } expectation(for: predicate, evaluatedWith: button, handler: nil) waitForExpectations(timeout: 20, handler: nil) } {% endcodeblock %}

使用expectation(description:)实现

帮助理解 expectation(for:evaluatedWith:handler:) 方法和 expectation(description:) 的区别 {% codeblock lang:swift %} func testThatBackgroundImageChanges() { //设置期望 let expectation = self.expectation(description: “backgroundImageForState”)

let viewController = OnclickLikeViewController()
//viewController.loadView()  //不执行viewDidload方法
let _ = viewController.view
let button = viewController.button
let img = button.backgroundImage(for: .normal)
XCTAssertNil(img,"此时img不为nil,中止执行")  //当img不是nil时,执行断言
let predicate = NSPredicate.init { (anyobject, bindings) -> Bool in
    //
    let button = anyobject as! UIButton
    return button.backgroundImage(for: UIControlState()) != nil
    //实现测试期望
    expectation.fulfill()
}

//等待期望实现
waitForExpectations(timeout: 20, handler: nil)

}

{% endcodeblock %}

传递expectation在目的方法中再fulfill()实现期望

例如将期望封装在字典中,通过通知来传递给异步下载的方法中调用该期望的fulfill()方法,实现单元测试的期望 {% codeblock lang:swift %} */ func testAsynForNotificationWithExpectation2() { let expectation = self.expectation(description: “BLDownloadImageNotification”)

let notif = Notification.Name(rawValue: "BLDownloadImageNotification")
NotificationCenter.default.addObserver(self, selector: #selector(AsyncTheOldWayTest.downLoadImage(_:)), name: notif, object: nil)

    //将期望封装在字典中传递
    let userInf = ["name":"iTBoyer","sex":"man","expectation":expectation]
    NotificationCenter.default.post(name: notif, object: self, userInfo: userInf)
    //等待期望实现
    waitForExpectations(timeout: 1, handler: nil)
    NotificationCenter.default.removeObserver(self)
}

//
func downLoadImage(_ notification:Notification) 
{
    //
    let userInfo = notification.userInfo as! [String:AnyObject]
    let name = userInfo["name"]
    let sex = userInfo["sex"]
    print("name:\(name), sex = \(sex)")

    let expectation = userInfo["expectation"] as! XCTestExpectation
    expectation.fulfill()
}

} {% endcodeblock %}