Убедитесь, что задача выполняется

у меня есть следующий код, который я хочу проверить:

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 вместо awaiting 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.


  1. использовать TaskCompletionSource и установить его результат в известное время.
  2. убедитесь, что перед установкой результата await on EndSession не завершен.
  3. убедитесь, что после установки результата 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);