2016-01-14 49 views
5

Stiamo scrivendo unit test per codice asincrono usando MSTest e Moq.Mocking Async Methods

Così abbiamo un codice che sembra qualcosa di simile:

var moq = new Mock<Foo>(); 
moq.Setup(m => m.GetAsync()) 
    .Returns(Task.FromResult(10)); 

O come questo su progetti che hanno una versione più recente di Moq

var moq = new Mock<Foo>(); 
moq.Setup(m => m.GetAsync()) 
    .ReturnsAsync(10); 

Guardando l'attuazione Moq di ReturnsAsync:

public static IReturnsResult<TMock> ReturnsAsync<TMock, TResult>(this IReturns<TMock, Task<TResult>> mock, TResult value) where TMock : class 
{ 
    TaskCompletionSource<TResult> completionSource = new TaskCompletionSource<TResult>(); 
    completionSource.SetResult(value); 
    return mock.Returns(completionSource.Task); 
} 

Entrambi i metodi sembrano essere gli stessi sotto il cofano. Entrambi creano TaskCompletionSource, chiamano SetResult e restituiscono il Task

Fin qui tutto bene.

Ma i metodi corti in esecuzione async sono ottimizzati per agire in modo sincrono. Ciò sembra implicare che TaskCompletionSource sia sempre sincrono, il che sembrerebbe anche suggerire che la gestione del contesto e eventuali problemi correlati che potrebbero verificarsi non si verifichino mai.

Quindi, se abbiamo avuto qualche codice che stava facendo alcuni async no-no, come la miscelazione awaits, Wait() e Result, che questi problemi non sarebbero rilevati nel test di unità.

Potrebbe esserci qualche vantaggio nel creare un metodo di estensione che fornisce sempre il controllo? Qualcosa di simile:

public async Task<T> ReturnsYieldingAsync<T>(T result) 
{ 
    await Task.Yield(); 
    return result; 
} 

In questo caso avremmo un metodo che è garantito per eseguire in modo asincrono.

Il vantaggio percepito potrebbe rilevare un codice asincrono errato. Ad esempio, potrebbe catturare qualsiasi deadlocking o eccezione durante la deglutizione durante il test dell'unità.

Non sono sicuro al 100% che sia così, quindi sarei davvero interessato a sentire ciò che la comunità ha da dire.

+0

Direi andare per 'ReturnsYieldingAsync', ma ancor più, per sperimentare potenziali deadlock, è necessario installare un contesto di sincronizzazione nei test runner, qualcosa come [' AsyncPump'] (http: // blogs. msdn.com/b/pfxteam/archive/2012/01/20/10259049.aspx). – Noseratio

+0

Penso che dovresti provare a scrivere un test che dimostra 1. funzionerà come previsto e 2. puoi creare un caso di test che segue esattamente il caso di test da 1 ma non chiama mai 'ReturnsYieldingAsync' e fallisce. –

+1

@Noseratio Bella scoperta su 'AsyncPump'. Il problema principale che ritengo di riscontrare è che il test in esecuzione è che il contesto di sincronizzazione del test runner è nullo. Scambiare il 'AsyncPump' sembra essere la strada da percorrere, come mostrato qui http: // StackOverflow.it/questions/14087257/how-to-add-synchronization-contest-to-async-test-method – swestner

risposta

2

Quando ho iniziato a utilizzare talking about testing asynchronous code quattro anni fa (!), Incoraggerei gli sviluppatori a testare l'asse asincrono per i loro mock. Vale a dire, testare l'asse dei risultati (successo, errore) e l'asse asincrono (sincronizzazione, asincrona).

Nel corso del tempo, tuttavia, mi sono allentato. Quando si tratta di testare il mio codice, in realtà testo solo i percorsi sincroni di successo/fallimento, a meno che non ci sia un chiaro motivo per provare anche il percorso di successo asincrono per quel codice. Non mi preoccupo più di test di errore asincrono.

+0

Nel nostro caso ci siamo imbattuti in una serie di esempi di pattern asincroni misti. In gran parte questo è un problema di formazione, ma è anche qualcosa che non possiamo trascurare. La tua libreria Nito ci ha aiutato a riprodurre costantemente i deadlock. Grazie! Ultimatley anche se probabilmente stiamo andando a testare questi tipi di problemi attraverso i test E2E. – swestner

+0

Ho una curiosità eccezionale che è stata fondamentale per la domanda originale. Sai se 'TaskCompletionSource' esegue sempre in modo sincrono? Era una supposizione della domanda originale che sarebbe stata piacevole sapere. – swestner

+0

@swestner: Se si chiama 'SetResult' (o simile), l'attività viene completata prima che la chiamata al metodo ritorni. Potrebbero comunque esserci continuazioni di quell'attività che non sono state completate. –