Убедитесь, что задача выполняется
у меня есть следующий код, который я хочу проверить:
private Task _keepAliveTask; // get's assigned by object initializer
public async Task EndSession()
{
_cancellationTokenSource.Cancel(); // cancels the _keepAliveTask
await _logOutCommand.LogOutIfPossible();
await _keepAliveTask;
}
важно EndSession
задача заканчивается только после завершения "_keepAliveTask". Тем не менее, я изо всех сил пытаюсь найти способ проверить его надежно.
вопрос: как я могу модульный тест EndSession
метод и убедитесь в том, что Task
возвращено EndSession
ждет _keepAliveTask
.
для демонстрационных целей модульный тест может выглядеть так что:
public async Task EndSession_MustWaitForKeepAliveTaskToEnd()
{
var keepAliveTask = new Mock<Task>();
// for simplicity sake i slightly differ from the other examples
// by passing the task as method parameter
await EndSession(keepAliveTask);
keepAliveTask.VerifyAwaited(); // this is what i want to achieve
}
дополнительные критерии: - надежный тест (всегда проходит, когда реализация правильна, всегда терпит неудачу, когда реализация неверна) - не может занять больше нескольких миллисекунд (это модульный тест, в конце концов).
я уже принял несколько альтернатив в соображениях, которые я документирую ниже:
неasync
метод
если бы не было вызова _logOutCommand.LogOutIfPossible () это было бы довольно просто: я бы просто удалил async
и return _keepAliveTask
вместо await
ing it:
public Task EndSession()
{
_cancellationTokenSource.Cancel();
return _keepAliveTask;
}
модульный тест будет выглядеть (упрощенно):
public void EndSession_MustWaitForKeepAliveTaskToEnd()
{
var keepAliveTask = new Mock<Task>();
// for simplicity sake i slightly differ from the other examples
// by passing the task as method parameter
Task returnedTask = EndSession(keepAliveTask);
returnedTask.Should().be(keepAliveTask);
}
однако есть два аргумента против этого:
- у меня есть несколько задач, которые нужно ждать (я рассматриваю
Task.WhenAll
дальше) - это только перемещает ответственность, чтобы дождаться задачи вызывающему
EndSession
. Еще будет надо проверить там.
не асинхронный метод, синхронизация по асинхронному
конечно, я мог бы сделать что-то подобное:
public Task EndSession()
{
_cancellationTokenSource.Cancel(); // cancels the _keepAliveTask
_logOutCommand.LogOutIfPossible().Wait();
return _keepAliveTask;
}
но это не-go (синхронизация по асинхронности). Плюс у него все еще есть проблемы предыдущего подхода.
неasync
способ использования Task.WhenAll(...)
является (допустимым) улучшением производительности, но вводит больше сложности: - трудно получить права, не скрывая второе исключение (когда не) - позволяет параллельное выполнение
поскольку производительность здесь не является ключевой, я хотел бы избежать дополнительной сложности. Кроме того, ранее упоминалось, что он просто перемещает проблему (проверки) вызывающему объекту EndSession
метод применим и здесь.
наблюдение эффектов вместо проверки вызовов
теперь, конечно, вместо "единичных" вызовов метода тестирования и т. д. Я всегда мог наблюдать эффекты. Что: пока _keepAliveTask
не закончил EndSession
Task
не конец. Но поскольку я не могу ждать неопределенно долго, приходится довольствоваться тайм-аутом. Тесты должны быть быстрыми, поэтому тайм-аут, такой как 5 секунд, не идет. Вот что я сделал:--24-->
[Test]
public void EndSession_MustWaitForKeepAliveTaskToEnd()
{
var keepAlive = new TaskCompletionSource<bool>();
_cancelableLoopingTaskFactory
.Setup(x => x.Start(It.IsAny<ICancelableLoopStep>(), It.IsAny<CancellationToken>()))
.Returns(keepAlive.Task);
_testee.StartSendingKeepAlive();
_testee.EndSession()
.Wait(TimeSpan.FromMilliseconds(20))
.Should().BeFalse();
}
но мне очень не нравится этот подход:
- трудно понять
- неблагонадежных
- или - когда это совсем надежный - это занимает много времени (что юнит-тесты не должны).
2 ответов
если все, что вы хотите, это проверить, что EndSession
ждет _keepAliveTask
(и у вас действительно есть полный контроль над _keepAliveTask
) тогда вы можете создать свой собственный ожидаемый тип вместо Task
сигналы, когда его ждут, и проверьте, что:
public class MyAwaitable
{
public bool IsAwaited;
public MyAwaiter GetAwaiter()
{
return new MyAwaiter(this);
}
}
public class MyAwaiter
{
private readonly MyAwaitable _awaitable;
public MyAwaiter(MyAwaitable awaitable)
{
_awaitable = awaitable;
}
public bool IsCompleted
{
get { return false; }
}
public void GetResult() {}
public void OnCompleted(Action continuation)
{
_awaitable.IsAwaited = true;
}
}
так как все, что вам нужно await
что-то, что имеет GetAwaiter
метод, который возвращает что-то С IsCompleted
, OnCompleted
и GetResult
вы можете использовать манекен ожидать, чтобы убедиться _keepAliveTask
будет ждали:
_keepAliveTask = new MyAwaitable();
EndSession();
_keepAliveTask.IsAwaited.Should().BeTrue();
если вы используете некоторые насмешливые рамки, вы можете вместо этого сделать Task
' s GetAwaiter
вернуться наши MyAwaiter
.
- использовать
TaskCompletionSource
и установить его результат в известное время. - убедитесь, что перед установкой результата await on EndSession не завершен.
- убедитесь, что после установки результата await on EndSession завершен.
упрощенная версия может выглядеть следующим образом (с помощью nunit):
[Test]
public async Task VerifyTask()
{
var tcs = new TaskCompletionSource<bool>();
var keepAliveTask = tcs.Task;
// verify pre-condition
Assert.IsFalse(keepAliveTask.IsCompleted);
var waitTask = Task.Run(async () => await keepAliveTask);
tcs.SetResult(true);
await waitTask;
// verify keepAliveTask has finished, and as such has been awaited
Assert.IsTrue(keepAliveTask.IsCompleted);
Assert.IsTrue(waitTask.IsCompleted); // not needed, but to make a point
}
вы также можете добавить короткую задержку в waitTask, чтобы гарантировать, что любое синхронное выполнение будет быстрее, что-то например:
var waitTask = Task.Run(async () =>
{
await Task.Delay(1);
await keepAliveTask;
});
и если вы не доверяете своей платформе модульного тестирования для правильной работы с async, вы можете установить завершенный флаг как часть waitTask и проверить это в конце. Что-то вроде:
bool completed = false;
var waitTask = Task.Run(async () =>
{
await Task.Delay(1);
await keepAliveTask;
completed = true;
});
// { .... }
// at the end of the method
Assert.IsTrue(completed);