XCTest - 无法理解/实现单元测试中的期望(测试 aysnc 代码)

XCTest - Unable to understand / implement expectations in unit tests (to test aysnc code)

(注意 - 我正在为 macOS 开发,所以请......iOS-具体的建议对我没有帮助)

我正在尝试做的事情: 我有一个在后台线程上执行短任务的应用程序组件,然后,如果满足某些条件,则在主线程上异步发送通知。

注意 - 我 没有在我的应用程序代码中使用 NSNotification。我正在使用自己的自定义通知机制。所以,任何与 NSNotification 相关的解决方案都不适用于我。

我正在为上述应用程序组件编写单元测试,只是想检查该通知是否确实已发送。在执行断言之前,我的测试必须能够等待一秒钟左右,以便通知时间达到其 subscriber/observer。

我希望能够在我的测试中测试两种可能的情况:两者都是正常情况。

在阅读了几个文档和代码示例数小时后,我不明白如何实现这一目标。

我只想在测试中等一秒钟。真的有这么复杂吗?

这是需要测试的应用程序组件及其单元测试:

在下面的代码中,我把 expectation.fulfill() 放在哪里???

class ComponentBeingTested {

    func methodBeingTested() {

        doSomeWork()
        if certainConditionsAreMet {
             DispatchQueue.main.async {sendOutNotification()}
        }
    }
}

...

class UnitTestForComponentBeingTested: XCTestCase {

    let objectBeingTested = ComponentBeingTested()

    func testMethodBeingTested() {

          let expectation = self.expectation(description: "Notification was sent")

          // Call the code being tested
          objectBeingTested.methodBeingTested()

          // How do I do this with expectations ??? Where does expectation.fulfill() go ?
          waitForOneSecond()

          XCTAssertTrue(notificationSent)      // Assume the value of notificationSent is available

    }
}

这是一个方法

func testMethodBeingTested() {

      // create expectation
      let expectation = self.expectation(description: "Notification was sent")

      // set expectation condition
      var notificationSent = false
      let observer = NotificationCenter.default
            .addObserver(forName: _Your_Notification_Name, object: nil, queue: nil) { _ in
            notificationSent = true
            expectation.fulfill()
        }

      // Call the code being tested
      objectBeingTested.methodBeingTested()

      // wait for expectation
      self.wait(for: [expectation], timeout: 5)

      XCTAssertTrue(notificationSent)
}

查看 XCTNSNotificationExpectation,当匹配的通知发布时,它就会实现。可以使用不同的初始值设定项,具体取决于您希望对实现期​​望的限制程度。

要检查通知是否发送,请在期望对象上将 isInverted 设置为 true

然后只需在测试结束时添加对 waitForExpectations(timeout:handler:) 的调用即可。

好的,经过反复试验,这对我来说非常有用:

描述:我基本上在我的测试用例 class 中创建了一个辅助函数,其中包含所有样板文件 expectation/wait 代码。它执行以下操作:

1 - 创建期望(即 XCTestExpectation)作为形式。

2 - 在预期的延迟期后在某个全局队列线程上调用我的(任意)测试用例断言代码(作为闭包传入)。一旦这个断言代码完成,期望就实现了(再次,一种形式)。

3 - 通过调用 XCTestCase.wait(超时)等待预期。这确保了主线程/运行 循环在我的断言代码在另一个线程上完成时保持活动状态。

然后,在我的测试用例中,我只需调用该辅助函数,为其提供等待时间和一些要执行的代码(即我的断言)。

这样,我就有了一个简单而富有表现力的可重用函数,它隐藏了我一开始就认为没有必要的所有过度丑陋的期望。

我可以把这个助手放在一个基础 class 中,比如 MyAppTestCase: XCTestCase,这样它就可以用于我所有的测试用例 classes.

注意 - 此解决方案可以得到增强并做得更多 generic/reusable,但截至目前,这对于最初发布的问题来说已经足够了。

解决方法:

class ComponentBeingTested {

    func methodBeingTested() {

        doSomeWork()
        if certainConditionsAreMet {
             DispatchQueue.main.async {sendOutNotification()}
        }
    }
}

...

class UnitTestForComponentBeingTested: XCTestCase {

    let objectBeingTested = ComponentBeingTested()

    // Helper function that uses expectation/wait to execute arbitrary
    // test code (passed in as a closure) after some delay period.
    func executeAfter(_ timeSeconds: Double, _ work: (@escaping () -> Void)) {

        let theExpectation = expectation(description: "some expectation")

        // Execute work() after timeSeconds seconds
        DispatchQueue.global(qos: .userInteractive).asyncAfter(deadline: .now() + timeSeconds) {

            // The call to work() will execute my test assertions
            work()

            // As a formality, fulfill the expectation
            theExpectation.fulfill()
        }

        // Wait for (timeSeconds + 1) seconds to give the work() call 
        // some time to perform the assertions
        wait(for: [theExpectation], timeout: timeSeconds + 1)
    }

    func testMethodBeingTested() {

          // Call the code being tested
          objectBeingTested.methodBeingTested()

          // Call the helper function above, to do the waiting before executing
          // the assertions
          executeAfter(0.5) {

              // Assume the value of notificationSent is computed elsewhere
              // and is available to assert at this point
              XCTAssertTrue(notificationSent)      
          }
    }
}