Что делает хороший модульный тест?

Я уверен, что большинство из вас пишут множество автоматических тестов и что вы также столкнулись с некоторыми распространенными ошибками при модульном тестировании.

Мой вопрос заключается в том, что вы выполняете какие-либо правила поведения для написания тестов, чтобы избежать проблем в будущем? Чтобы быть более конкретным: каковы свойства хороших модульных тестов или как вы пишете свои тесты?

Предложения по языковым агностикам поощряются.

Позвольте мне начать с подключения источников – Pragmatic Unit Testing на Java с JUnit (есть версия с C # -Nunit тоже .. но у меня есть это … ее агностик по большей части. Рекомендуется.)

Хорошие тесты должны быть TRIP (акроним не является достаточно липким – у меня есть распечатка cheatsheet в книге, которую я должен был вытащить, чтобы убедиться, что я прав.)

  • Автоматически : вызов тестов, а также проверка результатов для PASS / FAIL должны быть автоматическими
  • Тщательный : Покрытие; Хотя клопы имеют тенденцию группироваться вокруг определенных регионов кода, убедитесь, что вы проверяете все ключевые пути и сценарии. Используйте инструменты, если вам нужно знать непроверенные области
  • Повторяемость : тесты должны давать одинаковые результаты каждый раз … каждый раз. Тесты не должны полагаться на неконтролируемые параметры.
  • Независимый : Очень важно.
    • Тесты должны проверять только одну вещь за раз. Несколько утверждений в порядке, если они все тестируют одну функцию / поведение. Когда тест выходит из строя, он должен определить местоположение проблемы.
    • Тесты не должны полагаться друг на друга – изолированы. Нет предположений о порядке выполнения теста. Перед каждым испытанием убедитесь, что «чистый шифер», используя правильную настройку / parsingку
  • Профессионал : В конечном итоге у вас будет такой же тестовый код, как и производство (если не больше), поэтому следуйте тому же стандарту хорошего дизайна для вашего тестового кода. Хорошо укомплектованные методы-classы с раскрывающимися намерениями именами, без дублирования, тесты с хорошими именами и т. Д.

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

Обновление 2010-08:

  • Читаемость : это может считаться частью профессионала, однако его недостаточно подчеркнуть. Кислотным тестом было бы найти кого-то, кто не является частью вашей команды, и попросить его / ее выяснить, какое поведение проходит испытание в течение пары минут. Тесты должны поддерживаться так же, как и производственный код, поэтому упрощайте чтение, даже если требуется больше усилий. Тесты должны быть симметричными (следовать шаблону) и краткими (проверяйте одно поведение за раз). Используйте согласованное соглашение об именах (например, стиль TestDox). Избегайте загромождения теста «случайными деталями» .. станьте минималистом.

Помимо этого, большинство других – это рекомендации, которые сокращают работу с низкой выгодой: например, «Не проверяйте код, который у вас нет» (например, сторонние библиотеки DLL). Не ходите на тестирование геттеров и сеттеров. Следите за соотношением цены и прибыли или вероятности дефекта.

  1. Не пишите ginormous тесты. Как предполагает «единица» в «единичном тесте», сделайте каждый из них максимально атомным и изолированным . Если необходимо, создайте предварительные условия, используя макет объектов, вместо того, чтобы повторно создавать слишком много типичной пользовательской среды вручную.
  2. Не проверяйте, что очевидно работает. Избегайте тестирования classов у стороннего поставщика, особенно тех, которые предоставляют основные API-интерфейсы для кода, в который вы вставляете код. Например, не проверяйте добавление элемента в class Hashtable поставщика.
  3. Подумайте о том, как использовать инструмент покрытия кода, такой как NCover, чтобы выявить случаи кросс, которые вы еще не тестировали.
  4. Попробуйте написать тест перед реализацией. Подумайте о тестировании как о большей спецификации, которую придерживается ваша реализация. Ср также поведенческая разработка, более конкретная ветвь тестового развития.
  5. Быть последовательным. Если вы только пишете тесты для своего кода, это вряд ли полезно. Если вы работаете в команде, а некоторые или все остальные не пишут тесты, это тоже не очень полезно. Убедите себя и всех остальных в важности (и экономии времени ) тестирования, или не беспокойтесь.

Большинство ответов здесь, по-видимому, посвящены лучшим методам тестирования модhive в целом (когда, где, почему и что), а не самим написанием самих тестов (как). Поскольку вопрос казался довольно конкретным в части «как», я думал, что опубликую это, взятое из презентации «коричневого мешка», которую я провел в своей компании.

5 законов о труде Уома:


1. Используйте длинные имена описательных методов тестирования.

- Map_DefaultConstructorShouldCreateEmptyGisMap() - ShouldAlwaysDelegateXMLCorrectlyToTheCustomHandlers() - Dog_Object_Should_Eat_Homework_Object_When_Hungry() 

2. Напишите свои тесты в стиле Arrange / Act / Assert .

  • Хотя эта организационная страtagsя была на некоторое время и вызвала многие вещи, введение аббревиатуры «AAA» недавно стало отличным способом добиться этого. Выполнение всех ваших тестов в соответствии с типом AAA облегчает их чтение и обслуживание.

3. Всегда указывайте сообщение об ошибке с помощью своих утверждений.

 Assert.That(x == 2 && y == 2, "An incorrect number of begin/end element processing events was raised by the XElementSerializer"); 
  • Простая, но полезная практика, которая делает очевидным в вашем приложении для бегунов то, что не удалось. Если вы не предоставляете сообщение, вы обычно получаете что-то вроде «Ожидаемое истинное, ложное» в выводе сбоя, что заставляет вас действительно читать тест, чтобы узнать, что не так.

4. Прокомментируйте причину теста – что такое бизнес-предположение?

  /// A layer cannot be constructed with a null gisLayer, as every function /// in the Layer class assumes that a valid gisLayer is present. [Test] public void ShouldNotAllowConstructionWithANullGisLayer() { } 
  • Это может показаться очевидным, но эта практика защитит целостность ваших тестов от людей, которые не понимают причину теста в первую очередь. Я видел, как многие тесты удаляются или изменяются, что было прекрасно, просто потому, что человек не понимал предположений, которые проверял тест.
  • Если тест тривиален или имя метода достаточно описательно, допустимо оставить комментарий выключенным.

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

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

Помните эти цели (адаптированные из книги xUnit Test Patterns от Meszaros)

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

Некоторые вещи, чтобы сделать это проще:

  • Тесты должны заканчиваться только по одной причине.
  • Тесты должны проверять только одну вещь
  • Минимизировать тестовые зависимости (без зависимостей от баз данных, файлов, ui и т. Д.)

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

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

Некоторые свойства больших модульных тестов:

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

  • Когда вы рефакторинг, тесты не должны терпеть неудачу.

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

  • Все тесты должны проходить всегда; нет недетерминированных результатов.

  • Модульные тесты должны быть хорошо учтены, как и ваш производственный код.

@Alotor: Если вы предлагаете, чтобы библиотека имела только модульные тесты в своем внешнем API, я не согласен. Я хочу модульные тесты для каждого classа, включая classы, которые я не выставляю внешним абонентам. (Однако, если мне кажется, что нужно писать тесты для частных методов, мне нужно рефакторировать. )


EDIT: Был комментарий о дублировании, вызванном «одним утверждением за тест». В частности, если у вас есть код для настройки сценария, а затем вы хотите сделать несколько утверждений об этом, но только одно утверждение для каждого теста, вы можете дублировать настройку на нескольких тестах.

Я не принимаю такой подход. Вместо этого я использую тестовые приспособления для каждого сценария . Вот пример:

 [TestFixture] public class StackTests { [TestFixture] public class EmptyTests { Stack _stack; [TestSetup] public void TestSetup() { _stack = new Stack(); } [TestMethod] [ExpectedException (typeof(Exception))] public void PopFails() { _stack.Pop(); } [TestMethod] public void IsEmpty() { Assert(_stack.IsEmpty()); } } [TestFixture] public class PushedOneTests { Stack _stack; [TestSetup] public void TestSetup() { _stack = new Stack(); _stack.Push(7); } // Tests for one item on the stack... } } 

То, что вам нужно, – это определение поведения тестируемого classа.

  1. Проверка ожидаемого поведения.
  2. Проверка ошибок.
  3. Охват всех путей кода внутри classа.
  4. Управлять всеми функциями-членами внутри classа.

Основное намерение – увеличить вашу уверенность в поведении classа.

Это особенно полезно при анализе кода. У Мартина Фаулера есть интересная статья, касающаяся тестирования на его веб-сайте.

НТН.

веселит,

обкрадывать

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

Мне нравится правая аббревиатура BICEP из вышеупомянутой книги по изучению прагматического модуля :

  • Справа : Правильно ли результаты?
  • B : Правильны ли все условия?
  • Я : Можем ли мы проверить взаимосвязи?
  • C : Можем ли мы проверить результаты проверки другими способами?
  • E : Можем ли мы заставить e rror условия произойти?
  • P : Являются ли характеристики эффективности в пределах?

Лично я чувствую, что вы можете получить довольно далеко, проверив, что вы получите правильные результаты (1 + 1 должен вернуть 2 в функции добавления), проверяя все граничные условия, о которых вы можете думать (например, используя два числа, из которых сумма больше, чем целочисленное максимальное значение в функции добавления) и вызывая условия ошибки, такие как сбои сети.

Хорошие тесты нуждаются в ремонте.

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

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

  • Взаимодействие команды взрывается
  • количество взрывов
  • взаимодействие между компонентами взрывается.
  • время для создания всех unittests становится значительной частью времени сборки
  • изменение API может пульсировать до сотен тестовых случаев. Несмотря на то, что изменение кода производства было простым.
  • увеличивается количество событий, необходимых для последовательности процессов в правильное состояние, что, в свою очередь, увеличивает время выполнения теста.

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

Здесь вы начинаете иметь дело с компромиссами:

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

Вам также необходимо решить:

где вы храните тестовые примеры в своей базе кода?

  • как вы документируете свои тестовые примеры?
  • можно проверить, чтобы светильники были повторно использованы для сохранения обслуживания тестового случая?
  • что происходит, когда выполнение ночного теста не выполняется? Кто делает сортировку?
  • Как вы поддерживаете макет объектов? Если у вас есть 20 модhive, все из которых используют свой собственный дизайн API-интерфейсов для фальсификации, это быстро изменяет ряды API. Изменения не только меняются, но и 20 макетных объектов меняются. Эти 20 модhive были написаны в течение нескольких лет многими различными командами. Его classическая проблема повторного использования.
  • люди и их команды понимают ценность автоматических тестов, которые им просто не нравятся, как это делает другая команда. 🙂

Я мог бы продолжать вечно, но я хочу сказать следующее:

Тесты должны быть ремонтопригодными.

Я рассмотрел эти принципы некоторое время назад в этой статье MSDN Magazine, которая, как мне кажется, важна для любого разработчика.

Способ определения «хороших» модульных тестов заключается в том, что они обладают следующими тремя свойствами:

  • Они читаемы (имена, утверждения, переменные, длина, сложность).
  • Они поддаются контролю (без логики, а не по указанным, государственным, реорганизованным ..)
  • Они заслуживают доверия (проверяйте правильность, изолируйте, а не интеграционные тесты).
  • Unit Testing просто проверяет внешний API вашего модуля, вы не должны тестировать внутреннее поведение.
  • Каждый тест TestCase должен тестировать один (и только один) метод внутри этого API.
    • В случае сбоев следует включать дополнительные тестовые примеры.
  • Проверьте охват ваших тестов: как только тестировалось устройство, 100% линий внутри этого устройства должны были быть выполнены.

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

С уважением

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

Я второй ответ «A TRIP», за исключением того, что тесты ДОЛЖНЫ полагаться друг на друга !!!

Зачем?

DRY – Dont Repeat Yourself – также относится к тестированию! Тестовые зависимости могут помочь: 1) сохранить время настройки, 2) сохранить ресурсы прибора и 3) точно определить неисправности. Конечно, только учитывая, что ваша среда тестирования поддерживает первоclassные зависимости. В противном случае я признаю, что они плохие.

