Byłem niedawno pisanie asynchronicznej metodę, która wywołuje zewnętrzny długi bieg asynchroniczny metoda więc postanowiłem przejść CancellationToken umożliwiającą anulowanie. Metodę można nazwać jednocześnie.Jednostka Metoda badania asynchroniczny: Jak wyraźnie stwierdzić, że zadanie zostało anulowane wewnętrzny
Wykonanie łączy odczekiwanie wykładniczym i limitu czasu technik opisanych w Stephen Cleary jest książka współbieżności C# Cookbook w następujący sposób;
/// <summary>
/// Sets bar
/// </summary>
/// <param name="cancellationToken">The cancellation token that cancels the operation</param>
/// <returns>A <see cref="Task"/> representing the task of setting bar value</returns>
/// <exception cref="OperationCanceledException">Is thrown when the task is cancelled via <paramref name="cancellationToken"/></exception>
/// <exception cref="TimeoutException">Is thrown when unable to get bar value due to time out</exception>
public async Task FooAsync(CancellationToken cancellationToken)
{
TimeSpan delay = TimeSpan.FromMilliseconds(250);
for (int i = 0; i < RetryLimit; i++)
{
if (i != 0)
{
await Task.Delay(delay, cancellationToken);
delay += delay; // Exponential backoff
}
await semaphoreSlim.WaitAsync(cancellationToken); // Critical section is introduced for long running operation to prevent race condition
using (CancellationTokenSource cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
{
cancellationTokenSource.CancelAfter(TimeSpan.FromMilliseconds(Timeout));
CancellationToken linkedCancellationToken = cancellationTokenSource.Token;
try
{
cancellationToken.ThrowIfCancellationRequested();
bar = await barService.GetBarAsync(barId, linkedCancellationToken).ConfigureAwait(false);
break;
}
catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
{
if (i == RetryLimit - 1)
{
throw new TimeoutException("Unable to get bar, operation timed out!");
}
// Otherwise, exception is ignored. Will give it another try
}
finally
{
semaphoreSlim.Release();
}
}
}
}
Zastanawiam się, czy powinienem napisać badanej jednostki, która wyraźnie stwierdza, że zadanie zostanie anulowane wewnętrzny barService.GetBarAsync()
ilekroć FooAsync()
zostanie anulowane. Jeśli tak, jak go wdrożyć w czysty sposób?
Czy powinienem zignorować szczegóły implementacji i po prostu przetestować, który klient/dzwoniący jest zainteresowany, jak opisano w podsumowaniu metody (pasek jest aktualizowany, anulować wyzwalacze OperationCanceledException
, limit czasu wyzwala TimeoutException
).
Jeśli nie, powinienem dostać moje nogi mokre i rozpoczęcie wdrażania testy jednostkowe w następujących przypadkach:
- Testowanie jest thread-safe (monitor nabyte tylko przez jeden wątek na raz)
- Testowanie ponowić mechanizm
- Testowanie serwer nie jest zalany
- Testowanie może nawet regularne wyjątek jest propagowana do dzwoniącego
To jest więcej o testowaniu filozofii, więc prawdopodobnie nie ma jednoznacznej odpowiedzi, ale skłaniam się do testowania interfejsu publicznego. BarService jest własnym typem, więc powinieneś przetestować to z własnymi testami, a nie z tym. Testowanie kombinacji, jak sugerujesz, jest bardziej testem mechanizmu anulowania CLR niż kodu, i powinniśmy być w stanie założyć, że to w porządku. Jeśli - jak sądzę, sugerujesz - BarService jest wewnętrzna, możesz użyć InternalsVisibleTo (tylko dla zestawu testów), aby umożliwić testowanie (ale niektóre na pewno się nie zgodzą). – sellotape
@ labelotape Testuję 'FooAsync()' na własnym urządzeniu (Foo) _mokowanie_ 'BarService.GetBarAsync()'. Jednak reaguje na zachowanie zależności i właśnie to rozważam na temat testowania. Więc zasadniczo; jeśli sugerujesz, że powinienem przetestować tylko ogólne zachowanie zamiast szczegółów implementacji, czy uważasz, że powinienem przetestować przeciwko _twanemu bezpieczeństwu_ biorąc pod uwagę, że twierdzę, że metoda jest bezpieczna dla wątków. –
Jeśli kpisz z niego, to nie ma nic do przetestowania _do tego specjalnie_ i możesz przetestować go w izolacji. W związku z bezpieczeństwem, tak, prawdopodobnie musisz "przetestować ten aspekt, jeśli jest częścią kontraktu publicznego. Zostawię ci szczegóły, ale początkowo myślę, że GetBarAsync() jest wywoływana tylko raz w określonym momencie, kiedy FooAsync() jest wywoływana z 2 wątków mniej więcej w tym samym czasie. Możesz skonfigurować metodę GetBarAsync() (kpiącą), aby trochę opóźnić, aby uniknąć zbyt dużej ilości wyścigu. – sellotape