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

97

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

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

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

Spoike
источник

Ответы:

93

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

Хорошие тесты должны быть ПОЕЗДКОЙ (The acronymn не липкие достаточно - у меня есть распечатка Cheatsheet в книге , которую я должен был вытащить , чтобы убедиться , что я получил это право ..)

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

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

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

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

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

Гишу
источник
Мы можем не согласиться с использованием Mocks, но это был очень хороший обзор лучших практик модульного тестирования.
Джастин Стандарт
Я расскажу об этом как о ответе, потому что я считаю полезной аббревиатуру «A TRIP».
Спойк,
3
Я согласен по большей части, но хотел бы отметить, что есть преимущество в тестировании кода, которым вы не владеете ... Вы проверяете, соответствует ли он вашим требованиям. Как еще вы можете быть уверены, что обновление не сломает ваши системы? (Но, конечно, при этом не забывайте о соотношении затрат и выгод.)
Разочарованный
@Craig - я полагаю, вы имеете в виду регрессионные тесты (на уровне интерфейса) (или в некоторых случаях тесты для учащихся), которые документируют поведение, от которого вы зависите. Я бы не стал писать «модульные» тесты для стороннего кода, потому что. поставщик знает об этом коде больше, чем я b. Производитель не обязан сохранять какую-либо конкретную реализацию. Я не контролирую изменения этой кодовой базы и не хочу тратить свое время на исправление неисправных тестов с помощью обновления. Так что я бы предпочел закодировать некоторые регрессионные тесты высокого уровня для поведения, которое я использую (и хочу получать уведомления в случае нарушения)
Гишу
@Gishu: Да, конечно! Тесты должны выполняться только на уровне интерфейса; Фактически, вам следует протестировать только те функции, которые вы действительно используете. Кроме того, при выборе, с чем писать эти тесты; Я обнаружил, что простые и понятные фреймворки «модульного» тестирования обычно идеально подходят для всех требований.
Разочарованный
42
  1. Не пишите огромных тестов. Как предлагает «модуль» в «модульном тесте», сделайте каждый из них как можно более атомарным и изолированным . Если необходимо, создайте предварительные условия, используя фиктивные объекты, вместо того, чтобы вручную воссоздавать слишком большую часть типичной пользовательской среды.
  2. Не проверяйте заведомо работающие вещи. Избегайте тестирования классов от стороннего поставщика, особенно того, который предоставляет основные API-интерфейсы платформы, в которой вы кодируете. Например, не тестируйте добавление элемента в класс Hashtable поставщика.
  3. Рассмотрите возможность использования инструмента покрытия кода, такого как NCover, чтобы помочь обнаружить крайние случаи, которые вам еще предстоит протестировать.
  4. Попробуйте написать тест перед реализацией. Думайте о тесте как о спецификации, которой будет придерживаться ваша реализация. Ср. также разработка, управляемая поведением, более конкретная ветвь разработки, управляемой тестированием.
  5. Быть последовательным. Если вы пишете тесты только для части своего кода, это вряд ли будет полезно. Если вы работаете в команде, а некоторые или все остальные не пишут тесты, это тоже не очень полезно. Убедите себя и всех в важности (и способах экономии времени ) тестирования или не беспокойтесь.
Серен Куклау
источник
1
Хороший ответ. Но это не так уж плохо, если вы не тестируете все компоненты поставки. Конечно, это предпочтительнее, но здесь должен быть баланс и прагматизм. Re: привлечь ваших коллег к работе; иногда вам просто нужно сделать это, чтобы продемонстрировать ценность и в качестве ориентира.
Мартин Кларк,
1
Я согласен. Однако в долгосрочной перспективе вы должны быть уверены в наличии тестов, т. Е. Предполагать, что они будут обнаруживать распространенные ошибки. В противном случае выгоды значительно уменьшатся.
Сорен Куклау,
2
«Если вы пишете тесты только для некоторой части вашего кода, это вряд ли будет полезно». Так ли это на самом деле? У меня есть проекты с 20% -ным покрытием кода (критические / склонные к сбоям области), и они мне очень помогли, и проекты тоже в порядке.
доктор. зло
1
Я согласен с Слау. Даже если есть всего несколько тестов, при условии, что они хорошо написаны и достаточно изолированы, они очень помогут.
Spoike 01
41

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

5 законов написания тестов Вомпа:


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, что не удалось. Если вы не предоставите сообщение, вы обычно получите что-то вроде «Ожидаемое истинное, было ложным» в вашем сообщении об ошибке, что заставляет вас фактически пойти прочитать тест, чтобы выяснить, что не так.

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. Каждый тест всегда должен возвращать состояние любого ресурса, которого он касается.

  • По возможности используйте макеты, чтобы не иметь дело с реальными ресурсами.
  • Очистку необходимо производить на тестовом уровне. Тесты не должны зависеть от порядка выполнения.
мать
источник
2
+1 из-за пунктов 1, 2 и 5 важны. 3 и 4 кажутся чрезмерными для модульных тестов, если вы уже используете описательные имена методов тестирования, но я рекомендую документировать тесты, если они имеют большой объем (функциональное или приемочное тестирование).
Spoike
+1 за приземленные и практические знания и примеры
Фил
17

