2013-11-03 8 views
10

Sono abbastanza familiare con il modello asincrono/atteso, ma mi imbatto in un comportamento che mi sembra strano. Sono sicuro che c'è una ragione perfettamente valida per cui sta accadendo, e mi piacerebbe capire il comportamento.Comportamento imprevisto quando si passano azioni asincrone attorno a

Lo sfondo qui è che sto sviluppando un'app di Windows Store, e dal momento che sono uno sviluppatore cauto e coscienzioso, sono un'unità che testa tutto. Ho scoperto abbastanza rapidamente che lo ExpectedExceptionAttribute non esiste per i WSA. Strano, vero? Bene, nessun problema! Posso più o meno replicare il comportamento con un metodo di estensione! Così ho scritto questo:

public static class TestHelpers 
{ 
    // There's no ExpectedExceptionAttribute for Windows Store apps! Why must Microsoft make my life so hard?! 
    public static void AssertThrowsExpectedException<T>(this Action a) where T : Exception 
    { 
     try 
     { 
      a(); 
     } 
     catch (T) 
     { 
      return; 
     } 

     Assert.Fail("The expected exception was not thrown"); 
    } 
} 

Ed ecco, funziona magnificamente.

Così ho continuato felicemente a scrivere i miei test di unità, fino a quando non ho premuto un metodo asincrono che volevo confermare genera un'eccezione in determinate circostanze. "Nessun problema", pensai tra me e me, "posso passare in un lambda asincrono!"

Così ho scritto questo metodo di prova:

[TestMethod] 
public async Task Network_Interface_Being_Unavailable_Throws_Exception() 
{ 
    var webManager = new FakeWebManager 
    { 
     IsNetworkAvailable = false 
    }; 

    var am = new AuthenticationManager(webManager); 
    Action authenticate = async() => await am.Authenticate("foo", "bar"); 
    authenticate.AssertThrowsExpectedException<LoginFailedException>(); 
} 

Questo, a sorpresa, getta un errore di runtime. In realtà si blocca il test-runner!

ho fatto un sovraccarico del mio AssertThrowsExpectedException metodo:

public static async Task AssertThrowsExpectedException<TException>(this Func<Task> a) where TException : Exception 
{ 
    try 
    { 
     await a(); 
    } 
    catch (TException) 
    { 
     return; 
    } 

    Assert.Fail("The expected exception was not thrown"); 
} 

e ho ottimizzato la mia prova:

[TestMethod] 
public async Task Network_Interface_Being_Unavailable_Throws_Exception() 
{ 
    var webManager = new FakeWebManager 
    { 
     IsNetworkAvailable = false 
    }; 

    var am = new AuthenticationManager(webManager); 
    Func<Task> authenticate = async() => await am.Authenticate("foo", "bar"); 
    await authenticate.AssertThrowsExpectedException<LoginFailedException>(); 
} 

Io sto bene con la mia soluzione, sto solo chiedendo esattamente perché tutto va a forma di pera quando provo a invocare il async Action. Sto indovinando perché, per quanto riguarda il runtime, è non e uno Action, sto solo stipando il lambda dentro. So che Lambda sarà felicemente assegnata a Action o Func<Task>.

+3

Nei test di Windows Store, utilizzare 'Assert.ThrowsException', che ([come da VS2012 Update 2] (http://support.microsoft.com/kb/2797912)) supporta 'asdd' lambda. Si noti che 'Action' è un metodo * sincrono * senza un valore di ritorno, mentre' Func 'è un metodo * asincrono * senza un valore di ritorno. –

+0

Ooh, non ho notato "Assert.ThrowsException". Passerò i miei test per usarlo. –

risposta

6

Non è sorprendente che può bloccare il tester, nel secondo scenario frammento di codice:

Action authenticate = async() => await am.Authenticate("foo", "bar"); 
authenticate.AssertThrowsExpectedException<LoginFailedException>(); 

In realtà è un fuoco-e-dimentica invocazione del an async void method, quando si chiama l'azione:

try 
{ 
    a(); 
} 

a() restituisce immediatamente, così come il metodo AssertThrowsExpectedException. Allo stesso tempo, alcune attività avviate all'interno di am.Authenticate possono continuare a essere eseguite in background, probabilmente su un thread di pool. Che cosa sta succedendo esattamente dipende dall'implementazione di am.Authenticate, ma potrebbe mandare in crash il tuo tester più tardi, quando tale operazione async è completata e genera LoginFailedException. Non sono sicuro di quale sia il contesto di sincronizzazione dell'ambiente di esecuzione del test dell'unità, ma se utilizza il valore predefinito SynchronizationContext, in questo caso è possibile che l'eccezione venga generata inosservata su un thread diverso.

VS2012 supporta automaticamente i test delle unità asincrone, a condizione che le firme del metodo di prova siano async Task. Quindi, penso che tu abbia risposto alla tua stessa domanda usando await e Func<T> per il tuo test.

+0

Si dovrebbe anche notare che nel metodo asincrono che si sta testando, si deve lanciare l'errore sul thread dal quale è stato chiamato il metodo o che non sarà catchable dal chiamante e causerà comunque un'eccezione di runtime anche con la propria soluzione. –

+0

@ geezer498, credo che la soluzione dell'OP dove fa 'try {await a(); } catch ... 'è corretto. – Noseratio

+1

+1. Cordiali saluti, MSTest utilizza l'utilità di pianificazione predefinita (pool di thread), quindi l'eccezione viene generata su un thread pool di thread, probabilmente causando il crash del test runner. Dico "possibilmente" perché tecnicamente c'è una condizione di competizione tra il corridore di prova e l'eccezione; ad esempio, se l'eccezione è stata ritardata, il test runner potrebbe effettivamente terminare prima che si arrestasse in modo anomalo. –

Problemi correlati