2012-06-07 24 views
29

Có thể đặt lại trạng thái mã dispatch_once trong một tearDown thử nghiệm đơn vị không?Có thể đặt lại trạng thái của dispatch_once trong thử nghiệm đơn vị, để làm cho chúng chạy lại

Tôi nghĩ sẽ tốt hơn nếu các bài kiểm tra đơn vị của chúng tôi có thể chạy từ trạng thái thực sự sạch sẽ, nhưng chúng tôi đang đấu tranh với dispatch_once và một số đơn được thực hiện với công văn một lần.

Trả lời

42

Tôi cần lưu ý đầu tiên rằng đây không phải là điều tốt để làm trong bất kỳ tình huống nào ngoài thử nghiệm; thậm chí sau đó, tiến hành cẩn thận - AliSoftware cung cấp một số chi tiết và mã ví dụ trong phần bình luận bên dưới. Xem thêm các câu trả lời thú vị tại Can I declare a dispatch_once_t predicate as a member variable instead of static?, bao gồm một số thông tin quan trọng from the horse's mouth.

dispatch_once_ttypedef d long. Giá trị sai của nó là 0. Nếu bạn đặt lại cờ đó thành 0, dispatch_once() sẽ chạy lại. Vấn đề của bạn là "chỉ" làm thế nào để thay đổi giá trị của một biến tĩnh từ một đơn vị biên dịch khác. Đối với điều này, tôi nghĩ rằng bạn cần một cái móc thử nghiệm debug/đơn vị, như vậy:

MakeWhoopie.h

#import <Foundation/Foundation.h> 

void makeWhoopie(void); 

#ifdef DEBUG 
void resetDispatchOnce(void); 
#endif 

MakeWhoopie.m

#include "MakeWhoopie.h" 

static dispatch_once_t * once_token_debug; 

void makeWhoopie(void) 
{ 

    static dispatch_once_t once_token; 
    once_token_debug = &once_token; // Store address of once_token 
             // to access it in debug function. 
    dispatch_once(&once_token, ^{ 
     NSLog(@"That's what you get, folks."); 
    }); 

    NSLog(@"Making whoopie."); 
} 

#ifdef DEBUG 
void resetDispatchOnce(void) 
{ 
    *once_token_debug = 0; 
} 
#endif 

(Bạn cũng có thể di chuyển once_token lên đến nộp và thay đổi trực tiếp.)

Hãy thử cách này:

#import <Foundation/Foundation.h> 
#import "MakeWhoopie.h" 

int main(int argc, const char * argv[]) 
{ 

    @autoreleasepool { 

     makeWhoopie(); 
     makeWhoopie(); 
     resetDispatchOnce(); 
     makeWhoopie(); 
    } 
    return 0; 
} 

Kết quả trong:

2012-06-07 18: 45: 28,134 ResetDispatchOnce [8628: 403] Đó là những gì bạn nhận được, folks.
2012-06-07 18: 45: 28.163 ResetDispatchOnce [8628: 403] Làm whoopie.
2012-06-07 18: 45: 28.164 ResetDispatchOnce [8628: 403] Làm whoopie.
2012-06-07 18: 45: 28.165 ResetDispatchOnce [8628: 403] Đó là những gì bạn nhận được, mọi người.
2012-06-07 18: 45: 28.165 ResetDispatchOnce [8628: 403] Làm whoopie.

+0

Điểm của 'dispatch_once' là an toàn chỉ. Vấn đề bằng cách thiết lập '* once_token_debug = 0' theo cách này là nó không an toàn cho thread trong trường hợp một luồng khác sử dụng' dispatch_once (& onceToken,…) 'trong khi bạn tự đặt' onceToken'. Làm thế nào chúng ta có thể ngăn chặn vấn đề như vậy? – AliSoftware

+0

@AliSoftware: Kịch bản trong câu hỏi là kiểm tra đơn vị và đó là cách sử dụng _only_ tôi đề xuất cho quy trình này. Mã thông báo đang được thiết lập lại các kiểm tra _between_, để tạo một slate sạch giống như sẽ được tạo ra giữa các lần chạy của chương trình lớn hơn. Luồng không phải là vấn đề vì chương trình không chạy giữa các lần kiểm tra. –

+0

Tôi hiểu điều này, nhưng tất cả mọi người phải nhận thức được điều đó và không bị cám dỗ để sử dụng điều này một cách không an toàn ở đâu đó.Và có thể có một số trường hợp ngay cả trong Bài kiểm tra đơn vị rằng đây không phải là an toàn luồng, đặc biệt nếu một mã xấu kiểm tra và thực hiện các hành động không đồng bộ tiếp tục chạy ngay cả sau khi kiểm tra kết thúc (như một cuộc gọi đến 'dispatch_after (10s,^{/ * sthg sử dụng sharedInstance * /} 'nếu thử nghiệm đạt đến timeout của nó nó sẽ thất bại và bị dừng lại và tiếp cận' tearDown' nhưng khối sử dụng sharedInstance sẽ vẫn được gửi đi trong tương lai gần… sau tearDown – AliSoftware

4

Chúng tôi cũng kiểm tra đơn vị của chúng tôi và đôi khi cần phải thay thế chúng bằng các đối tượng giả hoặc đặt lại chúng. Tôi lấy câu trả lời của Josh và đơn giản hóa nó thêm một chút:

static ArticleManager *_sharedInstance = nil; 
static dispatch_once_t once_token = 0; 

+(ArticleManager *)sharedInstance { 
    dispatch_once(&once_token, ^{ 
     if (_sharedInstance == nil) { 
      _sharedInstance = [[ArticleManager alloc] init]; 
     } 
    }); 
    return _sharedInstance; 
} 

+(void)setSharedInstance:(ArticleManager *)instance { 
    if (instance == nil) once_token = 0; 
    _sharedInstance = instance; 
} 
+0

Tôi đã viết một bài đăng trên blog giải thích lý do bạn nên thực hiện theo cách này: http://twobitlabs.com/2013/01/objective-c-singleton-pattern-unit-testing/ – ToddH

+0

Tôi đã đề cập việc đặt mã thông báo một lần ở cấp tệp câu trả lời của tôi. Lưu ý rằng việc sử dụng mã của tôi - hoặc của bất kỳ ai được đăng trên SO, cho dù nguyên văn hay sửa đổi, [yêu cầu] (http://creativecommons.org/licenses/by-sa/3.0/) mà bạn bao gồm [thuộc tính] (http : //blog.stackoverflow.com/2009/06/attribution-required/). Trong trường hợp này, cơ chế này đơn giản đến mức khó có thể đề cập đến, nhưng xin đừng đi vào thói quen viết blog dựa trên các câu trả lời của SO mà không cần liên kết ngược lại. –

+0

Trên thực tế, kiểm tra == nil bên trong dispatch_once có vẻ dư thừa. Bạn chỉ đặt lại once_token nếu _sharedInstance được đặt là 0. – Vitali

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