2012-09-17 2 views
13

Ho letto altri post che presentano soluzioni a questa domanda. Tuttavia, le loro soluzioni richiedono l'aggiunta di un codice hacky alla mia applicazione per poterlo testare. Per me il codice pulito è più importante del test unitario.Objective C - Unità testando il blocco dispatch_async?

Uso dispatch_async nella mia app regolarmente e ho difficoltà a provarlo. Il problema è che il blocco viene eseguito dopo che il mio test è già stato eseguito, poiché è in esecuzione in modo asincrono sulla coda principale. C'è un modo per aspettare in qualche modo fino a quando il blocco viene eseguito e poi continuare con il test.

NON voglio passare un completamento al blocco solo a causa di unit test

- (viod)viewDidLoad 
{ 
    [super viewDidLoad]; 

    // Test passes on this 
    [self.serviceClient fetchDataForUserId:self.userId]; 


    // Test fails on this because it's asynchronous 
    dispatch_async(dispatch_get_main_queue(), ^{ 
     [self.serviceClient fetchDataForUserId:self.userId]; 
    }); 
} 

- (void)testShouldFetchUserDataUsingCorrectId 
{ 
    static NSString *userId = @"sdfsdfsdfsdf"; 
    self.viewController.userId = userId; 
    self.viewController.serviceClient = [[OCMockObject niceMockForClass:[ServiceClient class]]; 

    [[(OCMockObject *)self.viewController.serviceClient expect] fetchDataForUserId:userId]; 
    [self.viewController view]; 
    [(OCMockObject *)self.viewController.serviceClient verify]; 
} 

risposta

37

Eseguire brevemente il ciclo principale di lasciarlo chiamare il blocco asincrono:

- (void)testShouldFetchUserDataUsingCorrectId { 
    static NSString *userId = @"sdfsdfsdfsdf"; 
    self.viewController.userId = userId; 
    self.viewController.serviceClient = [[OCMockObject niceMockForClass:[ServiceClient class]]; 

    [[(OCMockObject *)self.viewController.serviceClient expect] fetchDataForUserId:userId]; 
    [self.viewController view]; 
    [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]]; 
    [(OCMockObject *)self.viewController.serviceClient verify]; 
} 

Suppongo che questo potrebbe fallire su un sistema pesantemente caricato, o se si dispone di un sacco di altre cose (timer o altri blocchi) andando sul thread principale. In tal caso, è necessario eseguire il ciclo di esecuzione più a lungo (che rallenta il test case) o eseguirlo più volte fino a quando non vengono soddisfatte le aspettative dell'oggetto fittizio o viene raggiunto un timeout (che richiede l'aggiunta di un metodo all'oggetto interrogare se le sue aspettative sono state soddisfatte).

+0

Utilizzando questo codice, il blocco non viene richiamato affatto, neanche dopo il test. – aryaxt

+0

Ho rivisto la mia risposta. Ho testato questo approccio in modo più approfondito. –

+0

Grazie. Questo metodo non sembra affidabile, ma non ho trovato una soluzione migliore, quindi andrò avanti e usarlo sperando che non rompa i miei test. – aryaxt

10

Avvolgere l'esecuzione in un dispatch_group e quindi attendere che il gruppo di terminare l'esecuzione di tutti i blocchi spedito via dispatch_group_wait().

+0

Molto interessante. Mai sentito parlare di dispatch_group prima. Potrei non utilizzare nuovamente NSOperationQueues. L'unico problema che avevo con GCD era la mancanza di questa funzionalità – aryaxt

+0

@aryaxt Yup, i gruppi di invio sono molto utili, ma raramente menzionati ovunque (anche i documenti ufficiali Apple non sono molto prolissi su di essi) – JustSid

+0

Questa tecnica è molto utile. Dovrebbe ricevere più ups. – e2l3n

0

Creare un wrapper dispatch_async con una firma del metodo simile che a sua volta chiama il numero reale dispatch_async. La dipendenza inserisce il wrapper nella classe di produzione e lo usa.

Quindi creare un mock wrapper, che registra i blocchi in coda e dispone di un metodo aggiuntivo per l'esecuzione in modo sincrono di tutti i blocchi accodati. Forse eseguire un "blockception" ricorsivo se i blocchi in esecuzione a turno hanno accodato più blocchi.

Nei test dell'unità, iniettare l'involucro fittizio nel sistema sottoposto a test. Puoi quindi fare in modo che tutto avvenga in modo sincrono anche se il SUT pensa che stia facendo un lavoro asincrono.

dispatch_group sembra una buona soluzione, ma richiederebbe alla tua classe di produzione di "sapere" di eseguire il ping del gruppo di invio alla fine dei blocchi in esso accodati.