Модульное тестирование async-методов
Задача: Имеется async-метод, для которого необходимо провести модульное тестирование.
Решение
Многие современные фреймворки модульного тестирования, включая MSTest, NUnit и xUnit, поддерживают методы модульного тестирования async Task. Пример асинхронного модульного теста в MSTest:
[TestMethod]Фреймворк модульного тестирования замечает, что метод возвращает
public async Task MethodAsync_False()
{
var sut = …;
var result = await sut.MethodAsync();
Assert.IsFalse(result);
}
Task, и ожидает завершения задачи перед тем, как сделать отметку о прохождении или отказе теста.Если ваш фреймворк модульного тестирования не поддерживает модульные тесты с
async Task, то ему придётся помочь с ожиданием тестируемой асинхронной операции. Один из вариантов — использовать GetAwaiter().GetResult() для синхронного блокирования по задаче. Использование GetAwaiter().GetResult() вместо Wait() позволит избежать обёртки в AggregateException, когда в задаче произойдёт исключение. Имитация (mocking) асинхронных зависимостей на первый взгляд кажется немного неуклюжей. Всегда желательно хотя бы проверить, как ваши методы реагируют на синхронный успех (имитация с
Task.FromResult), синхронные ошибки (имитация с Task.FromException) и асинхронный успех (имитация с Task.Yield и возвратом значения). Task.FromResult и Task.FromException рассматривались здесь. Task.Yield может использоваться для принудительного применения асинхронного поведения и задействуется прежде всего при модульном тестировании:interface IMyInterfaceПри тестировании асинхронного кода взаимоблокировки и состояния гонки могут проявляться чаще, чем при тестировании синхронного кода. Может быть полезным назначение тайм-аута тестам. Тестовому методу можно задать атрибут
{
Task<int> SomeAsync();
}
class SyncSuccess : IMyInterface
{
public Task<int> SomeAsync()
{
return Task.FromResult(42);
}
}
class SyncError : IMyInterface
{
public Task<int> SomeAsync()
{
return Task.FromException<int>(
new InvalidOperationException());
}
}
class AsyncSuccess : IMyInterface
{
public async Task<int> SomeAsync()
{
// Принудительно включаем асинхронное поведение
await Task.Yield();
return 42;
}
}
Timeout(int timeOut), передав ему количество миллисекунд таймаута. Значение по умолчанию - TestTimeout.Infinite ("бесконечно").Источник: Стивен Клири “Конкурентность в C#”. 2-е межд. изд. — СПб.: Питер, 2020. Глава 7.