我在许多地方(包括Apple Dev论坛)看到,为了测试异步操作,一些开发人员建议抓住当前的运行循环并让它运行一段时间以强制异步块被调用。 e.g。
__block id returnedModel = nil;
BOOL result = [binder fetchAndBind:...
successBlock:^(id *model) { returnModel = model; }
errorBlock:nil];
NSDate *loopUntil = [NSDate dateWithTimeIntervalSinceNow:10.0f];
BOOL isModelReturned = (returnedModel != nil);
while (!isModelReturned && [loopUntil timeIntervalSinceNow] > 0)
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:loopUntil];
isModelReturned = (returnedModel != nil);
}
上述实现有不同的风格,但概念是相同的。有些人正在使用dispatch_group
等。
问题:
Apple是否有任何关于测试异步操作的文档(我找不到)?
从非官方来源我已经读过单元测试了 它们正在运行的运行循环中自包含。所以他们不是 应该如上所述。真的吗?这是记录在案的 苹果在某个地方?
使用Xcode 5.1上述实现或dispatch_group
导致EXC_BAD_ACCESS (code=2, address=0xd)
。是不是因为单元测试在自己的线程中是自包含的概念而不应该被这样对待?
我已经看到了这种方法的问题和副作用,特别是如果该测试中的一个或多个对象被嘲笑。副作用,如导致应用程序崩溃,以便无法完成单元测试。例如Class A
中的一个方法,它的公共API将NSArray
作为输入崩溃,因为测试模拟了一个对象,让运行循环继续,然后该对象开始与Class A
交互,因为它被嘲笑,它通过字典! - 如果没有强制继续运行循环,那么该对象以后就会被嘲笑,每次测试都会很开心!
我个人认为没有理由测试async
。有一个以异步方式运行的操作。 操作/功能 需要进行测试而不是async
。
我正在寻找一些参考或文档(可以从Apple获得)来清楚地讨论异步单元测试,是否可以强制继续单元测试的运行循环,或者使用XCTests测试异步操作的推荐方法是什么
谢谢!
修改
在Xcode 6中,XCTest框架附带了异步测试宏。我在这里留下这个问题作为参考。
答案 0 :(得分:2)
您的示例代码存在一些误解:
首先,如果完成处理程序将在与调用站点执行的不同的线程上执行,则编译器将创建“未定义的行为”代码。这是因为修改了线程“A”中的变量returnedModel
并读取了线程“M”中的值。这是一个经典的“数据竞争”,它会产生不确定的行为(请在C和C ++规范中阅读更多内容)。
__block
修饰符可以缓解此问题,但我不相信clang会在此处采取特殊措施。在最坏的情况下,读取值(主线程)的线程永远不会“看到”通过处理程序执行的值的更新,或者它读取“垃圾”。
此方法的另一个问题是需要更彻底地了解Run Loops的实际工作方式。在您的示例中,在最坏的情况下,运行循环的方法runMode:beforeDate:
将仅在超时到期时返回 - 即10秒后返回。只有在此模式下处理了事件时,可能才能提前返回 - 可能与测试代码无关。
简而言之,这种方法并不适合完成任务。但其他“口味”确实可行。
Q1:没有。
原因可能是,XCTest实际上已经很老了(它只是SenTest的另一个名称),而且它发明时的代码可能没有像“异步操作”,“块”和“完成”这样的花哨的东西。处理程序”。因此,没有针对此任务的内置解决方案。
Q2:我不太明白这个问题。但是我们可能会假设“匹配器”(也称为“断言”)在测试失败时使用异常。这些需要在主线程上执行,其中有一个由底层测试实现实现的catch处理程序。也许XCTest不使用异常 - 但是,其他单元测试库可能确实使用异常 - 例如“Cedar”。这意味着,如果您在某个队列上执行完成处理程序,并且匹配器抛出异常,则必须在主线程上执行。 (长号)。
问题3:也许是例外问题?但我不知道。可能还有另一个问题。您可以提供更多信息。其他“副作用”可能是“竞争条件”或其他问题。但除非你提供更详细的信息,我猜测;)
是否需要“测试异步”实际上取决于您实际测试的内容:
例如,如果您使用具有完成处理程序的众所周知的第三方网络库,您真的想测试处理程序是否将被调用吗? (可能不是,因为你不想真正测试网络库)。
但是如果你实现了自己的异步操作,它通过一个完成处理程序报告结果,你实际上可能想测试是否会调用一个完成处理程序。