XCTest - 无法理解/实现单元测试中的期望(测试 aysnc 代码)
XCTest - Unable to understand / implement expectations in unit tests (to test aysnc code)
(注意 - 我正在为 macOS 开发,所以请......iOS-具体的建议对我没有帮助)
我正在尝试做的事情:
我有一个在后台线程上执行短任务的应用程序组件,然后,如果满足某些条件,则在主线程上异步发送通知。
注意 - 我 没有在我的应用程序代码中使用 NSNotification。我正在使用自己的自定义通知机制。所以,任何与 NSNotification 相关的解决方案都不适用于我。
我正在为上述应用程序组件编写单元测试,只是想检查该通知是否确实已发送。在执行断言之前,我的测试必须能够等待一秒钟左右,以便通知时间达到其 subscriber/observer。
我希望能够在我的测试中测试两种可能的情况:两者都是正常情况。
通知已发送。
通知未发送。
在阅读了几个文档和代码示例数小时后,我不明白如何实现这一目标。
我只想在测试中等一秒钟。真的有这么复杂吗?
- sleep() 不起作用
- DispatchQueue.main.asyncAfter(时间)不起作用
- 定时器不工作
这是需要测试的应用程序组件及其单元测试:
在下面的代码中,我把 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)
}
}
}
(注意 - 我正在为 macOS 开发,所以请......iOS-具体的建议对我没有帮助)
我正在尝试做的事情: 我有一个在后台线程上执行短任务的应用程序组件,然后,如果满足某些条件,则在主线程上异步发送通知。
注意 - 我 没有在我的应用程序代码中使用 NSNotification。我正在使用自己的自定义通知机制。所以,任何与 NSNotification 相关的解决方案都不适用于我。
我正在为上述应用程序组件编写单元测试,只是想检查该通知是否确实已发送。在执行断言之前,我的测试必须能够等待一秒钟左右,以便通知时间达到其 subscriber/observer。
我希望能够在我的测试中测试两种可能的情况:两者都是正常情况。
通知已发送。
通知未发送。
在阅读了几个文档和代码示例数小时后,我不明白如何实现这一目标。
我只想在测试中等一秒钟。真的有这么复杂吗?
- sleep() 不起作用
- DispatchQueue.main.asyncAfter(时间)不起作用
- 定时器不工作
这是需要测试的应用程序组件及其单元测试:
在下面的代码中,我把 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)
}
}
}