C#: Как протестировать исключение StackOverflowException

скажем, у вас есть метод, который потенциально может застрять в бесконечном цикле вызовов методов и сбой с StackOverflowException. Например, мой наивный RecursiveSelect метод, упомянутый в этот вопрос.

начиная с .NET Framework версии 2.0, объект StackOverflowException не может быть пойман блоком try-catch и соответствующий процесс завершается по умолчанию. Следовательно, пользователям рекомендуется написать свой код для обнаружения и предотвращения переполнение стека. Например, если приложение зависит от рекурсии, используйте счетчик или условие состояния для завершения рекурсивного цикла.

принимая эту информацию (от ответ) С учетом того, что исключение не может быть поймано, можно ли даже написать тест для чего-то подобного? Или тест для этого, если это не удалось, фактически сломает весь набор тестов?

Примечание: я знаю, что могу просто попробовать это и посмотреть, что произойдет, но меня больше интересует общая информация об этом. Например, будут ли разные тестовые фреймворки и тестовые бегуны справляться с этим по-разному? Должен ли я избегать такого теста, даже если это возможно?

7 ответов


вам нужно будет решить Проблема Останова! Это сделало бы вас богатым и знаменитым:)


Как насчет проверки количества кадров в стеке в операторе assert?

const int MaxFrameCount = 100000;
Debug.Assert(new StackTrace().FrameCount < MaxFrameCount);

в вашем примере из связанного вопроса это будет (дорогостоящий оператор assert будет удален в сборке выпуска):

public static IEnumerable<T> SelectRecursive<T>(this IEnumerable<T> subjects, Func<T, IEnumerable<T>> selector)
{
    const int MaxFrameCount = 100000;
    Debug.Assert(new StackTrace().FrameCount < MaxFrameCount);

    // Stop if subjects are null or empty
    if(subjects == null || !subjects.Any())
        yield break;

    // For each subject
    foreach(var subject in subjects)
    {
        // Yield it
        yield return subject;

        // Then yield all its decendants
        foreach (var decendant in SelectRecursive(selector(subject), selector))
            yield return decendant;
    }
}

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


это зло, но вы можете раскрутить его в новом процессе. Запустите процесс из модульного теста и дождитесь его завершения и проверки результата.


идея в том, чтобы отслеживать, как глубоко рекурсивная функция является вложенной, так что она не использует слишком много места в стеке. Пример:

string ProcessString(string s, int index) {
   if (index > 1000) return "Too deeply nested";
   s = s.Substring(0, index) + s.Substring(index, 1).ToUpper() + s.Substring(index + 1);
   return ProcessString(s, index + 1);
}

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


У нас не может быть теста для StackOverflow, потому что это ситуация, когда больше нет стека для распределения, приложение автоматически выйдет из этой ситуации.


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

нет простого способа написать тест, который ожидает и ловит StackOverflowException, но это не то поведение, которое вы хотите тестировать!

вот несколько советов для тестирования кода не стек переполнение:

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

  • потоки могут иметь разное количество места в стеке, и если кто-то вызывает ваш код, вы не можете контролировать, сколько пространства стека доступно. Хотя по умолчанию для 32-битной среды CLR размер стека составляет 1 МБ, а 64-бит-2 МБ, имейте в виду, что веб-серверы по умолчанию будут иметь гораздо меньший стек. Ваш тестовый код может использовать один из Thread конструкторы, которые принимают меньший размер стека, если вы хотите проверить, что ваш код не будет переполняться стеком с меньшим доступным пространством.


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

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