Как писать интеграционные тесты
Перейти к содержимому

Как писать интеграционные тесты

  • автор:

Трофей тестирования фронтенда

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

Пирамида тестирования #

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

Пирамида тестирования

В пирамиде выделяют 3 вида тестов:

  • Юнит — тесты на отдельные модули, хелперы, компоненты в изоляции от всего остального приложения;
  • Интеграционные — тесты на взаимодействие нескольких модулей, либо UI (но без запуска браузера и реальных сетевых запросов);
  • Функциональные (или E2E, или UI) — тесты, когда запускается реальное окружение (браузер, тестовые стенды API) и тест прогоняется на странице в браузере, кликая по кнопкам, заполняя формы и работая с внешними сервисами/API.

Из этой пирамиды главное запомнить две вещи:

  • Чем вид тестов находится ближе к вершине, тем тесты полезнее для бизнеса
  • Чем ближе к основанию, тем тесты быстрее выполняются, их проще писать и поддерживать

Трофей тестирования #

Пирамида тестирования устарела

Но что касается фронтенда, мне намного больше нравится визуализация от крутого инженера Kent C. Dodds, который представил так называемый трофей тестирования. В нем сильно изменились пропорции и добавился новый слой — «Статические тесты»:

Трофей тестирования

Одна из основных мыслей, которая была заложена в трофей, звучит следующим образом:

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

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

Статические тесты #

Сегодня уже нет смысла доказывать, что TypeScript (или другой аналог со статической типизацией) приносит намного больше пользы в современные проекты со сложной логикой на клиенте, чем его отсутствие. Один из плюсов, TypeScript покрывает огромное количество кейсов, связанных с неверными типами, для которых раньше приходилось писать пачку unit-тестов. Вам очень повезло, если вы не писали такие тесты:

