Новое для модульного тестирования, как написать отличные тесты?

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

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

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

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

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

Редактировать: я хотел бы поблагодарить Stack Overflow, у меня были большие входы менее чем за 15 минут, которые больше отвечали часам онлайн-чтения, которые я только что сделал.

Ответ 1

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

Я думаю, что вы делаете это неправильно.

A unit test должен:

  • проверить один метод
  • предоставить некоторые конкретные аргументы этому методу
  • проверьте, что результат как ожидалось

Не следует заглядывать внутрь метода, чтобы увидеть, что он делает, поэтому изменение внутренних компонентов не должно приводить к сбою теста. Вы не должны прямо проверять, что вызываются частные методы. Если вам интересно узнать, проверяется ли ваш личный код, используйте инструмент покрытия кода. Но не одержимы этим: 100% охват не является обязательным требованием.

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

Вы не должны использовать сам метод (или любой из внутреннего кода, который он использует) для генерации ожидаемого результата динамически. Ожидаемый результат должен быть жестко закодирован в ваш тестовый пример, чтобы он не менялся при изменении реализации. Здесь упрощенный пример того, что должен сделать unit test:

testAdd()
{
    int x = 5;
    int y = -2;
    int expectedResult = 3;

    Calculator calculator = new Calculator();
    int actualResult = calculator.Add(x, y);
    Assert.AreEqual(expectedResult, actualResult);
}

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

Ответ 2

Для модульного тестирования я нашел как Test Driven (сначала тесты, так и код второй) и код, а второй - чрезвычайно полезный.

Вместо написания кода, а затем для написания теста. Напишите код, а затем посмотрите, что ВЫ ДУМАЕТЕ, что код должен делать. Подумайте обо всех предполагаемых его применениях, а затем напишите тест для каждого. Я считаю, что тесты на запись быстрее, но более востребованы, чем сама кодировка. Тесты должны проверить намерение. Также, думая о намерениях, вы заканчиваете поиск угловых дел на этапе написания теста. И, конечно, при написании тестов вы можете обнаружить, что одно из немногих применений вызывает ошибку (что-то, что я часто нахожу, и я очень рад, что эта ошибка не испортила данные и не отключена).

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

Все простые примеры, такие как function square(number), велики и все, и, вероятно, являются плохими кандидатами, которые проводят много времени. Те, которые делают важную бизнес-логику, то, где тестирование важно. Проверьте требования. Не просто проверяйте водопровод. Если требования меняются, тогда угадайте, что и тесты тоже.

Тестирование не должно буквально проверять, что функция foo вызывала функциональную панель 3 раза. Это не правильно. Проверьте правильность результата и побочных эффектов, а не внутреннюю механику.

Ответ 3

Стоит отметить, что ретроинтерфейсные тесты в существующий код намного сложнее, чем вождение создания этого кода с помощью тестов в первую очередь. Это один из больших вопросов при работе с устаревшими приложениями... как unit test? Это было задано много раз раньше (так что вы можете быть закрыты как вопрос обмана), и люди обычно оказываются здесь:

Перемещение существующего кода в Test Driven Development

Вторым принятой рекомендацией для книги отзывов, но помимо этого там больше информации, связанной с ответами.

Ответ 4

Не пишите тесты, чтобы получить полный охват вашего кода. Напишите тесты, которые гарантируют ваши требования. Вы можете обнаружить кодировки, которые не нужны. И наоборот, если они необходимы, они должны выполнять какое-то требование; найти его, что это такое, и проверить требование (а не путь).

Держите ваши тесты маленькими: один тест на каждое требование.

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

Ответ 5

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

Ответ 6

Попробуйте написать Unit Test перед тем, как написать метод, который будет тестироваться.

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

Вы всегда должны тестировать результаты метода, а не как метод получает эти результаты.

Ответ 7

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

When I'm writing tests for a method, I have the feeling of rewriting a second time what I          
already wrote in the method itself.
My tests just seems so tightly bound to the method (testing all codepath, expecting some    
inner methods to be called a number of times, with certain arguments), that it seems that
if I ever refactor the method, the tests will fail even if the final behavior of the   
method did not change.

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

Ответ 8

Это лучшая книга для модульного тестирования: http://www.manning.com/osherove/

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