5

Theo dõi TDD Tôi đang phát triển một ứng dụng iPad tải xuống một số thông tin từ internet và hiển thị nó trên danh sách, cho phép người dùng lọc danh sách đó bằng thanh tìm kiếm.Mã thử nghiệm với dispatch_async gọi

Tôi muốn kiểm tra rằng, khi người dùng nhập vào thanh tìm kiếm, biến nội bộ với văn bản bộ lọc được cập nhật, danh sách các mục được lọc sẽ được cập nhật và cuối cùng chế độ xem bảng sẽ nhận được thông báo "reloadData".

Đây là những thử nghiệm của tôi:

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

LƯU Ý: Thay đổi "filterText" bất động sản gây nên ngay bây giờ quá trình lọc thực tế, mà đã được thử nghiệm trong các thử nghiệm khác.

này hoạt động OK như mã Searchbar đại biểu của tôi đã được viết như sau:

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

Vấn đề là lọc dữ liệu này là trở thành một quá trình nặng mà ngay bây giờ đang được thực hiện trên các chủ đề chính, vì vậy trong thời gian đó thời gian giao diện người dùng bị chặn.

Do đó, tôi nghĩ đến việc làm một cái gì đó như thế này:

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

Vì vậy mà quá trình lọc xảy ra trong một chủ đề khác nhau và khi nó kết thúc, bảng được hỏi để tải lại dữ liệu của nó.

Câu hỏi là ... làm cách nào để kiểm tra những điều này bên trong các cuộc gọi dispatch_async?

Có bất kỳ thanh lịch cách nào khác ngoài giải pháp dựa trên thời gian không? (như chờ đợi một thời gian và hy vọng rằng những nhiệm vụ đó đã hoàn thành, không phải rất quyết định)

Hoặc có lẽ tôi nên đặt mã của mình theo một cách khác để làm cho nó dễ kiểm chứng hơn?

Trong trường hợp bạn cần biết, tôi đang sử dụng OCMockitoOCHamcrest bởi Jon Reid.

Cảm ơn trước !!

+0

Sử dụng điểm dừng và NSLog có thể giúp bạn? –

+0

Vì mục đích nào bạn có hai phương pháp đầu tiên. –

+0

Xin chào @ArpitParekh! Ý tưởng đang sử dụng [kiểm tra đơn vị] (https://en.wikipedia.org/wiki/Unit_testing) để tự động kiểm tra mã của tôi. Nó không phải là về việc tìm kiếm một lỗi, nhưng để đảm bảo rằng mã này hoạt động đúng từ bây giờ. Hai phương pháp đầu tiên là các thử nghiệm từ bộ thử nghiệm của tôi. Kiểm tra liên kết về kiểm tra đơn vị để biết thêm thông tin :) – sergiou87

Trả lời

5

Có hai cách tiếp cận cơ bản. Hoặc là

  • Chỉ thực hiện đồng bộ khi đang thử nghiệm. Hoặc,
  • Giữ mọi thứ không đồng bộ, nhưng viết bài kiểm tra chấp nhận không đồng bộ hóa.

Để thực hiện mọi thứ đồng bộ chỉ để thử nghiệm, hãy trích xuất mã thực sự hoạt động theo cách riêng của chúng. Bạn đã có -filteredDataWithText:. Đây là một khai thác:

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

Phương pháp thực sự mà sẽ chăm sóc của tất cả các luồng bây giờ trông như thế này:

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

ý rằng bên dưới tất cả những gì luồng fanciness, nó thực sự chỉ gọi hai phương pháp.Vì vậy bây giờ phải giả vờ rằng tất cả những luồng đã được thực hiện, xét nghiệm của bạn chỉ cần gọi hai phương pháp theo thứ tự:

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

này có nghĩa là -searchBar:textDidChange: sẽ không được bao phủ bởi các unit test. Một thử nghiệm thủ công có thể xác nhận rằng nó đang gửi đúng thứ.

Nếu bạn thực sự muốn thử nghiệm tự động trên phương thức ủy nhiệm, hãy viết bài kiểm tra chấp nhận có vòng lặp chạy của riêng nó. Xem Pattern for unit testing async queue that calls main queue on completion. (Nhưng vẫn giữ các bài kiểm tra chấp nhận trong một mục tiêu kiểm tra riêng biệt. Chúng quá chậm để bao gồm các bài kiểm tra đơn vị.)

+0

Cảm ơn Jon! Ngay bây giờ tôi chỉ đang viết các bài kiểm tra đơn vị và bằng cách nào đó tôi khó có thể đưa ra quyết định không bao gồm một số phương pháp, nhưng tôi đoán đó là khi các bài kiểm tra chấp nhận được giải cứu trong các trường hợp như thế này. – sergiou87

3

Tùy chọn Albite Jons là lựa chọn rất tốt, đôi khi nó tạo ra ít mã lộn xộn hơn khi làm như sau. Ví dụ: nếu API của bạn có nhiều phương thức nhỏ được đồng bộ hóa bằng hàng đợi công văn.

Có chức năng như thế này (nó có thể là một phương pháp của lớp học của bạn).

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

Sau đó sử dụng chức năng này để gọi các khối trong các phương pháp API của bạn

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

Bạn thường sẽ khởi hàng đợi trong phương pháp init của bạn.

@implementation MyAPI 
{ 
    dispatch_queue_t dispQueue; 
} 

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

    return self; 
} 

Sau đó, có phương thức riêng tư như thế này, để đặt hàng này thành không. Nó không phải là một phần của giao diện của bạn, người tiêu dùng API sẽ không bao giờ thấy điều này.

- (void) disableGCD 
{ 
    dispQueue = nil; 
} 

Trong mục tiêu thử nghiệm của bạn, bạn tạo ra một thể loại để lộ các GCD phương pháp vô hiệu hóa:

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

Bạn gọi này trong thiết lập thử nghiệm của bạn và các khối của bạn sẽ được gọi trực tiếp.

Lợi thế trong mắt tôi là gỡ lỗi. Khi một trường hợp thử nghiệm liên quan đến một runloop để các khối thực sự được gọi, vấn đề là phải có một thời gian chờ liên quan. Thời gian chờ này thường khá ngắn vì bạn không muốn có các thử nghiệm kéo dài nếu chúng chạy hết thời gian chờ. Nhưng có một thời gian chờ ngắn có nghĩa là thử nghiệm của bạn chạy vào thời gian chờ khi gỡ lỗi.

+0

Cảm ơn câu trả lời của bạn! Tôi hiện đang chọn giải pháp khác: ẩn mã không đồng bộ trong một lớp khác và giả sử lớp đó trong khi thử nghiệm. Với một điệp viên tôi nắm bắt khối hoàn thành và giả lập chỉ thực hiện khối hoàn thành đó ngay lập tức. Không có mã không đồng bộ trong các thử nghiệm của tôi nữa :) – sergiou87

Các vấn đề liên quan