2013-02-20 1 views
1

그랜드 센트럴 디스패치를 ​​사용하는 일부 GUI 구성 요소에 대한 단위 테스트를 작성하려고합니다. 테스트에서 스레드 된 코드를 호출하고 완료 될 때까지 기다린 다음 GUI 개체에 대한 결과를 확인하고 싶습니다. 내 테스트에서그랜드 센트럴 디스패치를 ​​사용하는 GUI 구성 요소 테스트

dispatch_queue_t myQueue = dispatch_queue_create(); 

- (void)refreshGui { 
    [self.button setEnabled:NO]; 
    dispatch_async(myQueue, ^{ 
     //operation of undetermined length 
     sleep(1); 

     dispatch_sync(dispatch_get_main_queue(), ^{ 
      // GUI stuff that must be on the main thread, 
      // I want this to be done before I check results in my tests. 
      [self.button setEnabled:YES]; 
     }); 
    }); 
} 

, 나는 이런 식으로 뭔가를 할 :

-(void)testRefreshGui { 
    [object refreshGui]; 
    [object blockUntilThreadedOperationIsDone]; 
    STAssertTrue([object isRefreshedProperly], @"did not refresh"); 
} 

내 첫번째 생각은 다음과 같이 관련 큐에 동 기적으로 뭔가를 호출했다. 불행하게도,이 메인 큐에서 호출 교착 결과 (GUI를 코드의 주요 큐에 dispatch_sync()가 있기 때문에, 시험은 주 스레드에서 실행) :

-(void)blockOnQueue:(dispatch_queue_t)q { 
    dispatch_sync(q, ^{}); 
} 

사용 a dispatch_group_wait(group, DISPATCH_TIME_FOREVER)으로 디스패치 그룹도 같은 이유로 교착 상태가됩니다.

내가 생각 해낸 해킹 솔루션이 있었다 :

불행하게도
- (void)waitOnQueue:(dispatch_queue_t)q { 
    __block BOOL blocking = YES; 
    while (blocking) { 
     [NSRunLoop.mainRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:.1]]; 
     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{ 
      dispatch_sync(q, ^{}); 
      blocking = NO; 
     }); 
    } 
} 

이 '솔루션'에 대한 여러 가지를 나누기 주요 실행 루프를 펌핑하고 다른 테스트를 실행하는 원인의 문제를 가지고 나를.

GUI 코드 dispatch_sync()dispatch_async()으로 변경하고 싶지 않습니다.이 큐에 대한 올바른 동작이 아니므로 테스트에서 결과를 확인하기 전에 GUI 코드가 완료되지 않을 수도 있습니다.

아이디어를 제공해 주셔서 감사합니다.

+0

다른 스레드 또는 큐에서 단위 테스트를 실행할 수 없습니까? –

답변

2

주 코드 경로가 실행되는 방식에서 GUI 업데이트가 실행될 때까지 기다릴 필요가 없도록해야합니다. 게시 한 첫 번째 코드 블록에서 dispatch_sync은 틀림없이 (dispatch_async 대) 거의 틀림없이 주 스레드에서 기다리는 백그라운드 스레드를 차단할 것이기 때문에()이 코드로 이어질 수 있습니다 (dispatch_sync 뒤에 코드가 없음). 스레드 기아 (배치 상태). 당신이 그것을 dispatch_sync으로 만들었을 때 두 개의 병렬 작업을 연동하기 위해 대기열 자체를 사용하려고 시도한 것 같습니다. 당신이 정말 다소 차선의 방법을 사용하기 위해 최선을 다하고 있습니다 경우, 당신이 뭔가를 할 수 있습니다 : 가장 가까운 당신이 이미 가지고 무엇을 보였다 접근을했지만, 내가 할 수있는 뭔가를 내놓았다

- (void)testOne 
{ 
    SOAltUpdateView* view = [[SOAltUpdateView alloc] initWithFrame: NSMakeRect(0, 0, 100, 100)]; 

    STAssertNotNil(view, @"View was nil"); 
    STAssertEqualObjects(view.color, [NSColor redColor] , @"Initial color was wrong"); 

    dispatch_queue_t q = dispatch_queue_create("test", 0); 
    dispatch_group_t group = dispatch_group_create(); 
    view.queue = q; 


    // Run the operation 
    [view update]; 

    // An operation we can wait on 
    dispatch_group_async(group, q, ^{ }); 

    while (dispatch_group_wait(group, DISPATCH_TIME_NOW)) 
    { 
     CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, YES); 
    } 

    STAssertEqualObjects(view.color, [NSColor greenColor] , @"Updated color was wrong"); 

    view.queue = nil; 
    [view release]; 
    dispatch_release(group); 
    dispatch_release(q); 
} 