Последующие действия http://www.iam.unibe.ch/~scg/Research/JExample/

Часто модульные тесты основаны на макет-объекте или макет данных. Мне нравится писать три типа модульных тестов:

  • «переходные» модульные тесты: они создают свои собственные макетные объекты / данные и проверяют их функции с ним, но уничтожают все и не оставляют следов (например, никаких данных в тестовой базе данных)
  • «постоянный» модульный тест: они тестируют функции внутри вашего кода, создавая объекты / данные, которые будут необходимы более сложной функции позже для их собственного модульного теста (избегая для этой расширенной функции воссоздавать каждый раз, когда их собственный набор макетных объектов / данных)
  • «настойчивые» модульные тесты: модульные тесты с использованием макетных объектов / данных, которые уже существуют (потому что созданы в другом тестовом сеансе) постоянными модульными тестами.

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

  • Я запускаю третий вид очень часто, потому что все макетные объекты / данные уже существуют.
  • Я запускаю второй вид всякий раз, когда меняет модель.
  • Я запускаю первый, чтобы проверять основные функции время от времени, чтобы проверить основные регрессии.

Подумайте о двух типах тестирования и рассмотрите их по-разному – функциональное тестирование и тестирование производительности.

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

Я использую согласованное соглашение об именовании тестов, описанное в стандартах Roy Osherove Unit Test Naming. Каждый метод в данном classе тестового случая имеет следующий стиль именования MethodUnderTest_Scenario_ExpectedResult.

    Первый раздел тестового имени – это имя метода в тестируемой системе.
    Далее идет конкретный сценарий, который тестируется.
    Наконец, это результат этого сценария.

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

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

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

  • Записи classа Eclipse, используемые только для тестов
  • Как тестировать модули с Entity Framework 6, если вы беспокоитесь?
  • Mockito: как проверить метод был вызван на объект, созданный в рамках метода?
  • Как объединить тестовые абстрактные classы: расширить с помощью заглушек?
  • Запуск одиночного теста из classа JUnit с использованием командной строки
  • C # «внутренний» модификатор доступа при выполнении модульного тестирования
  • Xcode 4: запустить тесты из командной строки (xcodebuild)?
  • Должны ли частные / защищенные методы проходить единичный тест?
  • Mockito: InvalidUseOfMatchersException
  • Как создать stream из строки?
  • Можно ли запустить экземпляр сервера zookeeper в процессе, скажем, для модульных тестов?
  • Давайте будем гением компьютера.