使用dispatch_async调用测试代码

ser*_*u87 5 unit-testing objective-c objective-c-blocks dispatch-async ocmockito

继TDD之后,我正在开发一款iPad应用程序,可以从互联网上下载一些信息并将其显示在列表中,允许用户使用搜索栏过滤该列表.

我想测试一下,当用户在搜索栏中输入内容时,会更新带有过滤器文本的内部变量,更新过滤的项目列表,最后表格视图会收到"reloadData"消息.

这些是我的测试:

- (void)testSutChangesFilterTextWhenSearchBarTextChanges
{
    // given
    sut.filterText = @"previous text";

    // when
    [sut searchBar:nil textDidChange:@"new text"];

    // then
    assertThat(sut.filterText, is(equalTo(@"new text")));
}

- (void)testSutReloadsTableViewDataAfterChangeFilterTextFromSearchBar
{
    // given
    sut.tableView = mock([UITableView class]);

    // when
    [sut searchBar:nil textDidChange:@"new text"];

    // then
    [verify(sut.tableView) reloadData];
}
Run Code Online (Sandbox Code Playgroud)

注意:更改"filterText"属性现在会触发实际的过滤过程,该过程已在其他测试中进行了测试.

这工作正常,因为我的searchBar委托代码编写如下:

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
    self.filterText = searchText;
    [self.tableView reloadData];
}
Run Code Online (Sandbox Code Playgroud)

问题是过滤这些数据正变成一个繁重的过程,现在正在主线程上完成,因此在此期间UI被阻止.

因此,我想做这样的事情:

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSArray *filteredData = [self filteredDataWithText:searchText];

        dispatch_async(dispatch_get_main_queue(), ^{
            self.filteredData = filteredData;
            [self.tableView reloadData];
        });
    });
}
Run Code Online (Sandbox Code Playgroud)

因此,过滤过程发生在不同的线程中,当它完成时,要求该表重新加载其数据.

问题是......如何在dispatch_async调用中测试这些内容?

除了基于时间的解决方案之外,还有其他优雅的方法吗?(比如等待一段时间,并期望那些任务已经完成,不是很确定)

或许我应该以不同的方式使我的代码使其更可测试?

如果你需要知道,我使用OCMockitoOCHamcrest乔恩·里德.

提前致谢!!

Jon*_*eid 5

有两种基本方法.或

  • 仅在测试时使事物同步.要么,
  • 保持异步,但写一个重新同步的验收测试.

要使事物同步以进行测试,请将实际工作的代码提取到自己的方法中.你已经拥有了-filteredDataWithText:.这是另一个提取:

- (void)updateTableWithFilteredData:(NSArray *)filteredData
{
    self.filteredData = filteredData;
    [self.tableView reloadData];
}
Run Code Online (Sandbox Code Playgroud)

现在处理所有线程的真正方法如下所示:

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSArray *filteredData = [self filteredDataWithText:searchText];

        dispatch_async(dispatch_get_main_queue(), ^{
            [self updateTableWithFilteredData:filteredData];
        });
    });
}
Run Code Online (Sandbox Code Playgroud)

请注意,在所有线程范围之下,它实际上只调用两个方法.所以现在假装所有线程都已完成,让你的测试按顺序调用这两个方法:

NSArray *filteredData = [self filteredDataWithText:searchText];
[self updateTableWithFilteredData:filteredData];
Run Code Online (Sandbox Code Playgroud)

这确实意味着-searchBar:textDidChange:单元测试不会涵盖这一点.单个手动测试可以确认它正在调度正确的东西.

如果您真的想要对委托方法进行自动化测试,请编写一个具有自己的运行循环的验收测试.请参阅模式以获取在完成时调用主队列的单元测试异步队列.(但是将验收测试保存在单独的测试目标中.它们太慢而不能包含单元测试.)