describe('sum', () => < it('должен как-то работать, если аргумент типа string', () =><>) it('должен как-то работать, если аргумент типа boolean', () => <>) it('должен как-то работать, если аргумент типа number', () => <>) it('должен как-то работать, если аргумент типа object', () => <>) // . >); 

Сейчас же достаточно задавать валидный тип аргументам, и TypeScript уже сам позаботится, чтобы вы не вызвали у строки метод числа, типа ‘ops’.toFixed(2) .

Так же есть ESLint и IDE, которые позволяют отлавливать синтаксические ошибки. Пропустить скобку, кавычку или использовать переменную до момента ее объявления становится практически невозможно (конечно же возможно, но вы будете об этом уведомлены).

Все это Кент вынес в слой статических тестов:

Слой статических тестов

Юнит тесты #

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

Слой юнит тестов

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

Важно, чтобы unit-тестов не было много, и не нужно 100% покрытие.

Обычно для юнит тестов прикручивают инструменты, которые позволяют определить, а остались ли в коде логические ветки для которых не написаны тесты. И заставляют разработчиков писать тесты для всего подряд, а так же стремится к 100% покрытию. Не делайте так! В теории это звучит логично, но на практике у вам будет огромная куча тестов, которые ничего не проверяют. Бум. У меня был рабочий проект с 25000 юнит тестами, которые прогонялись за 10 минут (это долго для юнитов, если что); процентов 80% из них были бесполезными (проверяли какие-то синтетические случаи, и были написали только ради зеленой галки в CI).

Вам повезло, если вам ничего не говорит следующий комментарий кода в начале файла:

/* istanbul ignore file */ 

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

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

Закрепим. Юнит тест подходят для библиотек, core- и сложной логики.

E2E тесты #

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

  • Для их написания нужно настроенное окружение — подготовленые стенды API, предоставляющие тестовые данные
  • Такие тесты тяжело писать, отлаживать и поддерживать
  • Они ну очень долгие

Слой E2E тестов

Представьте, что вы хотите протестировать регистрацию нового пользователя. Его нужно создать в базе данных, а что быть со следующим запуском теста (если пользователь уже будет в базе данных)? А что, если один тест запустят два разработчика на своих компьютерах? А если API перепускался и не работал пару минут во время прохождения тестов? Или во время прогона страница не доскролилась до нужного элемента? И это только вершина айсберга, подобных проблем много, и писать честные E2E довольно тяжело.

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

Интеграционные #

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

Интеграционные тесты

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

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

Еще одно преимущество, их можно писать не на все приложение, а на отдельные модули или виджеты. В контексте ныне полуряного микрофронтенда — на конкретный сервис.

Недостаточно? Еще один аргумент в их пользу — они пропагандируют подход black-box тестирования, когда мы не тестируем с разных сторон внутреннюю реализацию (как юниты), а тестуем внешний API модуля, как с ним будут взаимодействовать другие модули или пользователи. Тем самым при изменении внутренней реализации (сохранив внешний интерфейс использования), тесты не придется переписывать. Круто, да?

Самые частные кейсы для написания таких тестов:

  • Клик по интерактивному элементу (кнопка)
  • Открытие модальных окон
  • Заполнение элементов формы и проверка клиентской валидации
  • Отправка запроса на бекенд (сам запрос перехватывается и возвращаются замоканные данные) и отображение результата

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

Я могу порекомендавать два вида библиотек для таких тестов:

  • когда мы не хотим использовать браузер — однозначно testing-library. Очень крутое framework agnostic решение, которое позволяет писать тесты на наш UI
  • когда хотим — cypress или playwright, которые так же подходят и для E2E тестов

Скриншотоные #

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

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

Кастомный слой со скриншотными тестами

Если у вас есть стенд компонентов (например, Storybook), то идеальнее всего травить скриншотные тесты именно на заготовленные стори. Причем есть довольно много уже готовых решений (например, loki), которые максимально упростят настройку инфраструктуры. У меня в текущем проекте несколько разных сторибуков, для всех них используем скриншотное тестирование (на момент написания статьи это пока в планах).

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

Скриншот из сторибука. Стейка захотелось.

Скриншот сторибука из одного моего проекта

Что по итогу? #

Мы прошлись по всем слоям (уровням) трофея и получили идеальный вариант для современного среднестатистический приложения:

Финальная версия трофея тестирования

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

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

А какой трофей тестирования у вас?

А какой трофей тестирования у вас?

В одном (справа), мы очень строго покрываем все TypeScript-ом (в проекте у нас нет ни одного any и пару @ts-expect-error ), но совсем нет E2E тестов (из-за сложности подготовить тестовое окружение). Так же этот проект запускался в довольно сжатые сроки (совсем не до E2E тестов), поэтому в таких рамках — этот вариант трофея близок к идеальному.

В другом (средний) — очень много E2E и unit-тестов. Проект огромный, поддерживается сразу 6 командами, поэтому E2E помогают отловить многие ошибки интеграций. Но если быть честным, 80% тестов (что E2E, что юнитов) бесполезные, поэтому этот вариант для этого проекта не оправдал себя. Нужно добавлять интеграционные тесты и сильно сокращать текущие выпуклые слои.

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

А что получилось у вас?

Оставайтесь на связи

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

Написание интеграционных тестов с использованием PageObjects

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

Обзор

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

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

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

Шаг 1. Настройка вашего плагина

Лучший способ начать создание собственных объектов страницы — включить объекты страницы JIRA в качестве ссылки и базы для ваших объектов. Поэтому вам нужно изменить свой pom.xml и добавить следующее:

  com.atlassian.jira atlassian-jira-pageobjects $ test   

Шаг 2. Анатомия объекта страницы

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

 public class CsvSetupPage extends AbstractJiraPage < @ElementBy(id = "nextButton") protected PageElement nextButton; @ElementBy(cssSelector = "#advanced h3.toggle-title") private PageElement advanced; @Override public TimedCondition isAt() < return nextButton.timed().isVisible(); >@Override public String getUrl() < return "/secure/admin/views/CsvSetupPage!default.jspa?externalSystem=com.atlassian.jira.plugins.jira-importers-plugin:csvImporter"; >public CsvSetupPage setCsvFile(String resource) < csvFile.type(resource); return this; >public CsvSetupPage setDelimiter(String delimiter) < showAdvanced(); this.delimiter.clear(); this.delimiter.type(delimiter); return this; >public CsvProjectMappingsPage next() < Poller.waitUntilTrue(nextButton.timed().isEnabled()); nextButton.click(); return pageBinder.bind(CsvProjectMappingsPage.class); >> 

Введем пару элементов:

  • PageElement — это элемент DOM, с которым вы хотите взаимодействовать (изменять, кликать, читать или писать)
  • Аннотирование @ElementBy определяет, как искать элемент; вы можете искать такие вещи, как id, cssSelector, name и tag
  • Метод isAt используется AbstractJiraPage для определения того, была ли загрузка страницы завершена и загружена ожидаемая страница; он использует внутреннюю аннотацию @WaitUntil
  • Метод getUrl , определенный в AbstractJiraPage , указывает, куда идти, чтобы открыть ожидаемую страницу
  • Объект pageBinder отвечает за привязку объекта страницы к DOM, следующий метод показывает, как перемещаться с одной страницы на другую

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

Шаг 3. Использование объектов страницы в ваших тестах

Я предполагаю, что вы используете JIRA Func Test Basics для создания теста интеграции. Чтобы упростить ситуацию, давайте предположим, что вы расширили TestBase (проверьте документацию JIRA Func Test Basics для лучшего, предлагаемого способа написания интеграционных тестов).

 public class TestCsvSetupPage extends TestBase < @Test public void testSetupPage() < CsvSetupPage setupPage = jira().gotoLoginPage() .loginAsSysAdmin(CsvSetupPage.class); CsvProjectMappingPage mappingPage = setupPage .setCsvFile("JIM-77.csv").setDelimiter(";").next(); >> 

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

Шаг 4. Запись утверждений для элементов

Вот как вы можете проверить, делает ли ваш код то, что вы ожидаете от него. Чтобы избежать условий гонки, ваш объект страницы всегда должен возвращать TimedQuery или TimedCondtion . Давайте проверим, правильно ли прочитано userEmailSuffix из конфигурации.

 public class CsvProjectMappingPage extends AbstractJiraPage < @ElementBy(name = "userEmailSuffix") PageElement userEmailSuffix; // removed for brevity public TimedQuerygetUserEmailSuffix() < return userEmailSuffix.timed().getAttribute("value"); >> 

Ваш тест может выглядеть следующим образом:

 public TestCsvProjectMappingPage extends TestBase < @Test public void testIfConfigurationIsReadProperly() < CsvSetupPage setupPage = jira().gotoLoginPage() .loginAsSysAdmin(CsvSetupPage.class); CsvProjectMappingPage projectMappingPage = setupPage .setCsvFile("JIM-80.csv") .setConfigurationFile("JIM-80.config").next(); Poller.waitUntil(projectMappingsPage.getUserEmailSuffix(), (Matcher) equalTo("atlassian.com")); > > 

Используйте Poller.waitUntil для проверки утверждения. Он будет опрашивать объект страницы до тех пор, пока не будет выполнено предположение или не пройдет тайм-аут. Это здорово, если у вас есть элементы, которые динамически создаются, или вы используете AJAX для извлечения данных. Он будет следить за тем, чтобы в вашем коде не было условий гонки.

Шаг 5. Написание утверждений для коллекций

Представьте, что мы хотим проверить, отображаются ли на странице правильные подсказки для пользователя. Вот объект страницы:

 public class CsvFieldMappingsPage extends AbstractJiraPage < @Inject private Timeouts timeouts; @Inject private ExtendedElementFinder extendedElementFinder; @ElementBy (className = "bottom-wizard-hints") PageElement bottomWizardHints; // removed for brevity public TimedQuery> getBottomWizardHints() < return Queries.forSupplier(timeouts, extendedElementFinder.within(bottomWizardHints) .newQuery(By.className("hint")) .supplier()); >> 

Вы можете проверить, содержит ли страница правильные подсказки в вашем тесте следующим образом:

 Poller.waitUntil(fieldMappingsPage.getBottomWizardHints(), IsIterableWithSize.iterableWithSize(3)); assertThat(PageElements.asText(fieldMappingsPage.getBottomWizardHints().now()), IsCollectionContaining.hasItems( containsString("For issues with multiple"), containsString("Existing custom fields must"), containsString("CSV file has at least one empty")));  

Шаг 6. Выполнение тестов на CI

Существует также простой способ запуска интеграционных тестов в режиме без заголовков в CI, все, что вам нужно сделать, это отредактировать pom.xml и установить свойство xvfb.enable :

  true   

Atlassian Plugin SDK автоматически запустит Xvfb и проведет ваши тесты, используя его.

Шаг 7. Еще больше ресурсов для объектов страницы

Также есть подробное руководство, которое было опубликовано в блоге разработчика Дариушем Кордонски. Проверьте это, чтобы узнать больше!

Поздравляю, вот и все

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

ПОХОЖИЕ ТЕМЫ

  • Написание интеграционных тестов для вашего JIRA-плагина
  • Интеллектуальное тестирование с TestKit

Как наконец-то начать писать тесты и не пожалеть об этом

Приходя на новый проект, я регулярно сталкиваюсь с одной из следующих ситуаций:

  1. Тестов нет совсем.
  2. Тестов мало, их редко пишут и не запускают на постоянной основе.
  3. Тесты присутствуют и включены в CI (Continuous Integration), но приносят больше вреда, чем пользы.

Что можно сделать, чтобы изменить сложившуюся ситуацию? Идея использования тестов не нова. При этом большинство туториалов напоминают знаменитую картинку про то, как нарисовать сову: подключаем JUnit, пишем первый тест, используем первый мок — и вперед! Такие статьи не отвечают на вопросы о том, какие тесты нужно писать, на что стоит обращать внимание и как со всем этим жить. Отсюда и родилась идея данной статьи. Я постарался кратко обобщить свой опыт внедрения тестов в разных проектах, чтобы облегчить этот путь для всех желающих.

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

Так как моя основная специализация — Java backend, то в примерах будет использован следующий стек технологий: Java, JUnit, H2, Mockito, Spring, Hibernate. При этом значительная часть статьи посвящена общим вопросам тестирования и советы в ней применимы к гораздо более широкому кругу задач.

Однако будьте осторожны! Тесты вызывают сильнейшую зависимость: однажды научившись ими пользоваться, вы уже не сможете без них жить.

Содержание

Тесты vs скорость разработки

Главные вопросы, которые возникают при обсуждении внедрения тестирования: сколько времени займет написание тестов и какие преимущества это будет иметь? Тестирование, как и любая другая технология, потребует серьезных усилий на освоение и внедрение, поэтому на первых порах никакой значимой выгоды ожидать не стоит. Что касается временных затрат, то они сильно зависят от конкретной команды. Однако меньше чем на 20–30 % дополнительных затрат на кодирование рассчитывать точно не стоит. Меньшего просто не хватит для достижения хоть какого-то результата. Ожидание мгновенной отдачи часто является главной причиной сворачивания этой деятельности еще до того, как тесты станут приносить пользу.

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

Запуск кода в произвольном месте

При отсутствии тестов в проекте единственным способом запуска является поднятие приложения целиком. Хорошо, если на это будет уходить секунд 15–20, но далеко не редки случаи больших проектов, в которых полноценный запуск может занимать от нескольких минут. Что же это означает для разработчиков? Существенную часть их рабочего времени будут составлять эти короткие сессии ожидания, на протяжении которых нельзя продолжать работать над текущей задачей, но при этом времени на переключение на что-то другое слишком мало. Многие хотя бы раз сталкивались с такими проектами, где написанный за час код требует многочасовой отладки из-за долгих перезапусков между исправлениями. В тестах же можно ограничиться запуском маленьких частей приложения, что позволит значительно сократить время ожидания и повысит продуктивность работы над кодом.

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

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

Повторный запуск тестов

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

Во-первых, каждый новый пришедший на проект разработчик сможет легко запустить имеющиеся тесты, чтобы разобраться в логике приложения на примерах. К сожалению, важность этого сильно недооценена. В современных условиях одни и те же люди редко работают над проектом дольше 1–2 лет. А так как команды состоят из нескольких человек, то появление нового участника каждые 2–3 месяца — типичная ситуация для относительно крупных проектов. Особо тяжелые проекты переживают смены целых поколений разработчиков! Возможность легко запустить любую часть приложения и посмотреть на поведение системы в разы упрощает погружение новых программистов в проект. Кроме того, более детальное изучение логики кода уменьшает количество допущенных ошибок на выходе и время на их отладку в будущем.

Во-вторых, возможность легко убедиться в том, что приложение работает корректно, открывает дорогу для непрерывного рефакторинга (Continuous Refactoring). Этот термин, к сожалению, гораздо менее популярен, чем CI. Он означает, что рефакторинг можно и нужно делать при каждой доработке кода. Именно регулярное следование небезызвестному правилу бойскаута «оставь место стоянки чище, чем оно было до твоего прихода», позволяет избегать деградации кодовой базы и гарантирует проекту долгую и счастливую жизнь.

Отладка

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

Эффективность

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

От теории к практике

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

Задача

В качестве шаблонной задачки рассмотрим небольшой фрагмент бэкенда интернет-магазина. Напишем типовой API для работы с продуктами: создание, получение, редактирование. А также пару методов для работы с клиентами: смена «любимого продукта» и расчет бонусных баллов по заказу.

Доменная модель

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

У клиента (Customer) есть логин, ссылка на любимый продукт и флаг, указывающий на то, является ли он премиальным клиентом.

У продукта (Product) — название, цена, скидка и флаг, указывающий на то, рекламируется ли он в данный момент.

Структура проекта

Структура основного кода проекта выглядит следующим образом.

Классы разбиты по слоям:

  • Model — доменная модель проекта;
  • Jpa — репозитории для работы с БД на основе Spring Data;
  • Service — бизнес-логика приложения;
  • Controller — контроллеры, реализующие API.

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

Удобно разделять юнит-тесты и интеграционные тесты. Они зачастую имеют разные зависимости, и для комфортной разработки должна быть возможность запустить либо одни, либо другие. Этого можно добиться разными способами: конвенции именования, модули, пакеты, sourceSets. Выбор конкретного способа — исключительно вопрос вкуса. В данном проекте интеграционные тесты лежат в отдельном sourceSet — integrationTest.

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

Интеграционные тесты

Есть разные подходы к тому, с каких тестов стоит начинать. В случае, если проверяемая логика не очень сложна, можно сразу переходить к интеграционным (их еще иногда называют приемочными — acceptance). В отличие от юнит-тестов они позволяют убедиться, что приложение в целом работает корректно.

Архитектура

Для начала надо определиться, на каком конкретно уровне будут выполняться интеграционные проверки. Spring Boot предоставляет полную свободу выбора: можно поднимать часть контекста, весь контекст и даже полноценный сервер, доступный из тестов. При увеличении размера приложения этот вопрос становится все более сложным. Часто приходится писать разные тесты на разных уровнях.

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

@PostMapping("new") public Product createProduct(@RequestBody Product product) < return productService.createProduct(product); >@GetMapping("") public Product getProduct(@PathVariable("productId") long productId) < return productService.getProduct(productId); >@PostMapping("/edit") public void updateProduct(@PathVariable("productId") long productId, @RequestBody Product product)

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

@Transactional(readOnly = true) public Product getProduct(Long productId) < return productRepository.findById(productId) .orElseThrow(() ->new DataNotFoundException("Product", productId)); > @Transactional public Product createProduct(Product product) < return productRepository.save(new Product(product)); >@Transactional public Product updateProduct(Long productId, Product product) < Product dbProduct = productRepository.findById(productId) .orElseThrow(() ->new DataNotFoundException("Product", productId)); dbProduct.setPrice(product.getPrice()); dbProduct.setDiscount(product.getDiscount()); dbProduct.setName(product.getName()); dbProduct.setIsAdvertised(product.isAdvertised()); return productRepository.save(dbProduct); >

Репозиторий ProductRepository вообще не содержит собственных методов:

public interface ProductRepository extends JpaRepository

Все намекает на то, что юнит-тесты этим классам не нужны просто потому, что всю цепочку можно легко и эффективно проверить несколькими интеграционными тестами. Дублирование одних и тех же проверок в разных тестах приводит к усложнению отладки. В случае появления ошибки в коде теперь упадет не один тест, а сразу 10–15. Это в свою очередь потребует дальнейшего анализа. Если же дублирования нет, то единственный упавший тест, скорее всего, сразу укажет на ошибку.

Конфигурация

Для удобства выделим базовый класс BaseControllerIT , который содержит конфигурацию Spring и пару полей:

@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE) @Transactional public abstract class BaseControllerIT

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

