Нужны ли вашему веб-приложению сквозные и модульные тесты?

Нужны ли вашему веб-приложению сквозные и модульные тесты?

23 февраля 2023 г.

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

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

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

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

От начала до конца

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

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

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

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

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

* Кипарис * Драматург * Ночной дозор * (устарело) Protractor для приложений 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, вы должны тестировать все, и единственный реальный способ сделать это — модульное тестирование.

:::информация Также опубликованы здесь.

:::


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE