я недавно пишу асинхронную метод, который вызывает внешний длинный ход асинхронная метод, поэтому я решил передать CancellationToken позволяющих отменить. Метод можно вызвать одновременно.Единицы Метод испытания асинхронный: Как явно утверждать, что внутренняя задача была отменена
Реализации объединила экспоненциальную отсрочку передачи и тайма-аута методов, описанных в Stephen Cleary «ы Книги Параллелизм в C# Поваренной следующим образом;
/// <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();
}
}
}
}
Интересно, если я должен написать модульный тест, который явно утверждает, что внутренняя задача barService.GetBarAsync()
отменяется, когда FooAsync()
отменяется. Если да, то как его реализовать чисто?
Кроме того, следует ли игнорировать детали реализации и просто проверять, что такое клиент/вызывающий, как описано в сводке методов (строка обновлена, триггеры отмены OperationCanceledException
, таймеры тайм-аута TimeoutException
).
Если нет, то я должен получить мои ноги мокрые и приступить к реализации модульных тестов для следующих случаев:
- Тестирование является поточно-(монитор приобретается только по одной нити в то время)
- Тестирование повторить механизм
- Тестирование сервера не затопило
- Тестирование может быть, даже регулярное исключение передается вызывающему абоненту
Это больше касается философии тестирования, поэтому, вероятно, нет окончательного ответа, но я склоняюсь к тестированию только открытого интерфейса. BarService - это собственный тип, поэтому вы должны проверить это своими собственными тестами, а не с этим. Тестирование комбинации, как вы предлагаете, скорее проверяет механизм отмены CLR, чем ваш код, и мы можем предположить, что все в порядке. Если, как я думаю, вы подразумеваете, что BarService является внутренним, вы можете использовать InternalsVisibleTo (только для сборки тестов), чтобы его можно было тестировать (но некоторые наверняка не согласятся). – sellotape
@sellotape Я тестирую 'FooAsync()' на своем собственном приспособлении (Foo) _mocking_ 'BarService.GetBarAsync()'. Тем не менее, он реагирует на поведение зависимостей, и это то, что я рассматриваю для тестирования. Так что в основном; если вы подразумеваете, что я должен только проверять общее поведение, а не детали реализации, вы думаете, что я должен проверить на _thread safety_, учитывая, что я заявляю, что метод является потокобезопасным. –
Если вы издеваетесь над этим, то нет ничего действительно проверить _ специально для него, и вы можете протестировать его изолированно. Re потоковой безопасности, да, вы, вероятно, должны проверить этот аспект, если это часть публичного контракта. Я оставлю вам более тонкие детали, но первоначальная мысль состоит в том, чтобы утверждать, что GetBarAsync() вызывается только до определенного момента времени, когда FooAsync() вызывается из 2 потоков примерно в одно и то же время. Вы можете настроить метод GetBarAsync() (mocked), чтобы немного задержаться, чтобы избежать слишком большой гонки. – sellotape