5

繼TDD之後,我正在開發一款iPad應用程序,用於從互聯網下載一些信息並將其顯示在列表中,允許用戶使用搜索欄過濾該列表。帶有dispatch_async調用的測試代碼

我想測試一下,當用戶在搜索欄中輸入內容時,帶有過濾文本的內部變量被更新,項目的過濾列表被更新,最後表格視圖接收到一個「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]; 
} 

注:更改「filterText」屬性觸發,現在實際的過濾過程,這在其他測試中進行了測試。

我的搜索欄委託寫的代碼爲這個工程確定如下:

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

的問題是,過濾該數據是成爲一個沉重的過程,現在的問題是在主線程期間所完成的,所以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]; 
     }); 
    }); 
} 

,從而過濾過程在不同的線程中發生,當它已經完成,該表被要求重新加載其數據。

問題是......如何在dispatch_async調用中測試這些東西?

有沒有優雅除了基於時間的解決方案以外的方式嗎? (就像等待一段時間,並期望這些任務已經完成,不是非常確定的)

或者我應該把我的代碼放在不同的方式來使其更具可測性?

如果您需要知道,我使用OCMockitoOCHamcrest通過Jon Reid

在此先感謝!

+0

使用brakpoints和NSLogs可能對您有幫助嗎? – 2013-05-12 18:48:50

+0

爲了什麼目的你有前兩種方法。 – 2013-05-12 18:49:26

+0

Hi @ArpitParekh!這個想法是使用[單元測試](https://en.wikipedia.org/wiki/Unit_testing)自動測試我的代碼。這不是關於找到一個錯誤,而是爲了確保此代碼從現在開始正確運行。前兩種方法是來自我的測試套件的測試。檢查鏈接關於單元測試的更多信息:) – sergiou87 2013-05-12 20:46:44

回答

5

有兩種基本方法。要麼

  • 只有在測試時才使物體同步。或者,
  • 保持異步,但編寫一個確實重新同步的驗收測試。

爲了使測試只能進行同步測試,請將實際工作的代碼提取到自己的方法中。您已有-filteredDataWithText:。下面是另一個提取:

- (void)updateTableWithFilteredData:(NSArray *)filteredData 
{ 
    self.filteredData = filteredData; 
    [self.tableView reloadData]; 
} 

這需要所有的線程照顧現在看起來是這樣的真正的方法:

- (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]; 
     }); 
    }); 
} 

注意,下面所有的線程花式,它真的只是調用兩種方法。所以現在假裝所有線程做,有你的測試只調用以這兩種方法:

NSArray *filteredData = [self filteredDataWithText:searchText]; 
[self updateTableWithFilteredData:filteredData]; 

這並不意味着-searchBar:textDidChange:不會被單元測試覆蓋。一次手動測試可以確認它正在調度正確的事情。

如果您確實需要對委託方法進行自動化測試,請編寫一個具有自己的運行循環的驗收測試。請參閱Pattern for unit testing async queue that calls main queue on completion。 (但是要將驗收測試保存在一個單獨的測試目標中,它們太慢而不能包含單元測試)

+0

謝謝喬恩!現在我只是寫單元測試,對於不採用某些方法的決定,我覺得很困難,但是我想這就是在這種情況下,驗收測試來解決問題的時候。 – sergiou87 2013-05-13 09:30:12

3

Albite Jons選項在大多數情況下都是非常好的選項,有時它會在執行以下操作時創建更少的混亂代碼。例如,如果您的API有很多使用調度隊列同步的小方法。

有這樣的功能(它也可能是你的課程的一種方法)。

void dispatch(dispatch_queue_t queue, void (^block)()) 
{ 
    if(queue) 
    { 
     dispatch_async(queue, block); 
    } 
    else 
    { 
     block(); 
    } 
} 

然後用這個函數來調用塊在您的API方法

- (void)anAPIMethod 
{ 
    dispatch(dispQueue,^
    { 
     // dispatched code here 
    }); 
} 

您通常會初始化隊列在你的init方法。

@implementation MyAPI 
{ 
    dispatch_queue_t dispQueue; 
} 

- (instancetype)init 
{ 
    self = [super init]; 
    if (self) 
    { 
     dispQueue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL); 
    } 

    return self; 
} 

然後有一個像這樣的私有方法來設置這個隊列爲零。它不是你的接口的一部分,API消費者永遠不會看到這一點。

- (void) disableGCD 
{ 
    dispQueue = nil; 
} 

在您的測試目標創建類別,露出GCD禁用方法:

@interface TTBLocationBasedTrackStore (Testing) 
- (void) disableGCD; 
@end 

您在您的測試設置調用這個和你的塊將被直接調用。

我眼中的優勢在於調試。當一個測試用例涉及一個runloop,以便實際調用塊時,問題是必須涉及超時。這個超時通常很短,因爲如果測試進入超時狀態,您不希望持續很長時間的測試。但短暫的超時意味着您的測試在調試時會遇到超時。

+0

感謝您的回答!我正在選擇其他解決方案:將異步代碼隱藏在另一個類中,並在測試期間模擬該類。通過間諜我捕獲完成塊,模擬器立即執行完成塊。 我的測試中沒有異步代碼了:) – sergiou87 2015-09-07 09:41:58