Асинхронные модульные тесты не работают должным образом

Я использую последнюю версию NUnit (2.6.2) в Visual Studio 2012, используя как resharper, так и средство запуска тестов Visual Studio. У меня есть следующие примеры тестов, в которых я пытаюсь проверить, возникает ли исключение при ожидаемом вызове асинхронного метода.

К сожалению, это не работает, как ожидалось. Первый тест AsyncTaskCanceledSemiWorking работает только потому, что у меня есть атрибут ожидаемого исключения. Фактическое утверждение полностью игнорируется (как вы можете видеть по исключению ArgumentOutOfRange, которое является просто подделкой, чтобы заставить его потерпеть неудачу).

AsyncTaskCanceledWorking работает нормально, но не проверяет, что исключение выдается в указанной строке, поэтому менее полезно.

Третий величественно терпит неудачу с приведенным ниже ....

System.Threading.Tasks.TaskCanceledException : A task was canceled.
Exception doesn't have a stacktrace

Любые идеи о том, как я могу проверить TaskCanceledException из определенной строки, были бы очень полезны.

Спасибо

    [Test]
    [ExpectedException(typeof(TaskCanceledException))]
    public async Task AsyncTaskCanceledSemiWorking()
    {
        CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
        CancellationToken token = cancellationTokenSource.Token;

        cancellationTokenSource.Cancel();

        Assert.That(await LongRunningFunction(token), Throws.InstanceOf<ArgumentOutOfRangeException>());


    }

    [Test]
    [ExpectedException(typeof(TaskCanceledException))]
    public async Task AsyncTaskCanceledWorking()
    {
        CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
        CancellationToken token = cancellationTokenSource.Token;

        cancellationTokenSource.Cancel();

        int i = await LongRunningFunction(token);
    } 


    [Test]
    public async Task AsyncTaskCanceledFailed()
    {
        CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
        CancellationToken token = cancellationTokenSource.Token;

        cancellationTokenSource.Cancel();

        Assert.That(await LongRunningFunction(token), Throws.InstanceOf<TaskCanceledException>());

    }

    public async Task<int> LongRunningFunction(CancellationToken token)
    {
        token.ThrowIfCancellationRequested();

        await Task.Delay(1000, token);

        return 5;
    } 

person Peter    schedule 24.10.2012    source источник
comment
Привет, Питер, было бы полезно узнать, какое поведение вы ожидаете вместо этого.   -  person Simone    schedule 25.10.2012


Ответы (3)


Я предполагаю, что вы хотите проверить, что LongRunningFunction выдаст TaskCanceledException.

Я думаю, что поведение, которое вы испытываете, совершенно правильно, и недоразумение заключается в этом утверждении:

await LongRunningFunction(token)

Здесь вы фактически выполняете асинхронную операцию и ждете ее завершения, что также повторно вызовет первое исключение, возникшее при ее вызове. Вы можете в основном заменить это на:

throw new TaskCanceledException()

Следовательно, почему первые два теста успешны - вы используете ExpectedExceptionAttribute - а третий терпит неудачу - вы не ожидаете исключения.

Первым аргументом Assert.That, когда вы позже будете использовать Throws, должен быть какой-то делегат, так как NUnit должен будет вызвать его, чтобы перехватить исключение, возникающее при его вызове. Если вы вызовете его самостоятельно, у NUnit, конечно же, не будет возможности перехватить исключение, кроме использования ExpectedExceptionAttribute.

Другими словами, идеально правильный способ сделать это:

// WARNING: this code does not work in NUnit <= 2.6.2
Assert.That(async () => await LongRunningFunction(token), Throws.InstanceOf<TaskCanceledException>());

Я хотел бы сказать вам, что NUnit поддерживает этот синтаксис для асинхронных методов, что было бы очень естественно и позволило бы вам проверить наличие исключения в определенной части кода, но это не так, и тест не сообщит, что вы были ожидание исключения, но исключения не произошло.

