Как обнаружить ошибки UnobservedTaskException в тестовых комплектах nunit - PullRequest
1 голос
/ 20 апреля 2011

Моя команда широко использует модульные тесты NUnit в нашем проекте на C #. Недавно мы начали использовать параллельную библиотеку задач (TPL) в .NET 4, которая представила нам морщинку.

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

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

Как другие команды решают эту проблему? Как вы обнаруживаете тестовые случаи, которые проходят (то есть завершаются без каких-либо исключений), но оставляют один или несколько объектов Task с ненаблюдаемыми исключениями?

Ответы [ 2 ]

2 голосов
/ 06 августа 2011

Вот как я это делаю:

[Test]
public void ObservesTaskException()
{
    bool wasUnobservedException = false;

    TaskScheduler.UnobservedTaskException += 
        (s, args) => wasUnobservedException = true;

    CauseATaskToThrowInTheSystemUnderTest();

    GC.Collect();
    GC.WaitForPendingFinalizers();

    Assert.That(wasUnobservedException, Is.False);
}

Вызов CauseATaskToThrowInTheSystemUnderTest () является заполнителем для всего, что вам нужно сделать.Я рекомендую обернуть ваш код в функцию, подобную этой, потому что это помогает удостовериться, что объект Task, который генерирует исключение, недоступен при запуске GC.Если объект «Задача» достижим, финализатор не запустится, и этот тест ничего не будет проверять.

Очевидно, что важно также убедиться, что задача завершена, прежде чем произойдет сборка мусора.То, как вы это сделаете (если сможете), зависит от вашего конкретного кода.Возможно, поток вашей программы гарантирует, что это так.Если нет, и если ваш тест может получить доступ к объекту Task, вы можете сделать следующее:

var continuation = GetTheTaskFromTheSystemUnderTest();
    .ContinueWith(t => {});

CauseATaskToThrowInTheSystemUnderTest();

bool isTaskCompleted = continuation.Wait(SomeSuitableTimeout);

Вы должны добавить isTaskCompleted к вашему утверждению.

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

Доступ к задачам, которые вы создаете (включая задачи продолжения, которые вы создаете для других задач),одно из соображений, о которых следует помнить при разработке для тестирования.При этом применяются обычные компромиссы.Я стараюсь включать такие тесты, когда мой код создает задачи - это важное поведение моего кода, и его необходимо протестировать.

0 голосов
/ 20 апреля 2011

Я бы оставил модульные тесты детерминированными, а затем провел бы функциональное тестирование с помощью CHESS или Jinx.

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

Что касается тестирования упаковки / распаковки нескольких исключений, я бы проверил это, как если бы это был цикл: без исключений, одно исключение и два исключения доказывают все это.

...