管理 XCTest 上的多个异步操作
Manage multiple asynchronous operation on XCTests
我在 XCTestCase 对象中遇到异步操作问题。
我从 expectation-waitForExpectation
链开始,有时将期望实例作为方法参数传递,以使其由异步操作完成块完成。
现在我换了方法,因为我看不到以前写的不好的代码,我是这样试的:
- (void)testThatItGoesToTheRightSport
{
if (! appDelegateInstance.firstTimeLoaded)
{
[self __waitForLoadingAppAndDo:^(BOOL launched)
{
if (launched)
{
[self __goToLiveWithHandler:^(BOOL success) {
if (success)
{
[self __goToLiveSport:[NSNumber numberWithUnsignedInteger:kDefaultSportCode]
handler:^(BOOL success) {
if (! success)
{
XCTFail(@"Test failed");
}
}];
}
}];
}
}];
}
else
{
[self __goToLiveWithHandler:^(BOOL success) {
if (success)
{
[self __goToLiveSport:[NSNumber numberWithUnsignedInteger:kDefaultSportCode]
handler:^(BOOL success) {
if (! success)
{
XCTFail(@"Test failed");
}
}];
}
}];
}
}
__waitForLoadingAppAndDo:
方法实现为
- (void)__waitForLoadingAppAndDo:(void (^)(BOOL launched))afterBlock
{
if (! afterBlock)
XCTFail(@"No afterBlock");
XCTestExpectation *dataEx = [self expectationForNotification:NOTIFICATION_HOME_LOADED
object:nil
handler:^BOOL(NSNotification *notification) {
[dataEx fulfill];
return YES;
}];
[self waitForExpectationsWithTimeout:SOCKOPT_TIMEOUT handler:^(NSError *error)
{
if (error)
{
XCTFail(@"No data received. %@", [error localizedDescription]);
}
else
{
dispatch_async(dispatch_get_main_queue(), ^{
afterBlock(YES);
});
}
}];
}
和其他__
方法类似。显然,现在 testThat
方法不在等待方法完成处理程序。我该如何改进它以及如何让 testThat
方法等待完成? XCTestExpectation
是这样吗? (告诉我不要嗯)
ADDING: 那么,这是唯一的方式吗?
- (void)testThatItGoesToTheRightSport
{
if (! appDelegateInstance.firstTimeLoaded)
{
XCTestExpectation *waitingLoading = [self expectationWithDescription:@"loadingApp"];
[self waitForExpectationsWithTimeout:SOCKOPT_TIMEOUT handler:^(NSError *error) {
if (error)
{
XCTFail(@"After block was not called.");
}
}];
[self __waitForLoadingAppAndDo:^(BOOL launched)
{
if (launched)
{
[self __goToLiveWithHandler:^(BOOL success) {
if (success)
{
[waitingLoading fulfill];
[...]
}
}];
}
}];
ADDING-2:
我试过
__block NSError *internalError = nil;
__block XCTestExpectation *finishedTest = [self expectationWithDescription:@"finishedTest"];
dispatch_group_t asyncGroup = dispatch_group_create();
if (! appDelegateInstance.firstTimeLoaded)
{
dispatch_group_enter(asyncGroup);
[self __waitForLoadingAppAndDo:^(BOOL launched) {
if (! launched)
{
internalError = [TestUtilities notLoadedAppError];
}
dispatch_group_leave(asyncGroup);
}];
dispatch_group_enter(asyncGroup);
[self __goToLiveWithHandler:^(BOOL success) {
if (! success)
{
internalError = [NSError errorWithDomain:@"goLive"
code:-1
userInfo:@{NSLocalizedDescriptionKey : @"Errore apertura Live"}];
}
dispatch_group_leave(asyncGroup);
}];
dispatch_group_notify(asyncGroup, dispatch_get_main_queue(), ^{
[finishedTest fulfill];
});
但是这些组是异步调用的,不需要等待完成块。
例如:我希望 OP1 首先开始,在 OP1 完成块中 OP2 将开始。
添加-3:
我对 dispatch_semaphore
使用了不同的方法。我喜欢它,但有一样东西我想换掉。如果我要等待信号,我会阻塞主线程,所以我必须 dispatch_async
wait
命令如下:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
dispatch_semaphore_wait(loadedApp, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_main_queue(), ^{
[self __goToLiveWithHandler:^(BOOL success) {
if (success)
{
[...]
}
}];
});
});
有没有办法避免这种情况?
使用
dispatch_group
创建事件链。
测试开始时:
Dispatch_group_t group = dispatch_group_create();
在每个异步部分调用之前:
Dispatch_group_enter(group);
并且当每个异步部分完成使用时:
Dispatch_group_leave(group);
("enter"的个数必须等于"leave"的个数)
在异步代码的末尾"wait":
// called when "group" |enter|=|leave|
Dispatch_group_apply(group,main_queue,^{
// check your conditions....
If (success) [expectation fulfill];
Else // failure
});
Expectation wait....// add the expectation wait handler as before
dispatch_group
有不同的变体,因此您可能需要根据您的用例进行调整。
希望对您有所帮助
*******编辑*******
根据您的评论,您可能希望以不同方式嵌套组。例如:
// create the expectation
Expectation = ....
dispatch_group_t myGroup = dispatch_group_create();
dispatch_group_enter(myGroup);
// do first portion
dispatch_group_leave(myGroup);
// do the second portion after the first
dispatch_group_apply(myGroup,dispatch_queue..., ^{
//second code portion
// chain as many as needed using this technique
// eventually you will need to fulfill your "expectation
// ...
if (success) {
[expectation fullfil];
} else {
XCTAssert(fail,@"The test failed because ....");
}
});
[self expectation wait...]
我在 XCTestCase 对象中遇到异步操作问题。
我从 expectation-waitForExpectation
链开始,有时将期望实例作为方法参数传递,以使其由异步操作完成块完成。
现在我换了方法,因为我看不到以前写的不好的代码,我是这样试的:
- (void)testThatItGoesToTheRightSport
{
if (! appDelegateInstance.firstTimeLoaded)
{
[self __waitForLoadingAppAndDo:^(BOOL launched)
{
if (launched)
{
[self __goToLiveWithHandler:^(BOOL success) {
if (success)
{
[self __goToLiveSport:[NSNumber numberWithUnsignedInteger:kDefaultSportCode]
handler:^(BOOL success) {
if (! success)
{
XCTFail(@"Test failed");
}
}];
}
}];
}
}];
}
else
{
[self __goToLiveWithHandler:^(BOOL success) {
if (success)
{
[self __goToLiveSport:[NSNumber numberWithUnsignedInteger:kDefaultSportCode]
handler:^(BOOL success) {
if (! success)
{
XCTFail(@"Test failed");
}
}];
}
}];
}
}
__waitForLoadingAppAndDo:
方法实现为
- (void)__waitForLoadingAppAndDo:(void (^)(BOOL launched))afterBlock
{
if (! afterBlock)
XCTFail(@"No afterBlock");
XCTestExpectation *dataEx = [self expectationForNotification:NOTIFICATION_HOME_LOADED
object:nil
handler:^BOOL(NSNotification *notification) {
[dataEx fulfill];
return YES;
}];
[self waitForExpectationsWithTimeout:SOCKOPT_TIMEOUT handler:^(NSError *error)
{
if (error)
{
XCTFail(@"No data received. %@", [error localizedDescription]);
}
else
{
dispatch_async(dispatch_get_main_queue(), ^{
afterBlock(YES);
});
}
}];
}
和其他__
方法类似。显然,现在 testThat
方法不在等待方法完成处理程序。我该如何改进它以及如何让 testThat
方法等待完成? XCTestExpectation
是这样吗? (告诉我不要嗯)
ADDING: 那么,这是唯一的方式吗?
- (void)testThatItGoesToTheRightSport
{
if (! appDelegateInstance.firstTimeLoaded)
{
XCTestExpectation *waitingLoading = [self expectationWithDescription:@"loadingApp"];
[self waitForExpectationsWithTimeout:SOCKOPT_TIMEOUT handler:^(NSError *error) {
if (error)
{
XCTFail(@"After block was not called.");
}
}];
[self __waitForLoadingAppAndDo:^(BOOL launched)
{
if (launched)
{
[self __goToLiveWithHandler:^(BOOL success) {
if (success)
{
[waitingLoading fulfill];
[...]
}
}];
}
}];
ADDING-2:
我试过
__block NSError *internalError = nil;
__block XCTestExpectation *finishedTest = [self expectationWithDescription:@"finishedTest"];
dispatch_group_t asyncGroup = dispatch_group_create();
if (! appDelegateInstance.firstTimeLoaded)
{
dispatch_group_enter(asyncGroup);
[self __waitForLoadingAppAndDo:^(BOOL launched) {
if (! launched)
{
internalError = [TestUtilities notLoadedAppError];
}
dispatch_group_leave(asyncGroup);
}];
dispatch_group_enter(asyncGroup);
[self __goToLiveWithHandler:^(BOOL success) {
if (! success)
{
internalError = [NSError errorWithDomain:@"goLive"
code:-1
userInfo:@{NSLocalizedDescriptionKey : @"Errore apertura Live"}];
}
dispatch_group_leave(asyncGroup);
}];
dispatch_group_notify(asyncGroup, dispatch_get_main_queue(), ^{
[finishedTest fulfill];
});
但是这些组是异步调用的,不需要等待完成块。 例如:我希望 OP1 首先开始,在 OP1 完成块中 OP2 将开始。
添加-3:
我对 dispatch_semaphore
使用了不同的方法。我喜欢它,但有一样东西我想换掉。如果我要等待信号,我会阻塞主线程,所以我必须 dispatch_async
wait
命令如下:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
dispatch_semaphore_wait(loadedApp, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_main_queue(), ^{
[self __goToLiveWithHandler:^(BOOL success) {
if (success)
{
[...]
}
}];
});
});
有没有办法避免这种情况?
使用
dispatch_group
创建事件链。
测试开始时:
Dispatch_group_t group = dispatch_group_create();
在每个异步部分调用之前:
Dispatch_group_enter(group);
并且当每个异步部分完成使用时:
Dispatch_group_leave(group);
("enter"的个数必须等于"leave"的个数)
在异步代码的末尾"wait":
// called when "group" |enter|=|leave|
Dispatch_group_apply(group,main_queue,^{
// check your conditions....
If (success) [expectation fulfill];
Else // failure
});
Expectation wait....// add the expectation wait handler as before
dispatch_group
有不同的变体,因此您可能需要根据您的用例进行调整。
希望对您有所帮助
*******编辑*******
根据您的评论,您可能希望以不同方式嵌套组。例如:
// create the expectation
Expectation = ....
dispatch_group_t myGroup = dispatch_group_create();
dispatch_group_enter(myGroup);
// do first portion
dispatch_group_leave(myGroup);
// do the second portion after the first
dispatch_group_apply(myGroup,dispatch_queue..., ^{
//second code portion
// chain as many as needed using this technique
// eventually you will need to fulfill your "expectation
// ...
if (success) {
[expectation fullfil];
} else {
XCTAssert(fail,@"The test failed because ....");
}
});
[self expectation wait...]