Вопрос

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

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

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

Это было полезно?

Решение

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

Хорошие тесты должны быть ПУТЕШЕСТВИЕ (Аббревиатура недостаточно запоминающаяся - у меня есть распечатка чита в книге, которую мне пришлось вытащить, чтобы убедиться, что я все правильно понял ..)

  • Автоматический :Вызов тестов, а также проверка результатов на предмет ПРОХОЖДЕНИЯ / НЕУДАЧИ должны быть автоматическими
  • Тщательный:Охват;Хотя ошибки, как правило, группируются вокруг определенных областей кода, убедитесь, что вы протестировали все ключевые пути и сценарии..Используйте инструменты, если вам необходимо знать непроверенные регионы
  • Повторяемый:Тесты должны каждый раз давать одни и те же результаты..каждый раз.Тесты не должны полагаться на неконтролируемые параметры.
  • Независимый:Очень важно.
    • Тесты должны проверьте только одну вещь по очереди.Несколько утверждений допустимы до тех пор, пока все они тестируют одну функцию / поведение.Когда тест завершается неудачей, он должен точно определить местонахождение проблемы.
    • Тесты не следует полагаться друг на друга - Изолированный.Никаких предположений о порядке выполнения теста.Обеспечивайте "чистый лист" перед каждым тестированием, используя соответствующую настройку / демонтаж
  • Профессиональный:В долгосрочной перспективе у вас будет столько же тестового кода, сколько и производственного (если не больше), поэтому следуйте тому же стандарту хорошего дизайна для вашего тестового кода.Хорошо продуманные методы -классы с намеренно раскрывающимися именами, Отсутствие дублирования, тесты с правильными именами и т.д.

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

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

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

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

Другие советы

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

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

5 Законов написания тестов Womp:


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

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

2.Запишите свои тесты в Стиль организации / Действия / Утверждения.

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

3.Всегда отправляйте сообщение об ошибке вместе со своими утверждениями.

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

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").

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

Некоторые вещи, которые облегчат это:

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

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

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

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

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

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

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

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

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

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


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

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

[TestFixture]
public class StackTests
{
    [TestFixture]
    public class EmptyTests
    {
        Stack<int> _stack;

        [TestSetup]
        public void TestSetup()
        {
            _stack = new Stack<int>();
        }

        [TestMethod]
        [ExpectedException (typeof(Exception))]
        public void PopFails()
        {
            _stack.Pop();
        }

        [TestMethod]
        public void IsEmpty()
        {
            Assert(_stack.IsEmpty());
        }
    }

    [TestFixture]
    public class PushedOneTests
    {
        Stack<int> _stack;

        [TestSetup]
        public void TestSetup()
        {
            _stack = new Stack<int>();
            _stack.Push(7);
        }

        // Tests for one item on the stack...
    }
}

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

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

Основная цель - повысить вашу уверенность в поведении класса.

Это особенно полезно при рассмотрении рефакторинга вашего кода.У Мартина Фаулера есть интересная Статья что касается тестирования на его веб-сайте.

ХТХ.

ваше здоровье,

Роб

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

Мне нравится аббревиатура Right BICEP от вышеупомянутого Прагматичное Модульное Тестирование книга:

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

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

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

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

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

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

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

Вот тут-то вам и приходится сталкиваться с компромиссами:

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

Вам также нужно решить:

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

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

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

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

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

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

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

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

С наилучшими пожеланиями

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

Я поддерживаю ответ "Поездка", за исключением того, что тесты ДОЛЖНЫ полагаться друг на друга!!!

Почему?

СУХОЙ - Не повторяйтесь - относится и к тестированию!Тестовые зависимости могут помочь 1) сэкономить время настройки, 2) сэкономить ресурсы устройства и 3) точно определить причины сбоев.Конечно, только при условии, что ваша платформа тестирования поддерживает первоклассные зависимости.В остальном, я признаю, они плохие.

Последующие действия http://www.iam.unibe.ch /~scg/Исследование/Пример/

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

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

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

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

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

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

Я использую согласованное соглашение об именовании тестов, описанное Стандарты именования модульных тестов Роя Ошерова Каждый метод в данном классе тестового примера имеет следующий стиль именования MethodUnderTest_Scenario_ExpectedResult.

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

Каждый раздел написан заглавными буквами и разделен цифрой ниже.

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top