Основная конфигурация Spring задается следующими строчками:

@SpringBootTest — используется для того, чтобы задать контекст приложения. WebEnvironment.NONE означает, что веб-контекст поднимать не надо.

@Transactional — оборачивает все тесты класса в транзакцию с автоматическим откатом для сохранения состояния базы.

Структура теста

Перейдем к минималистичному набору тестов для класса ProductController — ProductControllerIT .

@Test public void createProduct_productSaved()

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

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

assertEquals(product, dbProduct);

В другом тесте на обновление информации о продукте ( updateProduct ) видно, что создание данных стало немного сложнее и для сохранения визуальной целостности трех частей теста они отделены двумя переводами строк подряд:

@Test public void updateProduct_productUpdated()

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

Тестовые билдеры

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

public static ProductBuilder product(String name)

Название теста

Крайне важно понимать, что конкретно проверяется в данном тесте. Для наглядности лучше всего дать ответ на этот вопрос в его названии. На примере тестов для метода getProduct рассмотрим используемую конвенцию именования:

@Test public void getProduct_oneProductInDb_productReturned() < Product product = product("productName").build(); productRepository.save(product); Product result = productController.getProduct(product.getId()); assertEquals("productName", result.getName()); >@Test public void getProduct_twoProductsInDb_correctProductReturned()

В общем случае заголовок тестового метода состоит из трех частей, разделенных подчеркиванием: имя тестируемого метода, сценарий, ожидаемый результат. Однако здравый смысл никто не отменял, и вполне оправданным может быть опускание каких-то частей названия, если они не нужны в данном контексте (например, сценарий в единственном тесте на создание продукта). Цель такого именования — добиться того, чтобы суть каждого теста была понятна без изучения кода. Это делает окошко результатов прохождения тестов максимально наглядным, а именно с него обычно и начинается работа с тестами.

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

Стоит обратить внимание, что такие тесты не проверяют веб-слой приложения, однако зачастую этого и не требуется. При необходимости можно написать отдельные тесты для веб-слоя с заглушкой вместо базы ( @WebMvcTest , MockMvc , @MockBean ) или использовать полноценный сервер. Последнее может затруднить отладку и усложнить работу с транзакциями, поскольку транзакцию сервера тест уже контролировать не сможет. Пример такого интеграционного теста можно посмотреть в классе CustomerControllerServerIT .

Юнит-тесты

Юнит-тесты имеют ряд преимуществ перед интеграционными:

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

Единственный класс в данном примере, который заслуживает юнит-тестирования, — это BonusPointCalculator . Его отличительная особенность — большое количество ветвлений бизнес-логики. Например, предполагается, что покупатель получает бонусами 10 % от стоимости продукта, помноженные на не более чем 2 мультипликатора из следующего списка:

  • Продукт стоит больше 10 000 (× 4);
  • Продукт участвует в рекламной кампании (× 3);
  • Продукт является «любимым» продуктом клиента (× 5);
  • Клиент имеет премиальный статус (× 2);
  • В случае, если клиент имеет премиальный статус и покупает «любимый» продукт, вместо двух обозначенных мультипликаторов используется один (× 8).
