调用异步方法的方法的单元测试

时间:2017-07-17 03:37:39

标签: ios objective-c swift unit-testing testing

假设我有这些代码行:

func reset() {
    initializeAnythingElse() {
        // AnythingElse
    }

    initializeHomeData() {
        // HomeData
    }
}

func initializeHomeData(callback: @escaping (()-> Void)) {
    getHomeConfig() {
        callback()
    }
}

func initializeAnythingElse(callback: @escaping (()-> Void)) {
    getAnythingElse() {
        callback()
    }
}

我想为该代码编写单元测试。对于initializeHomeDatainitializeAnythingElse,我可以编写单元测试,如:

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()
}

但我认为这不是适当的实现。

3 个答案:

答案 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。步骤 -

  1. 创建XCTestExpectation

  2. 的实例
  3. 调用异步方法

  4. 致电waitForExpectations(timeout: timeout, handler: handler)

  5. 步骤3提供异步调用完成的等待时间。每当调用异步方法完成时,请在.fulfill()

  6. 的实例上调用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)
    }
    

    跳这有帮助。