День 1136. #ЗаметкиНаПолях #AsyncTips
Модульное тестирование 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.