private List calculateMultipliers(Customer customer, Product product) < Listmultipliers = new ArrayList<>(); if (customer.getFavProduct() != null && customer.getFavProduct().equals(product)) < if (customer.isPremium()) < multipliers.add(PREMIUM_FAVORITE_MULTIPLIER); >else < multipliers.add(FAVORITE_MULTIPLIER); >> else if (customer.isPremium()) < multipliers.add(PREMIUM_MULTIPLIER); >if (product.isAdvertised()) < multipliers.add(ADVERTISED_MULTIPLIER); >if (product.getPrice().compareTo(EXPENSIVE_THRESHOLD) >= 0) < multipliers.add(EXPENSIVE_MULTIPLIER); >return multipliers; >

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

Соответствующий набор тестов можно посмотреть в классе BonusPointCalculatorTest . Вот некоторые из них:

@Test public void calculate_oneProduct() < Product product = product("product").price("1.00").build(); Customer customer = customer("customer").build(); Mapquantities = mapOf(product, 1L); BigDecimal bonus = bonusPointCalculator.calculate(customer, list(product), quantities::get); BigDecimal expectedBonus = bonusPoints("0.10").build(); assertEquals(expectedBonus, bonus); > @Test public void calculate_favProduct() < Product product = product("product").price("1.00").build(); Customer customer = customer("customer").favProduct(product).build(); Mapquantities = mapOf(product, 1L); BigDecimal bonus = bonusPointCalculator.calculate(customer, list(product), quantities::get); BigDecimal expectedBonus = bonusPoints("0.10").addMultiplier(FAVORITE_MULTIPLIER).build(); assertEquals(expectedBonus, bonus); >

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

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