Причина в том, что для получения исключения от вызова этого асинхронного анонимного метода NUnit должен был бы ожидать его, чего в настоящее время нет.

Один из вариантов, который я мог бы вам предложить, — это использование неасинхронной лямбда-выражения, в которой вы Wait выполняете задачу, возвращаемую асинхронной операцией, но синтаксис, к сожалению, не так хорош, потому что ожидание асинхронной операции ведет себя как отличается от ожидания возвращаемой задачи. В частности, в случае исключения, вызванного асинхронной операцией, вы получите фактическое исключение в первом случае и AggregateException во втором. В любом случае вот код, который будет работать с 2.6.2:

var aggregate = Assert.Throws<AggregateException>(() => LongRunningFunction(token).Wait());
Assert.IsInstanceOf<TaskCanceledException>(aggregate.InnerExceptions.Single());

В заключение, хотя NUnit 2.6.2 действительно представил поддержку асинхронных тестовых методов, что позволяет вам писать async [void|Task|Task<T>] тестов, мы не рассматривали возможность расширения поддержки асинхронных анонимных методов, которые могли бы оказаться полезными в такого рода утверждениях, хотя я считаю, мы могли бы.

person Simone    schedule 24.10.2012
comment
Я хотел дождаться метода, который выдает исключение и утверждает, что он выдает исключение. Я верю, что именно здесь вы говорите, что мне действительно нужен делегат. Как вы думаете, сможет ли NUnit поддерживать это в ближайшее время? Спасибо за ответ - person Peter; 25.10.2012
comment
Я не могу сказать вам наверняка, все, что я могу сказать, это то, что это было бы возможно. Позвольте мне указать, что ваше первое утверждение противоречиво. Если вы ждете операции, вы не можете утверждать, что она вызывает исключение, если вы не оберните вызов кодом, который может перехватить исключение, иначе исключение просто всплывет, что и происходит в ваших примерах. - person Simone; 25.10.2012

Если вы используете Resharper 7.1 или более позднюю версию с NUnit 2.6.2 или более поздней версии, методы тестирования public async void будут работать. Resharper 7.1 был выпущен сегодня (13 ноября 2012 г.)

Более ранние версии средства запуска тестов Resharper не ждут завершения теста и могут проходить без фактического тестирования чего-либо.

Версии NUnit до 2.6.2 имели почти ту же проблему.

Средство запуска тестов Resharper NUnit не имеет той же кодовой базы, что и средства запуска тестов с графическим интерфейсом NUnit и командной строки, и обновляется Jetbrains отдельно.

person Anthony    schedule 13.11.2012

Питер К сожалению, это не работает должным образом. Первый тест AsyncTaskCanceledSemiWorking работает только потому, что у меня есть атрибут ожидаемого исключения. Фактическое утверждение полностью игнорируется (как вы можете видеть по исключению ArgumentOutOfRange, которое является просто подделкой, чтобы заставить его потерпеть неудачу).

Ожидание неверно, поскольку атрибут имеет приоритет над Assert.Throws.

Как продемонстрировала Симона, предполагается «утвердить», что исключение является InstanceOf и, следовательно, принадлежит утверждению, а не броскам. Я думаю, что ваши ожидания, вероятно, основаны на том, как Throws используется в Java.

person Ravichandran Jv    schedule 25.10.2012
comment
Андрей, Пожалуйста, не вмешивайтесь во имя модерации, так как вы полностью вводите пост в заблуждение. - person Ravichandran Jv; 25.10.2012
comment
Эндрю, кажется, использование html-тегов, которые съедает браузер (меньше и больше), навело меня на мысль, что это было отредактировано вами. Извините за предыдущие комментарии (пытался удалить свои комментарии, но это не предусмотрено). - person Ravichandran Jv; 25.10.2012
comment
Я отредактировал сообщение. Я, безусловно, нисколько не ввел пост в заблуждение в своем редактировании; Вы по ошибке поместили имя Питера в тег HTML. Я исправил это для вас. Пожалуйста... - person Andrew Barber; 25.10.2012