Помните об этих целях (адаптировано из книги Месароса «Шаблоны тестов xUnit»)

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

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

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

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

Мендельт
источник
Думаю, вы имели в виду, что адаптировали из книги Джерарда Месароса «Шаблоны тестов xUnit». xunitpatterns.com
Spoike
Ага, ты прав. Я
проясню это в своем
Отличные баллы. Модульные тесты могут быть очень полезными, но очень важно избегать попадания в ловушку сложных, взаимозависимых модульных тестов, которые создают огромный налог на любые попытки изменить систему.
Wedge
9

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

Haacked
источник
9

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

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

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

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

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

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

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


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

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

[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...
    }
}
Джей Базузи
источник
Я не согласен только с одним утверждением на тест. Чем больше утверждений у вас будет в тесте, тем меньше у вас будет тестовых случаев вырезания и вставки. Я считаю, что тестовый пример должен фокусироваться на сценарии или пути кода, а утверждения должны исходить из всех предположений и требований для выполнения этого сценария.
Lucas B,
Я думаю, мы согласны с тем, что DRY применяется к модульным тестам. Как я уже сказал, «Модульные тесты должны быть хорошо факторизованы». Однако есть несколько способов устранить дублирование. Один из них, как вы упомянули, - это иметь модульный тест, который сначала вызывает тестируемый код, а затем утверждает его несколько раз. Альтернативой является создание новой «тестовой оснастки» для сценария, которая вызывает тестируемый код на этапе инициализации / настройки, а затем имеет серию модульных тестов, которые просто утверждают.
Джей Базузи, 01
Мое практическое правило: если вы используете копипаст, вы делаете что-то не так. Одно из моих любимых высказываний - «Копипаст - это не шаблон дизайна». Я также согласен, что одно утверждение для каждого модульного теста - это вообще хорошая идея, но я не всегда настаиваю на этом. Мне нравится более общий термин «тестируйте одну вещь на единичный тест». Хотя обычно это означает одно утверждение на единичный тест.
Джон Тернер,
7

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

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

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

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

HTH.

ура,

Роб

Роб Уэллс
источник
Роб - механический, это хорошо, но не по замыслу. Зачем ты все это сделал? Такой подход может помочь другим встать на путь TDD.
Марк Левисон,
7

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

Придирчивый
источник
@Rismo Сам по себе не эксклюзив. По определению то, что здесь написано Quarrelsome, является эксклюзивной методологией Test First, которая является частью TDD. TDD также принимает во внимание рефакторинг. Самое "умное" определение, которое я читал, - это TDD = Test First + Refactor.
Spoike,
Да, это не обязательно должно быть TDD, просто сначала убедитесь, что ваш тест не прошел. Затем подключите остальные провода. Чаще всего это происходит при выполнении TDD, но вы также можете применить это, когда не используете TDD.
Quibblesome
6

Мне нравится аббревиатура Right BICEP из вышеупомянутой книги Pragmatic Unit Testing :

  • Право : Являются ли результаты верно ?
  • B : Являются ли все б oundary условия исправить?
  • Я : Можно ли проверить я nverse отношения?
  • C : Можно ли Ĉ результаты росс-проверки с использованием других средств?
  • E : Можно ли заставить е условия rror произойти?
  • P : Есть р наилучших показателей характеристики в пределах?

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

Питер Эвьян
источник
6

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

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

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

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

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

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

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

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

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

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

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

Тесты должны быть обслуживаемыми.

DanM
источник
5

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

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

  • Они читабельны (именование, утверждения, переменные, длина, сложность ..)
  • Они обслуживаются (без логики, без переопределения, на основе состояния, рефакторинга ..)
  • Они заслуживают доверия (тестируйте правильные вещи, изолированные, а не интеграционные тесты ..)
РойОшеров
источник
Рой, я полностью согласен. Эти вещи намного важнее, чем охват крайних случаев.
Мэтт Хинце
заслуживает доверия - отличный балл!
ratkok
4
  • Модульное тестирование просто проверяет внешний API вашего модуля, вы не должны тестировать внутреннее поведение.
  • Каждый тест TestCase должен проверять один (и только один) метод внутри этого API.
    • Для случаев сбоя следует включить дополнительные тестовые наборы.
  • Проверьте охват ваших тестов: после того, как модуль был протестирован, все строки внутри этого модуля должны быть выполнены.
Алотор
источник
2

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

С уважением

Маркосперейра
источник
1

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

Джоэл ин Гё
источник
1

Я второй ответ "ПУТЕШЕСТВИЕ", за исключением того, что тесты ДОЛЖНЫ полагаться друг на друга !!!

Зачем?

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

Следите за http://www.iam.unibe.ch/~scg/Research/JExample/

Акун
источник
Я согласен с тобой. TestNG - это еще одна структура, в которой легко разрешаются зависимости.
Davide
0

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

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

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

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

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

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

Techboy
источник
Тогда как насчет модульного тестирования?
Spoike
0

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

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

Каждый раздел использует верхний регистр верблюда и ограничен нижней оценкой.

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

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

Yack
источник