@Test public void calculateBonusPoints_twoProductTypes_correctValueCalculated() < Product product1 = product("product1").price("1.01").build(); Product product2 = product("product2").price("10.00").build(); productRepository.save(product1); productRepository.save(product2); Customer customer = customer("customer").build(); customerRepository.save(customer); Mapquantities = mapOf(product1.getId(), 1L, product2.getId(), 2L); BigDecimal bonus = customerController.calculateBonusPoints( new CalculateBonusPointsRequest("customer", quantities) ); BigDecimal bonusPointsProduct1 = bonusPoints("0.10").build(); BigDecimal bonusPointsProduct2 = bonusPoints("1.00").quantity(2).build(); BigDecimal expectedBonus = bonusPointsProduct1.add(bonusPointsProduct2); assertEquals(expectedBonus, bonus); >

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

Рекомендации по внедрению

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

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

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

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

Не увлекайтесь юнит-тестами. В продолжение темы «количество vs качество» нужно отметить, что честными юнит-тестами первое время увлекаться не стоит, потому что это легко может привести к чрезмерной спецификации приложения. В свою очередь это станет серьезным тормозящим фактором при последующем рефакторинге и доработках приложения. Юнит-тесты следует использовать только при наличии сложной логики в конкретном классе или группе классов, которую неудобно проверять на уровне интеграционных.

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

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

  1. Для запусков тестов нужно будет настраивать внешнее окружение. Например, устанавливать базу данных на каждую машину, где будет собираться приложение. Это усложнит вход новых разработчиков в проект и настройку CI.
  2. Состояние внешних систем может отличаться на разных машинах перед запуском тестов. Например, в базе могут уже находиться нужные приложению таблицы с данными, которые не ожидаются в тесте. Это приведет к непредсказуемым сбоям в работе тестов, и их устранение потребует значительного количества времени.
  3. В случае, если ведется параллельная работа над несколькими проектами, возможно неочевидное влияние одних проектов на другие. Например, специфические настройки базы, выполненные для одного из проектов, смогут помочь корректно работать функционалу другого проекта, который, однако, сломается при запуске на чистой базе на другой машине.
  4. Тесты выполняются долго: полный прогон может достигать десятков минут. Это приводит к тому, что разработчики перестают запускать тесты локально и смотрят на их результаты только после отправки изменений в удаленный репозиторий. Такое поведение сводит на нет большинство плюсов тестов, о которых говорилось в первой части статьи.

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

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