좀 더 나은/청소기 : 세마포어 당신을 위해이 연동을 할 수 있고 약간의 노력으로, 당신은 실제 GUI 코드에 대한 침입을 최소화 할 수 있습니다. (참고 : 침입을 사용하는 것은 사실상 불가능합니다. 두 개의 병렬 작업을 인터 로킹하려면 에 의 코드를 공유해야합니다. - 기존 코드에서 뭔가 공유되었습니다. 대기열, 여기서 세마포어를 사용하고 있습니다.)이 고안된 예제를 고려해보십시오. 백그라운드 작업이 완료 될 때이를 알리는 데 사용할 수있는 세마포어를 테스트 하네스에 전달하는 일반적인 방법을 추가했습니다. 테스트 할 코드의 "침입"은 두 개의 매크로로 제한됩니다.

NSObject의 + AsyncGUITestSupport.h :

@interface NSObject (AsyncGUITestSupport) 

@property (nonatomic, readwrite, assign) dispatch_semaphore_t testCompletionSemaphore; 

@end 

#define OPERATION_BEGIN(...) do { dispatch_semaphore_t s = self.testCompletionSemaphore; if (s) dispatch_semaphore_wait(s, DISPATCH_TIME_NOW); } while(0) 
#define OPERATION_END(...) do { dispatch_semaphore_t s = self.testCompletionSemaphore; if (s) dispatch_semaphore_signal(s); } while(0) 

NSObject의 + AsyncGUITestSupport.m :

#import "NSObject+AsyncGUITestSupport.h" 
#import <objc/runtime.h> 

@implementation NSObject (AsyncGUITestSupport) 

static void * const kTestingSemaphoreAssociatedStorageKey = (void*)&kTestingSemaphoreAssociatedStorageKey; 

- (void)setTestCompletionSemaphore:(dispatch_semaphore_t)myProperty 
{ 
    objc_setAssociatedObject(self, kTestingSemaphoreAssociatedStorageKey, myProperty, OBJC_ASSOCIATION_ASSIGN); 
} 

- (dispatch_semaphore_t)testCompletionSemaphore 
{ 
    return objc_getAssociatedObject(self, kTestingSemaphoreAssociatedStorageKey); 
} 

@end 

SOUpdateView.h

@interface SOUpdateView : NSView 
@property (nonatomic, readonly, retain) NSColor* color; 
- (void)update; 
@end 

SOUpdateView.m

#import "SOUpdateView.h" 
#import "NSObject+AsyncGUITestSupport.h" 

@implementation SOUpdateView 
{ 
    NSUInteger _count; 
} 

- (NSColor *)color 
{ 
    NSArray* colors = @[ [NSColor redColor], [NSColor greenColor], [NSColor blueColor] ]; 
    @synchronized(self) 
    { 
     return colors[_count % colors.count]; 
    } 
} 

- (void)drawRect:(NSRect)dirtyRect 
{ 
    [self.color set]; 
    NSRectFill(dirtyRect); 
} 

- (void)update 
{ 
    OPERATION_BEGIN(); 
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 
     sleep(1); 

     @synchronized(self) 
     { 
      _count++; 
     } 

     dispatch_async(dispatch_get_main_queue(), ^{ 
      [self setNeedsDisplay: YES]; 
      OPERATION_END(); 
     }); 
    }); 
} 

@end 

그리고 테스트 환경 :이 도움이

#import "TestSOTestGUI.h" 
#import "SOUpdateView.h" 
#import "NSObject+AsyncGUITestSupport.h" 

@implementation TestSOTestGUI 

- (void)testOne 
{ 
    SOUpdateView* view = [[SOUpdateView alloc] initWithFrame: NSMakeRect(0, 0, 100, 100)]; 

    STAssertNotNil(view, @"View was nil"); 
    STAssertEqualObjects(view.color, [NSColor redColor] , @"Initial color was wrong"); 

    // Push in a semaphore... 
    dispatch_semaphore_t sem = dispatch_semaphore_create(0); 
    view.testCompletionSemaphore = sem; 

    // Run the operation 
    [view update]; 

    // Wait for the operation to finish. 
    while (dispatch_semaphore_wait(sem, DISPATCH_TIME_NOW)) 
    { 
     CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, YES); 
    } 

    // Clear out the semaphore 
    view.testCompletionSemaphore = nil; 

    STAssertEqualObjects(view.color, [NSColor greenColor] , @"Updated color was wrong");  
} 

@end 

희망.

+0

감사. 카테고리를 사용하여 두 번째 접근 방식을 취했습니다. 여기에 지금의 모습입니다 : – stevel

+0

내가 테스트 코드가 같이 할 수 있습니다 NSObject의 + AsyncGUITestSupport 메소드 만든 : 를'[보기 prepareForOperation]'는 '[보기 업데이트]'는 '[보기 waitForOperationToFinish]' – stevel

+0

보인다 합리적인; 기꺼이 도와주세요. – ipmcc