【问题标题】:Unit test for method that call Async methods调用异步方法的方法的单元测试
【发布时间】:2017-12-21 11:34:45
【问题描述】:

假设我有这些代码行:

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

但我认为这不是正确的实现方式。

【问题讨论】:

  • 这么重要的问题!。经过长时间的搜索找到了这个

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


【解决方案1】:

你是对的。要测试reset,您需要调用reset,而不是它的内部方法。

话虽如此,reset 目前的编写方式使其无法测试。您能够如此轻松地测试其他独立方法的原因是因为 callback 参数都接受。

我建议你重写 reset 以允许两个可选的回调,如下所示:

typealias Callback = () -> ()

func reset(
    homeDataCallback: @escaping Callback? = nil,
    anythingElseCallback: @escaping Callback? = nil) {
    initializeAnythingElse() {
       anythingElseCallback?()
    }
    initializeHomeData() {
       homeDataCallback?()
    }
}

请注意,此更改可让您在这两个内部调用完成时收到异步通知。

现在,您的测试方法需要在编写时考虑到某种同步原语,因为从逻辑上讲,只有当家庭数据和其他任何事情都完成并调用它们的回调时,reset 才完成。

有很多方法可以实现这一点,但我将向您展示一种使用信号量的方法:

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。

【讨论】:

  • 嘿@Benzi,感谢您的回答。但是对于那个双s.wait()的行,还有其他写法吗?如果我确实有超过 2 个回调的事情呢?
  • 使用信号量路由,如果有n回调,需要调用wait()n次。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    继续检查XCTestExpectation

    这是推荐用于异步测试的 api。步骤 -

    1. 创建XCTestExpectation的实例

    2. 调用你的异步方法

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

    4. 第 3 步提供了异步调用完成的等待时间。每当您的异步方法完成被调用时,请在 XCTestExpectation 的实例上调用 .fulfill()

    供您参考-

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

    这有帮助。

    【讨论】:

    • 请注意,您在任何时候都只能有一个未完成的期望,所以请注意您的嵌套。
    猜你喜欢
    • 2020-07-14
    • 2011-11-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-19
    • 1970-01-01
    相关资源
    最近更新 更多