Не зацикливайтесь на TDD (Test-Driven Development). TDD является довольно популярной практикой, однако я не считаю ее обязательной, особенно на первых этапах внедрения. В целом, умение писать хорошие тесты не связано с тем, в какой момент они написаны. Что действительно важно, так это делать первичную отладку кода уже на тестах, поскольку это один из основных способов экономии времени.

Первые тесты написаны, что дальше?

Далее надо внимательно наблюдать за жизнью тестов в проекте и периодически задавать себе вопросы, подобные следующим:

  1. Какие тесты мешают рефакторингу и доработкам (требуют постоянных исправлений)? Такие тесты требуется переписать либо полностью удалить из проекта и заменить более высокоуровневыми.
  2. Какие тесты часто и непредсказуемо ломаются при многократном либо параллельном запуске, при запуске в разных средах (компьютер коллеги, сервер CI)? Они также требуют переработки.
  3. Какие ошибки проходят мимо тестов? На каждый такой баг желательно добавлять новый тест и в будущем иметь их в виду при написании тестов для аналогичного функционала.
  4. Какие тесты работают слишком долго? Нужно постараться их переписать. Если это невозможно, то отделить их от более быстрых, чтобы сохранить возможность оперативного локального прогона.

Заключение

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

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

Интеграционное и системное тестирование

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

Время чтения: 5 мин

