菜鸟教程小白 发表于 2022-12-12 09:43:44

ios - XCTest 和 NSRunLoop 用于异步测试


                                            <p><p>我在很多地方(包括 Apple 开发论坛)看到,为了测试异步操作,一些开发人员建议获取当前运行循环并让它运行一段时间以强制调用异步 block 。例如</p>

<pre><code>__block id returnedModel = nil;
BOOL result = [binder fetchAndBind:...
                      successBlock:^(id *model) { returnModel = model; }
                        errorBlock:nil];

NSDate *loopUntil = ;

BOOL isModelReturned = (returnedModel != nil);

while (!isModelReturned &amp;&amp; &gt; 0)
{
    [ runMode:NSDefaultRunLoopMode beforeDate:loopUntil];
    isModelReturned = (returnedModel != nil);
}
</code></pre>

<p>上述实现有不同的风格,但概念是相同的。有些正在使用 <code>dispatch_group</code> 等。</p>

<p><strong>问题:</strong></p>

<ul>
<li><p>Apple 是否有任何关于测试异步操作的文档(我找不到)?</p></li>
<li><p>我从非官方来源了解到单元测试是
自包含在它们正在运行的运行循环中。所以他们不是
应该像上面那样处理。真的吗?这是由
苹果在哪里?</p></li>
<li><p>对于 Xcode 5.1,上述实现或 <code>dispatch_group</code> 会导致 <code>EXC_BAD_ACCESS (code=2, address=0xd)</code>。是不是因为单元测试是自包含在自己的线程中,不应该这样对待的概念?</p></li>
</ul>

<p>我已经看到这种方法的问题和副作用,特别是如果该测试中的一个或多个对象被模拟。副作用,例如导致应用程序崩溃,从而无法完成单元测试。例如 <code>Class A</code> 中的一个方法,它的公共(public) API 将 <code>NSArray</code> 作为输入,因为测试模拟了一个对象,让运行循环继续,然后该对象开始与 <code>Class A</code> 并且因为它被模拟了,所以它传入了一个字典! -- 而如果不强制运行循环继续,则该对象稍后将被取消模拟,并且每个测试都会很开心!</p>

<p>我个人认为没有理由去测试<code>async</code>。有一个以异步方式运行的操作。需要测试的是<strong><em>操作/功能</em></strong>而不是<code>async</code>。</p>

<p>我正在寻找一些引用资料或文档(最好来自 Apple),以清楚地讨论异步单元测试、单元测试的运行循环是否可以强制继续,或者使用 XCTests 测试异步操作的推荐方法是什么.</p>

<p>谢谢!</p>

<hr/>

<p><strong>编辑:</strong></p>

<p>在 Xcode 6 中,XCTest 框架附带异步测试宏。我把这个问题留在这里以供引用。</p></p>
                                    <br><hr><h1><strong>Best Answer-推荐答案</ strong></h1><br>
                                            <p><p>关于您的示例代码有一些误解:</p>

<p>首先,如果完成处理程序将在与调用站点执行位置不同的线程上执行,编译器将创建“未定义行为”的代码。这是由于修改了线程“A”中的变量 <code>returnedModel</code> 并读取了线程“M”中的值。这是一个经典的“数据竞赛”,会产生未定义的行为(请阅读 C 和 C++ 规范中的更多内容)。</p>

<p><code>__block</code> 修饰符<em>可以</em>缓解这个问题,但我不相信 clang 在这里采取特殊措施。在最坏的情况下,读取值的线程(主线程)永远不会“看到”通过处理程序执行的值更新,或者它读取的是“垃圾”。</p>

<p>这种方法的另一个问题需要更彻底地了解 Run Loops 的实际工作原理。在您的示例中,在最坏的情况下,运行循环的方法 <code>runMode:beforeDate:</code> 只会在超时到期时返回 - 即 10 秒后。它<em>可能</em>只有在此模式下处理了一个事件时才会更早返回 - 可能与测试代码无关。</p>

<p>简而言之,这种方法并不适合完成任务。但其他“口味”可能确实有效。</p>

<h3>根据您的问题:</h3>

<p>Q1:没有。</p>

<p>原因可能是,XCTest 实际上已经很老了(它只是 SenTest 的另一个名称),并且在它被发明时的代码可能没有像“异步操作”、“ block ”和“完成”这样的花哨的东西处理程序”。所以这个任务没有内置的解决方案。</p>

<p>Q2:我不太明白这个问题。但是我们可能会假设“匹配器”(又名“断言某事”)在测试失败时使用异常。这些需要在主线程上执行,其中有一个由底层测试实现实现的 catch 处理程序。也许 XCTest 不使用异常 - 但是,其他单元测试库可能确实使用异常 - 例如“Cedar”。这意味着,如果您在某个队列上执行完成处理程序,并且匹配器抛出异常,则它必须在主线程上执行。 (无赖)。</p>

<p>Q3:也许是异常问题?但我不知道。可能还有别的问题。您可以提供更多信息。</p>

<p>其他“副作用”可能是“竞争条件”或其他问题。但除非你提供更详细的信息,否则我猜 ;)</p>

<p>是否需要“测试异步”实际上取决于您实际测试的内容:</p>

<p>例如,如果您使用具有完成处理程序的知名第三方网络库,您是否真的要测试该处理程序是否会被<em>调用</em>? (可能不会,因为您不想实际测试网络库)。</p>

<p>但如果您实现了自己的异步操作并通过完成处理程序报告结果,您实际上可能想要测试是否会调用完成处理程序。</p></p>
                                   
                                                <p style="font-size: 20px;">关于ios - XCTest 和 NSRunLoop 用于异步测试,我们在Stack Overflow上找到一个类似的问题:
                                                        <a href="https://stackoverflow.com/questions/23499801/" rel="noreferrer noopener nofollow" style="color: red;">
                                                                https://stackoverflow.com/questions/23499801/
                                                        </a>
                                                </p>
                                       
页: [1]
查看完整版本: ios - XCTest 和 NSRunLoop 用于异步测试