Async/await unit test охват кода

Как написать unit test для метода async/await, я использую Visual Studio 2013.

Предположим, что у нас есть асинхронный метод:

public async Task DoSomethingAsync()
{
    ...
    await _service.DoInternalAsync();
    ...
}

Поскольку я использую последнюю версию Visual Studio, она имеет хорошую поддержку метода асинхронизации unit test:

[TestMethod]
public async Task DoSomthingAsyncTest()
{
    ...
    await _objectUnderTest.DoSomethingAsync();
    // how to verify the result??? here is what I did
    _service.Verify(_ => _.DoInternalAsync());
}

В принципе у меня есть два вопроса:

  • Как прокомментировано в коде, как проверить результат Task? Я сделал это правильно?
  • Если я запустил этот тест, VS сказал бы, что тест прошел. Но когда я проверяю покрытие кода, предложение await _service.DoInternalAsync(), кажется, не покрывается, из представления результатов покрытия кода, он предлагает предложение MoveNext() имеет 6 незакрытых блоков. Что не так в нем?

Ответ 1

Хорошо, из моего исследования проблема с распространением кода - ошибка Visual Studio в последней версии Visual Studio 2013, они исправят/улучшат ее в следующей крупной версии.

Цитата из feedback:

Проблема, которую вы видите, связана с ошибкой с нашего конца, из-за которой у нас пока нет полной поддержки шаблона async/await в охвате кода. Работа находится на рассмотрении и должна быть тем, что мы поставляем в следующем крупном обновлении/выпуске. Нет никаких обходных путей для этой проблемы.

Ответ 2

Причина, по которой код не отображается как охватываемый, связан с тем, как реализуются методы async. Компилятор С# фактически переводит код в async-методах в класс, который реализует конечный автомат, и преобразует исходный метод в заглушку, которая инициализируется и вызывает этот конечный автомат. Поскольку этот код генерируется в вашей сборке, он включен в анализ покрытия кода.

Если вы используете задачу, которая не завершена в момент выполнения кода, выполняемого, созданный с помощью компилятора конечный автомат завершает обратный вызов завершения, чтобы возобновить выполнение задачи. Это более полно реализует код конечного автомата и приводит к полному охвату кода (по крайней мере, для инструментов покрытия кода на уровне инструкций).

Обычный способ получить задачу, которая не завершена на данный момент, но в какой-то момент будет завершена, - использовать Task.Delay в unit test. Тем не менее, это, как правило, плохой вариант, поскольку временная задержка слишком мала (и приводит к непредсказуемому охвату кода, потому что иногда задача завершается до того, как проходит тест кода) или слишком большой (излишне замедляя тесты).

Лучшим вариантом является использование "ожидание Task.Yield()". Это немедленно вернется, но вызовет продолжение, как только оно будет установлено.

Еще один вариант - хотя и несколько абсурдный - заключается в том, чтобы реализовать свой собственный ожидаемый шаблон, который имеет семантику отчетности неполной до тех пор, пока не будет подключен обратный вызов продолжения, а затем для немедленного завершения. Это в основном заставляет машину состояний перейти на асинхронный путь, обеспечивая полный охват.

Конечно, это не идеальное решение. Самым печальным аспектом является то, что он требует модификации производственного кода для ограничения ограничения инструмента. Я бы предпочел, чтобы инструмент покрытия кода игнорировал части машины состояния async, которые генерируются компилятором. Но до тех пор, пока это не произойдет, существует множество вариантов, если вы действительно хотите получить полный охват кода.

Более полное объяснение этого взлома можно найти здесь: http://blogs.msdn.com/b/dwayneneed/archive/2014/11/17/code-coverage-with-async-await.aspx