Открыть/закрыть навигацию по статье

  1. Кратко
  2. Интеграционное тестирование
    1. Когда использовать
    2. Какие есть инструменты
    3. Рекомендации к тестам
    1. Когда использовать
    2. Инструменты
    1. Когда использовать
    2. Инструменты
    1. Когда использовать
    2. Инструменты

    Обновлено 16 июля 2022

    Юнит-тесты, как правило, занимают наибольший объём тестов в проекте. Ограничение же юнит-тестов в том, что они не проверяют, как модули работают в связи друг с другом.

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

    В этой статье мы говорим о тестировании. Если вы не знакомы с тестами, мы рекомендуем сперва прочитать статью «Как и зачем писать тесты», а потом вернуться сюда.

    Кратко

    Скопировать ссылку «Кратко» Скопировано

    Интеграционное тестирование проверяет работу нескольких модулей в группе. Системное тестирование проверяет программную систему полностью.

    Они решают проблему, которая остаётся после покрытия кода юнит-тестами — проверяют, как модули работают в связи друг с другом. Системное тестирование более обширно и само делится на несколько видов тестов по их типу и назначению.

    Системные и интеграционные тесты тоже можно автоматизировать и встроить в CI/CD проекта.

    Интеграционное тестирование

    Скопировать ссылку «Интеграционное тестирование» Скопировано

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

    Например, проверка одного из сценариев регистрации на бэкенде может быть описана в виде интеграционного теста. Такая проверка затронет и API-эндпоинты, и контроллеры, и общение с базой данных.

    Когда использовать

    Скопировать ссылку «Когда использовать» Скопировано

    Когда мы хотим проверить работу нескольких модулей, но не всего проекта в целом.

    Какие есть инструменты

    Скопировать ссылку «Какие есть инструменты» Скопировано

    Среди самых популярных инструментов можно назвать:

    Рекомендации к тестам

    Скопировать ссылку «Рекомендации к тестам» Скопировано

    Выбрать подход

    Есть два подхода к интеграции модулей для тестирования: «большой взрыв» и «инкрементальная интеграция». В первом подходе мы объединяем все модули, а затем проверяем сценарии.

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

    Определить критические фичи

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

    Подготовить тестовые данные

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

    Подготовить заглушки и драйверы

    Заглушки и драйверы похожи на стабы и моки. Заглушка вызывается тестируемой группой модулей — проверяет «выход» группы; а драйвер вызывает тестируемую группу — подаёт сигнал на «вход».

    Системное тестирование

    Скопировать ссылку «Системное тестирование» Скопировано

    Системное тестирование — это целый класс тестов. Их объединяет общий признак: они тестируют программную систему полностью.

    Их много, но мы выделим самые часто используемые:

    • End-to-end тесты;
    • скриншотные тесты;
    • тесты безопасности;
    • тесты производительности.

    E2E-тестирование

    Скопировать ссылку «E2E-тестирование» Скопировано

    End-to-end (E2E) тесты — помогают нам имитировать, как пользователи будут работать с нашей программой.

    Отсюда и end — проверка поведения конечного потребителя.

    Обычно такие тесты описывают сценарий для теста в терминах действий пользователя, например, в браузере:

    • зайти на такую-то страницу;
    • нажать такую-то кнопку;
    • увидеть такой-то текст.

    Когда использовать

    Скопировать ссылку «Когда использовать» Скопировано

    Когда мы хотим проверить, как ведёт себя вся программа полностью при различных действиях пользователей.

    Инструменты

    Скопировать ссылку «Инструменты» Скопировано

    Для описания таких сценариев у нас тоже богатый набор инструментов:

    Можно также адаптировать и инструменты для интеграционного тестирования под E2E нужды.

    Скриншотное тестирование

    Скопировать ссылку «Скриншотное тестирование» Скопировано

    Скриншотным тестированием мы проверяем регрессии пользовательского интерфейса. Сперва мы делаем скриншот-эталон, с которым потом сравниваем интерфейс после изменений.

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

    Когда использовать

    Скопировать ссылку «Когда использовать» Скопировано

    Когда мы хотим проверить, что UI остался неизменным после изменения кода.

    Инструменты

    Скопировать ссылку «Инструменты» Скопировано

    Некоторые из E2E-инструментов содержат и функциональность для скриншотного тестирования:

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

    Скопировать ссылку «Тестирование производительности» Скопировано

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

    Во фронтенде мы можем замерять так называемые web vitals — показатели скорости загрузки, отзывчивости и стабильности UI.

    Мы также сперва делаем эталонные замеры, с которыми затем сравниваем показатели после изменений кода.

    Когда использовать

    Скопировать ссылку «Когда использовать» Скопировано

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

    Инструменты

    Скопировать ссылку «Инструменты» Скопировано

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

    Тестирование безопасности

    Скопировать ссылку «Тестирование безопасности» Скопировано

    Это больше относится к бэкенду, но иногда такие тесты пишут и для фронтенд-кода тоже. Чаще всего во фронтенде проверяют экранирование пользовательского ввода, защищённость куки и запросы к API.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *