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

Мои основные предположения:

  • Я хочу, чтобы приложение имело максимально возможное качество — вместо того, чтобы двигаться быстро и ломать вещи, я стремлюсь двигаться медленно и не испортить производство;
  • Я планирую на долгосрочную перспективу — я не верю в то, что нужно начинать с нуля, и я надеюсь, что то, что я создам, будет использоваться через 10 лет и более; и
  • Мне важен опыт разработчиков — мне нужны надежные тесты, возможно, с быстрой обратной связью для разработчиков. Как локально, так и при непрерывной интеграции (CI).

Помня об этом, давайте взглянем на автоматизированные инструменты контроля качества.

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

Название end-to-end (E2E) происходит от характера тестирования: мы проверяем приложение от внешнего интерфейса к внутреннему. Тесты взаимодействуют с приложением через графический пользовательский интерфейс (GUI), поэтому мы тестируем внешний интерфейс, когда он работает в браузере. В то же время у нас есть сервер и база данных, работающие на бэкэнде — через взаимодействия в браузере мы можем проверить, ведет ли бэкэнд так, как мы ожидаем.

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

  1. Войдите как клиент,
  2. Найдите продукт,
  3. Добавьте его в корзину,
  4. Перейти на страницу оформления заказа,
  5. Ожидайте один товар в корзине и общую сумму, равную его цене.

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

Примеры библиотек, которые позволяют создавать тест E2E:

  • Кипарис
  • Драматург
  • Ночной дозор
  • (устарело) Транспортир для приложений Angular

Модульные тесты

Модульные тесты названы так потому, что мы тестируем одну единицу кода: класс, функцию или объект любого типа, определяемый используемой вами структурой. Вы тестируете эти модули, взаимодействуя с интерфейсом, который они предоставляют, и имитируя части кода, которые они используют.

Модульные тесты проверяют код на уровне кода. Пример сценария тестирования:

  1. Создайте объект заказа и назовите его testOrder,
  2. Добавьте объект продукта в заказ,
  3. Ожидайте, что testOrder.totalPrice, testOrder.totalTax и testOrder.totalQuantity будут соответствовать ожидаемым значениям

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

Примеры библиотек, которые вы можете использовать для написания модульных тестов на JavaScript:

  • Жасмин
  • мока
  • Шутка

Перекрытие функций

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

С другой стороны, существуют случаи сложного пользовательского интерфейса (UI), которые нельзя протестировать в модульном тесте. Модульные тесты обычно проверяют только то, что возвращает JS, без расчета всего экрана с HTML, CSS и JS. С помощью модульных тестов вы не можете проверить, можно ли нажать кнопку на данном экране.

Итак, если модульные тесты имеют эти ограничения, в то время как E2E может охватывать все, что делают наши модульные тесты, означает ли это, что нам нужен только E2E в нашем приложении?

Недостатки сквозного соединения

Есть набор тестов E2E, которым я доволен — он относительно быстрый (более 350 тестов выполняется за 15 минут), стабильный (случайные сбои случаются примерно один раз на каждые пять запусков) и хорошо справляется с обнаружением регрессий. Но даже хорошие E2E-тесты требуют много времени на разработку — когда они создаются, выполняются и поддерживаются. Давайте рассмотрим несколько причин, почему это так.

Комплексная настройка

Тесты, которые я упомянул, требуют:

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

Благодаря Docker и контейнеризации относительно легко разделить весь стек между разработчиками и CI-сервером.

Помимо требований, перечисленных выше, CI вводит несколько других движущихся частей:

  • сервер CI, который запускает задания.
  • Агенты CI, выполняющие задание, также работают в контейнерах Docker.
  • какое-то время у нас был координатор агентов CI, который запускал и останавливал агент CI в зависимости от запроса на сервер CI.

Таким образом, для запуска E2E в CI требуется несколько слоев облачных экземпляров: контейнеры Docker, работающие внутри контейнеров Docker, — короче говоря, множество вещей. Обычно вам просто нужно что-то одно, чтобы провалить весь тестовый прогон. Это создает ложноположительные сбои, которые, если они случаются слишком часто, приучают всю команду игнорировать сбой E2E — поведение, прямо противоположное тому, что мы хотели бы видеть.

Медленно в исполнении

Большинству E2E, которые я видел в своем наборе тестов, требуется от 5 до 15 секунд для запуска. Не так уж плохо, но даже в нижней части диапазона 350 тестов заняли бы полчаса, выполняя их один за другим. Общее время выполнения можно сократить, выполняя тесты параллельно. Это снова приносит несколько недостатков:

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

Медленно пишет

E2E и относительно медленный с точки зрения записи. При разработке время выполнения, о котором мы говорили выше, вносит задержки в цикл обратной связи разработчика. Что-то вроде 5–15 секунд — это немного, но эти секунды складываются, и это усложняет пребывание в продуктивной зоне.

Сила модульных тестов

В то же время модульные тесты имеют немало важных сильных сторон.

Быстрый

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

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

Я поддерживаю набор из 3200 модульных тестов, которые выполняются примерно за 30 секунд — всего около 1/100 секунды для теста. На этой скорости вы можете повторно запускать соответствующие тесты каждый раз, когда вы изменяете код, и получать почти немедленную обратную связь. Это очень помогает при разработке через тестирование (TDD): когда вы пишете код только после написания теста, проверяющего ожидаемое поведение.

Интерактивная спецификация

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

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

Если вы сравните «спецификацию в комментариях»:

// quantity has to be positive
if (order.quantity < 0) {
  order.quantity = 0
}

К спецификации в модульных тестах:

it(‘should reset quantity to 0 if negative’, () => {
  order.setQuantity(-1);
  expect(order.quantity).toEqual(0)
})

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

Помогает вам проектировать блоки

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

Что куда

Итак, если я оставлю и модульные тесты, и E2E, как мне решить, что нужно тестировать с помощью какого инструмента?

Дымовые тесты

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

Счастливый путь для пользовательских историй

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

Болезненные ошибки, которые часто затрагивают пользователей

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

Тонкие детали реализации

Есть много важных, но малозаметных поведений, которые было бы очень сложно протестировать непосредственно из графического интерфейса. Например:

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

Эти случаи идеально подходят для модульных тестов.

Все остальное

Если вы используете TDD, вы должны тестировать все, и единственный реальный способ сделать это — модульное тестирование.

Хотите узнать больше?

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

Первоначально опубликовано на https://how-to.dev.

Дополнительные материалы на PlainEnglish.io.

Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter, LinkedIn, YouTube и Discord .

Заинтересованы в масштабировании запуска вашего программного обеспечения? Ознакомьтесь с разделом Схема.