2013-06-05 8 views
7

Sto usando il Moq per testare l'unità di un codice che include un ciclo Parallel.foreach.Perché un test di unità .NET copre un loop di Parallel.foreach dipendente dall'hardware?

Il Disponi la fase imposta 4 eccezioni da gettare all'interno del ciclo e quindi avvolto in un AggregateException.

Questo è passato sul mio processore i7 e ho controllato il codice.

In seguito, un collega si è lamentato che non gli passava accanto. Si è scoperto che lo Parallel.foreach stava generando solo 2 thread sul suo Core2duo prima di bombardare e quindi solo 2 eccezioni sono state incapsulate nello AggregateException.

La domanda è cosa fare al riguardo, quindi il test dell'unità non dipende dall'architettura del processore? Un paio di pensieri: -

  1. C'è un Microsoft article su come aggiungere manualmente le eccezioni al il AggregateException ma non siamo interessati a fare questo come il ciclo dovrebbe uscire il più presto possibile, se c'è un problema.
  2. ParallelOptions.MaxDegreeOfParallelism can porre un limite superiore al numero di thread utilizzati. Ma a meno che ciò sia abbassato a 1 (che sembra più imbroglio di prova adeguata unit) come può unit test sapere quanti thread sarà effettivamente utilizzato e quindi impostare il Disporre e asserzione fasi propertly?
+2

Il numero di eccezioni child non è un requisito e non deve essere testato in questo modo. È un dettaglio di implementazione. È possibile verificare 1+ eccezioni del tipo corretto. –

+0

@HenkHolterman Vero ma devo impostare il simulatore in modo appropriato. Stai suggerendo che dovrei impostare il numero massimo di chiamate simulate possibili, ma non asserire che tutte queste sono state effettivamente invocate alla fine? –

+0

Non sono sicuro di quale parte vuoi prendere in giro ma non dovrebbe avere importanza. –

risposta

6

Non si dovrebbe testare qualcosa del genere: sono i dettagli di implementazione.

Il momento è - Parallel.ForEach elaborerà gli elementi finché non ottiene l'eccezione. Quando si verifica un'eccezione, smetterà di processare qualsiasi nuovo elemento (ma terminerà l'elaborazione di quelli attualmente elaborati) e quindi di generare AgregateException.

Ora - la CPU i7 ha 4 core + Hyper Threading, il che si traduce in più thread generati per l'elaborazione, quindi è possibile ottenere più eccezioni (perché per esempio 4 cose possono essere elaborate nello stesso momento in cui si verifica un'eccezione). Ma su Core2Duo con solo 2 core, solo 2 elementi verranno elaborati contemporaneamente (è perché TPL è abbastanza intelligente da creare solo thread sufficienti per l'elaborazione, non più dei core disponibili).

Verificare che effettivamente si sia verificata un'eccezione 4 non fornisce alcuna conoscenza. Dipende dalla macchina. Dovresti invece verificare se si è verificata almeno un'eccezione, poiché è ciò che ti aspetti. Se l'utente futuro eseguirà il codice su una macchina single core vecchio riceverà solo questa eccezione in AggregateException.

Il numero di eccezioni generate è specifico della macchina, come ad esempio il tempo di calcolo - non si asserire su quanto tempo è stato calcolato, quindi in questo caso non si deve far valere il numero di eccezioni.

+2

BTW, non si tratta solo della CPU. Il comportamento specifico può anche cambiare a seconda di ciò che è in esecuzione sul ThreadPool nello stesso processo, quali altri processi stanno utilizzando la CPU, se l'operazione nel ciclo sta bloccando e forse altre cose. – svick

+0

Sì, è assolutamente vero. La relazione della CPU è la più semplice da individuare, ma tutto ciò che hai menzionato è altrettanto valido. – Pako

+0

@Pako Grazie - la tua risposta spiega molto bene il problema. A quanto ho capito, la soluzione è impostare il mock per consentire il numero massimo di eccezioni (= thread), ma in realtà non asserire che si siano verificati tutti. Questo mi sembra un po 'insolito in quanto normalmente quando sto prendendo in giro sono abituato a verificare tutte le configurazioni. –

1

Il test di unità verifica che ciò che si aspetta che accada e ciò che accade effettivamente sia la stessa cosa. Ciò significa che devi chiederti cosa ti aspetti che accada.

Se si prevede che tutti i potenziali errori nell'elaborazione vengano segnalati, la corsa del collega ha rilevato un bug nel codice.Ciò significa che lo standard Parallel.ForEach() non funzionerà per te, ma qualcosa come Parallel.ForEach() con un try - catch e l'elaborazione di un'eccezione personalizzata lo farebbe.

Se quello che ti aspetti è che ogni eccezione generata verrà generata, allora è quello che devi testare. Ciò potrebbe complicare il tuo test o potrebbe non essere possibile testare, a seconda di come stai gettando le eccezioni. Un semplice test per questo sarebbe simile a questa:

[Test] 
public void ParallelForEachExceptionsTest() 
{ 
    var exceptions = new ConcurrentQueue<Exception>(); 
    var thrown = Assert.Throws<AggregateException>(
     () => Parallel.ForEach(
      Enumerable.Range(1, 4), i => 
      { 
       var e = new Exception(i.ToString()); 
       exceptions.Enqueue(e); 
       throw e; 
      })); 

    CollectionAssert.AreEquivalent(exceptions, thrown.InnerExceptions); 
} 

Se ciò che vi aspettate è che quando una o più eccezioni sono gettati, almeno alcuni di loro saranno presi, allora questo è ciò che si dovrebbe provare, e si non preoccuparti se tutte le eccezioni sono state rilevate correttamente.

+0

Mille grazie per i tuoi sforzi qui. È il secondo caso - ogni eccezione * lanciata * deve essere catturata. Le eccezioni generate sono in realtà provenienti da una chiamata di metodo su un oggetto deriso. Quindi, come dici tu, ho solo bisogno di verificare che un sottoinsieme di questi siano effettivamente lanciati. –

Problemi correlati