2016-01-14 24 views
5

Piszemy testy jednostkowe dla kodu asynchronicznego przy użyciu MSTest i Moq.Makiety metod asynchronicznych

Mamy więc jakiś kod, który wygląda mniej więcej tak:

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

Albo jak to się na projektach, które mają nowszą wersję Min

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

Patrząc na realizację Min z 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); 
} 

Obie metody wydają się być takie same pod maską. Oba tworzą TaskCompletionSource, wywołują SetResult i zwracają wartość Task Do tej pory tak dobrze.

Ale krótko pracujące metody async są zoptymalizowane do działania synchronicznego. Wydaje się to sugerować, że TaskCompletionSource jest zawsze synchroniczna, co również sugerowałoby, że obsługa kontekstu i wszelkie związane z tym problemy, które mogą wystąpić, nigdy nie miałyby miejsca.

Więc gdybyśmy mieli jakiś kod, który robił jakieś async nie-nie, podobnie jak mieszanie awaits, Wait() i Result, że problemy te nie zostaną wykryte podczas testów jednostkowych.

Czy istniała jakaś korzyść w tworzeniu metody rozszerzenia, która zawsze daje kontrolę? Coś takiego:

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

W tym przypadku mamy metodę, która jest gwarantowana do wykonywania asynchronicznie.

Dostrzeganą zaletą byłoby wykrycie złego asynchronicznego kodu. Na przykład może złapać wszelkie zakleszczenia lub przypadkowe połknięcie podczas testowania jednostkowego.

Nie jestem w 100% pewien, że tak jest, więc naprawdę chciałbym usłyszeć, co społeczność ma do powiedzenia.

+0

Powiedziałbym, idź na "ReturnsYieldingAsync", ale jeszcze bardziej, aby doświadczyć potencjalnych zakleszczeń, które trzeba zainstalować kontekst synchronizacji w testowych biegaczy, coś jak ['AsyncPump'] (http: // blogs. msdn.com/b/pfxteam/archive/2012/01/20/10259049.aspx). – Noseratio

+0

Myślę, że powinieneś spróbować napisać przypadek testowy, który udowodni, że 1. będzie działał zgodnie z oczekiwaniami i 2. możesz utworzyć case test, który następuje po przypadku testowym od 1 dokładnie, ale nigdy nie wywołuje 'ReturnsYieldingAsync' i kończy się niepowodzeniem. –

+1

@Noseratio Ładne znalezisko na 'AsyncPump'. Główny problem, o którym myślę, że napotykamy, polega na tym, że test uruchamia się w taki sposób, że kontekst synchronizacji testera jest zerowy. Zamiana 'AsyncPump' wydaje się być sposobem na przejście, jak pokazano tutaj: http: // stackoverflow.com/questions/14087257/how-to-add-synchronization-context-to-async-test-method – swestner

Odpowiedz

2

Kiedy po raz pierwszy uruchomiłem talking about testing asynchronous code cztery lata temu (!), Zachęciłbym programistów do testowania wzdłuż osi asynchronicznej dla ich makiet. To znaczy przetestuj wzdłuż osi wyniku (sukces, niepowodzenie), a także osi asynchronicznej (synchronizacja, asynchronizacja).

Z czasem jednak się rozluźniłem. Jeśli chodzi o testowanie własnego kodu, naprawdę testuję tylko synchroniczne ścieżki sukces/niepowodzenie, chyba że istnieje wyraźny powód, aby przetestować asynchroniczną ścieżkę sukcesu dla tego kodu. Nie zajmuję się już asynchronicznymi testami usterek.

+0

W naszym przypadku natrafiliśmy na wiele przypadków mieszanych asynchronicznych wzorców. W dużej mierze jest to kwestia szkoleniowa, ale jest to również coś, czego nie możemy przeoczyć. Twoja biblioteka Nito pomogła nam konsekwentnie odtworzyć impasy. Dzięki! Ostatecznie jednak prawdopodobnie będziemy testować tego typu problemy za pomocą testów E2E. – swestner

+0

Mam jedną wyjątkową ciekawość, która była podstawą pierwotnego pytania. Czy wiesz, czy 'TaskCompletionSource' zawsze działa synchronicznie? Było to założenie pierwotnego pytania, które byłoby miło wiedzieć. – swestner

+0

@swestner: Jeśli wywołasz funkcję 'SetResult' (lub podobną), zadanie zostanie wykonane przed zwróceniem wywołania metody. Nadal mogą istnieć kontynuacje tego zadania, które nie zostały jeszcze zakończone. –