Изображение от fanjianhua на Freepik
Изображение от fanjianhua на Freepik

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

Позвольте мне рассказать вам историю...

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

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

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

Что мне теперь делать без этого человека? Во время адаптации нам были даны четкие инструкции следовать Пирамиде тестирования:

  • Написать множество модульных тестов.

  • Написать некоторые интеграционные тесты.

  • Написать тест end-to-end (E2E), покрывающий путь пользователя, если функция достаточно важна.

  • Провести ручное тестирование функционала.

И вот моей первой задачей было изменить форму регистрации (довольно важной части приложения).

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

  • Я написал модульные тесты.

  • Я написал интеграционные тесты.

  • Я написал тест E2E.

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

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

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

Мы решили внедрить больше проверок в наш CI/CD-процесс, чтобы убедиться, что новые сотрудники, как и я, не так легко ломали бы продукт.

Но это не предотвратит, то, что разработчики влияют на снижени  рейтинга SEO приложения, ухудшая значения основных веб-показателей (Core Web Vitals). Для этого нам нужно так же иметь тестирование производительности

Тесты производительности

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

Google идет еще дальше и повышает рейтинг SEO для веб-сайтов с лучшей производительностью. Он оценивает производительность на основе следующих трех метрик:

  • FID (First Input Delay) - Время первой реакции.

  • LCP (Largest Contentful Paint) - Время загрузки основного содержимого.

  • CLS (Cumulative Layout Shift) - Кумулятивный сдвиг компоновки.

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

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

Для достижения этой цели мы используем рекомендуемый инструмент - Lighthouse CI (этот инструмент поддерживается основными членами Google).

После интеграции библиотеки CI в вашу систему, написание тестов производительности довольно просто.

На самом деле, вам интересна всего одна команда: cy.lighthouse(), которая запускает проверки производительности с использованием библиотеки Cypress.

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

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

 

Мутационные тесты

И говоря о тестах, которых нам приходится писать много... Модульные (Unit) тесты существуют уже давно.

Они находятся в нижней части классической пирамиды тестирования. Считаются самыми важными, потому что они быстрые в выполнении и дешевые в написании. (Что может уже не быть таким актуальным в современной инфраструктуре Cloud Run).

Но как мы можем убедиться, что самая важная часть нашей тестовой инфраструктуры ведет себя как ожидается?

Что, если некоторые из наших тестов являются ложноположительными? Они показываются в нашем терминале и CI как успешные (зеленые), но на самом деле либо их пропускают, либо, что хуже всего, они написаны плохо и не проверяют результат функции, которую они тестируют.

Мутационное тестирование — это решение нашей проблемы. И оно работает немного иначе, чем обычные тесты. Обычно вы тестируете, что что-то работает, как ожидается, но мутационное тестирование проверяет, что что-то не работает, когда оно должно было бы не работать.

Давайте подумаем о простой функции сложения sum.

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

Например, она находит оператор + и может заменить его на другие операторы, такие как минус, умножение и т. д.

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

Вы можете впоследствии изучить мутацию, отлаживать и улучшать ваш код.

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

Визуальные тесты

Помните историю из начала статьи? Ну, она определенно не произошла бы, если бы у нас были визуальные тесты.

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

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

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

Они дают вам наибольшее доверие, когда дело касается части приложения, отвечающей за стили (CSS).

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

Если вам нужно нажать кнопку или прокрутить страницу до конца, внедрение взаимодействия также увеличивает непостоянство результатов (flakiness). 

Функциональные тесты

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

Интеграция различных компонентов и тестирование результата в терминале — это не то, что мы действительно хотим, ведь в основном это юнит-тесты с дополнительными шагами (мокирование, заглушивание и так далее).

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

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

Вы можете настраивать ваш компонент с разными параметрами или вообще без параметров и тестировать его стандартное поведение.

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

Это также хороший способ разрабатывать ваш компонент, если вам нравится писать тесты на основе TDD (разработки через тестирование).

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

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

Однако будьте осторожны с E2E-тестами, обычно они сопровождаются рядом проблем:

  • Они медленные.

  • Они зависят от бэкэнд-сервисов для динамической генерации контента.

  • API может иметь большие задержки по сравнению с периодом ожидания библиотеки E2E.

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

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

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

  • Мы реализовали функциональность пропуска в наших тестах с помощью мокирования куков или параметров URL. Пример: ?SKIP_LOGIN_FUNCTIONALITY=true, что имитирует вход пользователя (только в среде тестирования).

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

Вы можете подумать, что мокирование всего бэкэнда может убить смысл E2E-тестов. Но у нас есть сильные договорённости, используя контрактное тестирование.

 

Контрактные тесты

Мы обнаружили, что наиболее распространенной причиной всех наших багов было то, что ответ API отличался от ожидаемого фронтенд-приложением.

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

Теперь фронтенд-приложение, потребитель данных, объявляет контракт и указывает в каждой точке входа, какой ответ оно ожидает, каких значений и, что более важно, какого типа должны быть каждое значение. Конечно же, некоторые значения могут быть нулевыми.

Вы также объявляете в своем контракте сервисы, которые вы потребляете на случай, если у вас есть несколько API или вы являетесь бэкэнд-сервисом, потребляющим несколько микросервисов.

Контракт затем сохраняется в библиотеке тестирования, которую вы используете, мы выбрали PACT, и для каждой PR (запроса на внесение изменений в исходный код) бэкэнда выполняется проверка полученного от поставщика вывода по этому контракту. Если контракт не соблюдается, PR блокируется.

Комментарии (0)