我想要熟悉TDD。如何测试异步的URLSession调用?在哪个阶段使用哪个XCAssert更好?
我的第一个想法是创建一个函数,该函数内部有URLSession,并在此函数内部将bool标志设置为true,然后在XCAssertTrue中进行测试。或者另一个想法是在调用包含USLSession代码的函数后同步返回虚假数据。
XCTest
测试异步代码,第二部分则是关于您建议的策略。waitForExpectationsWithTimeout:handler
函数,专门用于这种情况。它允许您等待异步调用完成,然后再检查其结果。import XCTest
@testable import MyApp
class CallbackTest: XCTestCase {
func testAsyncCalback() {
let service = SomeService()
// 1. Define an expectation
let expectation = expectationWithDescription("SomeService does stuff and runs the callback closure")
// 2. Exercise the asynchronous code
service.doSomethingAsync { success in
XCTAssertTrue(success)
// Don't forget to fulfill the expectation in the async callback
expectation.fulfill()
}
// 3. Wait for the expectation to be fulfilled
waitForExpectationsWithTimeout(1) { error in
if let error = error {
XCTFail("waitForExpectationsWithTimeout errored: \(error)")
}
}
}
}
XCAssertTrue
中对其进行测试,这个想法不错。这是一种很好的测试异步回调是否实际被调用的方法。需要记住的唯一一件事是,测试需要等待异步调用运行,否则它将永远失败。let service = SomeService()
var called = false
let expectation = expectationWithDescription(
"The callback passed to SomeService is actually called"
)
service.doSomethingAsync { _ in
called = true
expectation.fulfill()
}
waitForExpectationsWithTimeout(1) { error in
if let error = error {
XCTFail("waitForExpectationsWithTimeout errored: \(error)")
}
XCTAssertTrue(called)
}
与测试GUI类似,我不建议从客户端应用程序代码直接测试后端。问题主要是来自于网络的不一致状态 - 如果例如您的数据库可以更改,那么如何保持稳定的测试呢?如果您的代码没有变化,但您的测试结果有所变化,那就不好了。
相反,您对测试虚假数据是正确的。我建议将网络服务放在某种接口后面,然后根据您想采用的测试策略,在整个测试过程中提供“实现”该接口的测试替身。
与其直接测试应用程序的网络交互,不如使用预定义的领域模型对象(仿佛它们已经从网络JSON/XML转换)测试您的领域层,这样可以更精确地控制测试,这很好。
我建议这样做的另一个原因是为了保护您的时间和理智。维护可预测、精确的测试已经很难了,尤其是当您重构应用程序代码时。在此基础上添加一个可能会发生变化的外部因素是一种风险。
除了@drhr提到的要点,考虑使用门面模式来抽象化网络细节。在构造函数中传入一个特定的实现,包装网络功能,即在实际操作中使用Networking,在测试中使用存根网络代码。
这种类型的单元测试的目的是测试您的代码如何响应来自网络代码的不同响应,您的目标不是在这里测试实际的网络元素(如果需要可以在集成/系统测试中完成)。
我会使用模拟对象来伪造URLSession,进行真正的单元测试并加速开发。http://ocmock.org/是一个很好的工具来实现这一点。如果你仍然想测试URLSession,你可以使用下面的代码,它使用XCT expectations和线程。与其他答案不同,这个测试期望被测试的函数在其中运行URLSession,并设置布尔值(称为myFlag)的完成处理程序。函数本身根本不是异步的,只是由URLSession调用的部分。我们将不得不使用稍微不同的expectations:
func testAFunc(){
let expect = expectation(description: "...")
DispatchQueue.global(qos: .userInitiated).async { // 1
let myObject = MyObject()
myObject.myFuncUnderTestWhichCallURLSession() // the function which calls URLSession
while(true){
if( myObject.myFlag == nil ){
XCTAssertEqual(true, myObject.myFlag)
expect.fulfill()
break;
}
}
}
waitForExpectations(timeout: 5000) { error in
// ...
}
defer { expectation.fullfill() }
,允许在块内使用多个返回语句(guards / etc),同时仅编写一次 fullfill()。 - Abhi Beckert