Рекомендуется читать оригинал с лучшим форматированием, чем этот пост на Medium.

"Если вам не нравится модульное тестирование вашего продукта, скорее всего, ваши клиенты тоже не захотят его тестировать". — Аноним

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

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

Основы тестирования

Принципы

Тесты должны,

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

Упорядочить: код, необходимый для настройки сценария, который должен протестировать тест.

Действие: вызовите код, который вы тестируете.

Утверждение: проверьте, соответствует ли полученный результат ожидаемым результатам.

  • используйте декларативные утверждения, а не императивные утверждения.
  • сосредоточьтесь на поведенческих тестах, то есть тестах, которые проверяют поведение, а не конкретную реализацию. По сути, это сводится к тестированию только общедоступных методов, а не частных методов, которые они могут использовать.
  • отдавайте предпочтение заглушкам и шпионам, а не макетам. Макеты сосредоточены на внутренностях службы и, следовательно, тесно связаны с реализацией. С другой стороны, шпионы и заглушки сосредоточены на мониторинге использования службы, а не на том, как она реализована.
  • улучшите тестирование входных данных с помощью библиотеки, такой как faker, которая генерирует случайные имена, номера телефонов и т. д., и/или библиотеки тестирования на основе свойств, такой как fast-check, которая генерирует огромное количество входных данных на основе заданных вами входных свойств.
  • Избегайте глобальных начальных значений и текстовых фикстур, вместо этого предпочитая добавлять необходимые данные для каждого теста, чтобы они оставались независимыми.
  • ожидать ошибок вместо того, чтобы пытаться их перехватывать (например, expect(foo).to.throw(MyError)).
  • быть помечены, чтобы такие вещи, как быстрые тесты, выполнялись при сохранении, а более медленные тесты запускались при более крупных событиях, например, перед отправкой.
  • стремитесь к покрытию кода ~80%.
  • используйте библиотеку тестирования мутаций, такую ​​как Stryker, чтобы убедиться, что тесты, о которых сообщается в отчете о покрытии кода, действительно эффективны.
  • используйте тестовые линтеры типа eslint-plugin-jest.

Типы

Статический

Статические тесты запускаются по мере ввода кода.

К ним относятся,

  • Линтеры
  • Системы типов
  • Сканер уязвимых зависимостей
  • Анализ сложности кода
  • Проверка лицензии
  • Проверка на плагиат

Единица измерения

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

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

Принципы

F.I.R.S.T.

  • Быстро. В проекте могут быть тысячи модульных тестов, поэтому они должны быть быстрыми.
  • Независимый: тест должен проверять часть кода, независимую от остальной части проекта.
  • Повторяемость: каждый тест должен давать одинаковые результаты каждый раз, пока тестируемый код не изменился. Это означает, что он не может зависеть от конкретных элементов, которые могут меняться, таких как дата/время, запуск системы или любой вывод функции renadom.
  • Самопроверка: не требует ручной проверки, чтобы определить, пройден тест или нет.
  • Подробно. Должен охватывать все сценарии использования, включая крайние и крайние случаи, исключения/ошибки, неверные входные данные и т. д.

Интеграция

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

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

Концы с концами

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

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

Разработка тестового драйвера (TDD)

«Разработка тестов — это не только процесс тестирования, но и один из лучших известных способов предотвращения ошибок. Мышление, которое необходимо сделать для создания полезного теста, может обнаружить и устранить ошибки до того, как они будут закодированы». — Борис Бейзер

Эта методология разработки программного обеспечения направлена ​​​​на написание более надежного и хорошо спроектированного программного обеспечения путем написания сначала неудачных тестов, а затем написания кода, который обеспечивает их прохождение. Сосредоточившись на написании только того кода, который необходим для выполнения ваших тестов, ваш код должен оставаться коротким и чистым. TDD сосредоточен вокруг 4 основных циклов.

Три закона

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

Эти законы применяются в TDD каждую секунду.

Красный/Зеленый/Рефакторинг

  1. Создайте модульные тесты, которые терпят неудачу
  2. Напишите производственный код, который сделает этот тест пройденным.
  3. Уберите беспорядок, который вы только что сделали.

Эти шаги выполняются поминутно в TDD.

Конкретный/общий

По мере того, как тесты становятся более конкретными, код становится более общим.

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

Границы

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

CI/CD

Непрерывная интеграция (CI)

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

Непрерывное развертывание (CD)

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

Вывод

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

Первоначально опубликовано на https://thefinnternet.com.