假设我有这些代码行:
func reset() {
initializeAnythingElse() {
// AnythingElse
}
initializeHomeData() {
// HomeData
}
}
func initializeHomeData(callback: @escaping (()-> Void)) {
getHomeConfig() {
callback()
}
}
func initializeAnythingElse(callback: @escaping (()-> Void)) {
getAnythingElse() {
callback()
}
}
我想为该代码编写单元测试。对于initializeHomeData
和initializeAnythingElse
,我可以编写单元测试,如:
func testInitializeHomeData() {
let successExpectation = expectation(description: "")
sut.initializeHomeData {
successExpectation.fulfill()
}
waitForExpectations(timeout: 1.0, handler: nil)
// Validation goes here
}
func testInitializeAnythingElse() {
let successExpectation = expectation(description: "")
sut.initializeAnythingElse {
successExpectation.fulfill()
}
waitForExpectations(timeout: 1.0, handler: nil)
// Validation goes here
}
我的问题是,如何测试reset()
?我应该在testReset()
内调用它们,如:
func testReset() {
testInitializeHomeData()
testInitializeAnythingElse()
}
但我认为这不是适当的实现。
答案 0 :(得分:10)
你是对的。要测试reset
,您需要调用reset
,而不是内部方法。
话虽如此,reset
目前的编写方式使其不可测试。您能够如此轻松地测试其他独立方法的原因是因为callback
参数都接受了。
我建议你重写reset以允许两个可选的回调,如下所示:
typealias Callback = () -> ()
func reset(
homeDataCallback: @escaping Callback? = nil,
anythingElseCallback: @escaping Callback? = nil) {
initializeAnythingElse() {
anythingElseCallback?()
}
initializeHomeData() {
homeDataCallback?()
}
}
请注意,此更改允许您在异步时通知这两个内部调用完成。
现在,您的测试方法需要在编写时考虑某种同步原语,因为从逻辑上讲,只有当主数据和其他任何内容都完成并且调用了它们的回调时,才能完成重置。
有很多方法可以实现这一目标,但我将向您展示一种信号量方法:
func testReset() {
let expectation = expectation(description: "reset() completes within some duration")
// some mechanism to synchronize concurrent tasks
// I am using a semaphore
let s = DispatchSemaphore(value: 0)
let homeCallback: Callback = {
s.signal() // signals the completion of home data init
}
let anythingElseCallback: Callback = {
s.signal() // signals the completions of anything else setup
}
// call your reset method as part of the test
reset(homeDataCallback: homeCallback, anythingElseCallback: anythingElseCallback)
// we know we need to wait for two things to complete
// init home data and anything else, so do that
s.wait()
s.wait()
// at this step, reset's internal async methods
// have completed so we can now
// fulfill the expectation
expectation.fulfill()
}
注意,所有这些更改都是纯粹允许您测试reset
来电所必需的。您的函数签名允许您在现有代码中将reset()
写为当前代码,因为它具有可选参数,默认值都设置为nil。
答案 1 :(得分:0)
将回调添加到reset
方法:
func reset(callback: @escaping (()-> Void)) {
var callbacksCount = 0
func tryCallback() {
if callbacksCount == 2 {
callback()
}
}
initializeAnythingElse() {
callbacksCount += 1
}
initializeHomeData() {
callbacksCount += 1
}
}
以与其他方法相同的方式测试它。
你也可以进行顺序调用,并行执行似乎不是一个值得更复杂代码的优化:
func reset(callback: @escaping (()-> Void)) {
initializeAnythingElse() {
initializeHomeData() {
callback()
}
}
}
答案 2 :(得分:0)
继续检查XCTestExpectation
。
这是异步测试的推荐api。步骤 -
创建XCTestExpectation
调用异步方法
致电waitForExpectations(timeout: timeout, handler: handler)
步骤3提供异步调用完成的等待时间。每当调用异步方法完成时,请在.fulfill()
XCTestExpectation
醇>
供您参考 -
XCTest and asynchronous testing in Xcode 6
粘贴上面的代码,以防链接在以后停止工作 -
func testFetchNews() {
let expectation = self.expectationWithDescription("fetch posts")
Post.fetch(.Top, completion: {(posts: [Post]!, error: Fetcher.ResponseError!) in
XCTAssert(true, "Pass")
expectation.fulfill()
})
self.waitForExpectationsWithTimeout(5.0, handler: nil)
}
跳这有帮助。