Сценарий интеграционного тестирования примеры

Что такое интеграционное тестирование? Зачем оно нужно? Примеры, подходы, стратегия и методологии...​

Стратегии, методологии и подходы в интеграционном тестировании

Программная инженерия задает различные стратегии интеграционного тестирования:

  • Подход Большого взрыва.
  • Инкрементальный подход:
    • Нисходящий подход (сверху вниз)
    • Подход «снизу вверх»
    • Сэндвич – комбинация «сверху вниз» и «снизу вверх»

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

Подход Большого взрыва

Здесь все компоненты собираются вместе, а затем тестируются.

Преимущества:

  • Удобно для небольших систем.

Недостатки:

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


Инкрементальный подход

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

  • Снизу вверх
  • Сверху вниз


Заглушка и драйвер

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

Заглушка: вызывается тестируемым модулем.
Драйвер: вызывает модуль для тестирования.

Интеграция «снизу вверх»

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

Преимущества:

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

Недостатки:

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


Интеграция «сверху вниз»

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

Преимущества:

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

Недостатки:

  • Нужно много пней.
  • Модули на более низком уровне тестируются неадекватно

Сэндвич (гибридная интеграция)

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

Как сделать интеграционное тестирование?

Алгоритм интеграционного тестирования:

  1. Подготовка план интеграционных тестов
  2. Разработка тестовых сценариев.
  3. Выполнение тестовых сценариев и фиксирование багов.
  4. Отслеживание и повторное тестирование дефектов.
  5. Повторять шаги 3 и 4 до успешного завершения интеграции.

Атрибуты Интеграционного тестирования

Включает в себя следующие атрибуты:

  • Методы / Подходы к тестированию (об этом говорили выше).
  • Области применения и Тестирование интеграции.
  • Роли и обязанности.
  • Предварительные условия для Интеграционного тестирования.
  • Тестовая среда.
  • Планы по снижению рисков и автоматизации.

Критерии старта и окончания интеграционного тестирования

Критерии входа и выхода на этап Интеграционного тестирования, независимо от модели разработки программного обеспечения

Критерии старта:

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

Критерии окончания:

  • Успешное тестирование интегрированного приложения.
  • Выполненные тестовые случаи задокументированы
  • Все ошибки с высоким приоритетом исправлены и закрыты
  • Технические документы должны быть представлены после выпуска Примечания.

Лучшие практики / рекомендации по интеграционному тестированию

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

Статья подготовлена на основе материалов сайта guru99.com

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

Содержание

Что такое интеграционное тестирование
Определение
Зачем делать интеграционное тестирование
Пример интеграционного тест кейса
Подходы, стратегии, методологии интеграционного тестирования
Подход Большой Взрыв
Инкрементальный подход
Восходящий подход (Bottom-Up)
Нисходящий подход (Top-Down)
Смешанный подход — сэндвич
Как организовать интеграционное тестирование
Краткое описание интеграционных тест планов
Входные и выходные критерии интеграционного тестирования
Руководства и советы
Похожие статьи

Что такое интеграционное тестирование

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

Разработчики провели модульное тестирование
и убедились, что все необходимые юнит тесты (Unit Tests) пройдены.

Эти системы нужно объединить в одну. Логичный вопрос:

Будет ли новая большая система работать так же хорошо как и её части?

Чтобы ответить на него нужно провести тестирование системы (System Testing).

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

Есть ли смысл тестировать систему целиком в данный момент?

Взаимодействуют ли части между собой правильно?

Ответить на эти вопросы можно только после интеграционного тестирования (Integration Testing).

Лирическое отступление

Пропустить

Рассмотрим аналогию далёкую от IT. У Вас есть склад и два отряда новобранцев: пожарные и крестьяне. Нужно проверить насколько быстро пожарные
носят воду, а крестьене сеют пшеницу. Результатом будет, например тысяча литров в сутки и один гектар в день. Это аналог системного тестирования:
поле засеяно, вода перенесена.

Но что если подходя ко складу каждый пожарный будет брать сито вместо ведра а крестьянам придётся пользоваться оставшимися вёдрами?

вода в решете www.andreyolegovich.ru

Воду несут в решете, а сеют через ведро — есть ли смысл тратить сутки на такой тест? Даст ли он Вам какую-то полезную информацию? Думаю, ответ очевиден.

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

Это и будет интеграционным тестированием взаимодействия новобранцев со складом.

Определение

ИНТЕГРАЦИОННОЕ ТЕСТИРОВАНИЕ определяется как тип тестирования,
при котором программные модули интегрируются логически и тестируются как группа.

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

Целью данного уровня тестирования является выявление дефектов взаимодействия между этими
программными модулями при их интеграции.

Интеграционное тестирование фокусируется на проверке обмена данными между этими модулями.
Следовательно, его также называют «I & T» (интеграция и тестирование), «тестирование строк» и
иногда «тестирование потоков».

Ещё пара комментариев о том, что можно считать интеграционным тестированием:

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

Это по-прежнему юнит-тест, интеграционного тестирования здесь нет.

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

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

Зачем делать интеграционное тестирование

Хотя каждый программный модуль проходит модульное тестирование (Unit Testing),
дефекты все еще существуют по разным причинам, таким как:

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

Пример интеграционного тест кейса

Рассмотрим простой пример с картинками.

Допустим я тестировщик из

Aviasales

и хочу проверить как работает интеграция с сайтом

Booking.com

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

Как будет выглядеть мой тест в упрощённом виде:

Test Case ID Test Case Objective Test Case Description Expected Result
1 Проверить работу кнопки «ОТЕЛИ» Перейти на страницу «Поиск отелей со скидками» нажав на кнопку «ОТЕЛИ» на главной странице Показана страница поиска отелей на сайте

Aviasales

2 Проверить интерфейс между сайтом aviasales.ru и сайтом booking.com Перейти на сайт

Booking.com

нажав на кнопку «Найти отели»

Осуществлён переход на сайт

Booking.com

Aviasales указан в качестве партнёра.

3 Проверить интеграцию Booking.com с картами Google Нажать кнопку «На карте» и убедиться, что отели видны. Карта открыта и на ней можно увидеть отели

Test Case ID — это номер теста. Test Case Objective — цель. Test Case Description — описание. Expected Result — ожидаемый результат.

Теперь разберём действия пошагово.

Нужно зайти на сайт

Aviasales

и выбрать какой-то маршрут.

Допустим, я соскучился по

Коста-дель-Соль

и хочу билет в

Малагу

,
заполняю формы и нажимаю кнопку «Отели»

Наглядный пример интеграционного теста www.andreyolegovich.ru

Изображение с сайта

Aviasales

Переход на новую страницу осуществлён, но я по-прежнему на том же сайте.

Нужно нажать кнопку «Найти отели»

Наглядный пример интеграционного теста www.andreyolegovich.ru

Изображение с сайта

Aviasales

Переход осуществлён, на сайте букинга есть упоминание Авиаcейлз. Интеграция
Aviasales — Booking работает.

Проверим интеграцию Booking — Google Maps. Нажимаем кнопку «На карте»

Наглядный пример интеграционного теста www.andreyolegovich.ru

Изображение с сайта

Booking.com

Отели видны на карте. Интеграция Booking — Google Maps работает.

Интересно почему у Aviasales интеграция с Booking, когда у них есть
свой агрегатор отелей —
Hotellook

Наглядный пример интеграционного теста www.andreyolegovich.ru

Изображение с сайта

Booking.com

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

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

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

Подходы, стратегии, методологии интеграционного тестирования

Подход Большой Взрыв

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

Затем начинается тестирование.

Преимущества

Если всё работает, то таким спобом можно сэкономить много времени.

Недостатки

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

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

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

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

Инкрементальный подход

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

Затем добавляются и проверяются на правильность функционирования другие соответствующие модули.

Процесс продолжается до тех пор, пока все модули не будут соединены и успешно протестированы.

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

  • Снизу Вверх

  • Сверху Вниз

Заглушки и драйверы

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

Заглушка: вызывается тестируемым модулем.

Драйвер: вызывает модуль для тестирования.

Как делать заглушки?

Конечно, всё зависит от того, для чего Вы делаете заглушку. Кругому люку нужна круглая крышка.

Качественная крышка для люка www.andreyolegovich.ru


Изображение с сайта bestluki.ru

Подход Снизу Вверх

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

Требуется помощь драйверов для тестирования

Преимущества

Локализовать ошибки намного проще. Сразу видно какой из-за какого модуля
проваливается тест.

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

Недостатки

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

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

Ранний прототип невозможен поэтому если MVP Вам нужен быстро и наличие каких-то ошибок
некритично, то с Bottom-Up тестированием можно немного подождать и провести
хотя бы несколько тестов сразу на более высоком уровне.

Метод Сверху Вниз

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

Пользуется заглушками для тестирования.

Преимущества

Локализация неисправностей проще.

Возможность получить ранний прототип.

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

Ошибки в реализации бизнес-логики будут видны в самом начале тестирования.

Недостатки

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

Модули на более низком уровне тестируются неадекватно. Какие-то ошибки
особенно в маловероятных сценариях и пограничных случаях (Corner Cases)
могут быть до определённого момента не видны.

Смешанный подход — сэндвич

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

Модули нижнего уровня тестируются по схеме снизу вверх.

Преимущества

Даёт уверенность в модулях нижнего уровня плюс сразу виден общий уровень готовности софта к релизу.

Хорош для больших проектов в которых нужно ставить реалистичные сроки выполнения.

Недостатки

Нужно дополнительно время на координацию и вовлечение потенциально большего числа участинков тестировани.

Как организовать интеграционное тестирование

  1. Подготовка Плана интеграционных тестов
  2. Разработайте Тестовые сценарии, Кейсы и Сценарии.
  3. Выполнение тестовых случаев с последующим сообщением о дефектах.
  4. Отслеживание и повторное тестирование дефектов.
  5. Шаги 3 и 4 повторяются до тех пор, пока интеграция не завершится успешно.

Краткое описание интеграционных тест планов

Включает в себя следующие атрибуты:

  • Методы/подходы к тестированию (как обсуждалось выше).
  • Области применения и вне областей применения Элементов интеграционного тестирования.
  • Роли и обязанности.
  • Предпосылки для интеграционного тестирования.
  • Среда тестирования.
  • Планы снижения рисков.

Входные и выходные критерии интеграционного тестирования

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

Входные критерии :

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

Выходные критерии:

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

Руководства и советы

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

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

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

  • Распил приложения на более мелкие продукты со своими релизными циклами.
  • Покрытие продукта тестами в соответствии с тестовой пирамидой.

Последний пункт и стал темой моей статьи.

image

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

Как мы знаем, в пирамиде тестирования три уровня: unit-тесты, интеграционные тесты и e2e-тесты. Думаю, с юнитами знакомы многие, как и с e2e, поэтому чуть подробнее остановлюсь на интеграционных тестах.

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

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

  1. Очень подробная документация.
  2. Легкий debugging тестов (у Cypress для этого сделан специальный GUI с time-travel по шагам в тесте).

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

Начало пути

В самом начале для организации кода использовался Angular Workspace с одним приложением. После установки пакета Cypress в корне приложения появилась папка cypress с конфигурацией и тестами, на данном варианте мы остановились. При попытке подготовить в package.json скрипт, необходимый для запуска приложения и прогона поверх него тестов, мы столкнулись со следующими проблемами:

  1. В index.html были зашиты некоторые скрипты, которые не нужны в интеграционных тестах.
  2. Для запуска интеграционных тестов необходимо было убедиться, что сервер с приложением запущен.

Проблему с index.html решили через отдельную конфигурацию сборки — назовем ее сypress, — в которой указали кастомный index.html. Как это реализовать? Находим в angular.json конфигурацию вашего приложения, открываем секцию build, добавляем там отдельную конфигурацию для Cypress и не забываем указывать эту конфигурацию для serve-режима.

Пример конфигурации для build:

"build": {
 ...
 "configurations": {
   … // Другие конфигурации
   "cypress": {
     "aot": true,
     "index": "projects/main-app-integrations/src/fixtures/index.html",
     "fileReplacements": [
       {
         "replace": "projects/main-app/src/environments/environment.ts",
         "with": "projects/main-app/src/environments/environment.prod.ts"
       }
     ]
   }
 }
}

Интеграция с serve:

"serve": {
 ...
 "configurations": {
   … // Другие конфигурации
   "cypress": {
     "browserTarget": "main-app:build:cypress"
   }
 }
}

Из основного: для cypress конфигурации мы указываем aot сборку и подменяем файлы с environment — это необходимо для создания prod-like сборки при тестировании.

Итак, с index.html разобрались, осталось поднять приложения, дождаться окончания сборки и прогнать поверх него тесты. Для этого используем библиотеку start-server-and-test и на ее основе напишем скрипты:

 "main-app:cy:run": "cypress run",
 "main-app:cy:open": "cypress open",
 "main-app:integrations": "start-server-and-test main-app:serve:cypress http://localhost:8808/app/user/ main-app:cy:run",
 "main-app:integrations:open": "start-server-and-test main-app:serve:cypress http://localhost:8808/app/user/ main-app:cy:open"

Как можно заметить, тут два типа скриптов: open и run. Режим open открывает GUI самого Cypress, где можно переключаться между тестами и использовать time-travel. Режим run — это просто прогон тестов и получение конечного результата этого прогона, отлично подходит для запуска в CI.

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

Монорепозиторий

У описанного подхода есть очень заметная проблема: если в репозитории два и более приложений, то подход с одной папкой нежизнеспособен. Так и произошло у нас. Но случилось это довольно интересным способом. В момент внедрения Cypress мы переезжали на NX, а этот красавец из коробки дает возможность работать с Cypress. Каков принцип работы в нем:

  1. У вас есть приложение, например main-app, рядом с ним создается приложение main-app-e2e.
  2. Переименуйте main-app-e2e в main-app-integrations — вы восхитительны.

Теперь вы можете прогонять интеграционные тесты одной командой — ng e2e main-app-integrations. NX автоматически поднимет main-app, дождется ответа и прогонит тесты.

К сожалению, остались в стороне те, кто сейчас использует Angular Workspace, но ничего страшного, у меня есть рецепт и для вас. Файловую структуру будем использовать как и у NX:

  1. Создаем рядом с вашим приложением папку main-app-integrations.
  2. Создаем в ней папку src и заносим в нее содержимое папки cypress.
  3. Не забываем перенести cypress.json (изначально он появится в корне) в папку main-app-integrations.
  4. Правим cypress.json, указывая пути до новых папок с тестами, плагинами и вспомогательными командами (параметры integrationFolder, pluginsFile и supportFile).
  5. Cypress умеет работать с тестами в любых папках, для указания папки используется параметр
    project, поэтому меняем команду с cypress run/open на cypress run/open -–project ./projects/main-app-integrations/src.

Решение под Angular Workspace максимально похоже на решение для NX, за исключением того, что папку создаем руками и она не является одним из проектов в вашем монорепозитории. В качестве альтернативы можно напрямую использовать билдер от NX для Cypress (пример репозитория на NX с Cypress, там можно подсмотреть итоговое использование nx-cypress билдера — внимание на angular.json и проект
cart-e2e и products-e2e).

Visual Regressing

После первых пяти тестов мы задумались о скриншот-тестировании, ведь, по сути, все возможности для этого есть. Заранее скажу, что слово «скриншот-тестирование» вызывает большую боль внутри команды, так как путь к получению стабильных тестов был не самым простым. Далее я опишу основные проблемы, с которыми мы столкнулись, и их решение.

В качестве решения была взята библиотека cypress-image-snapshot. Внедрение не отняло много времени, и вот спустя 20 минут мы получили первый скриншот нашего приложения размером 1000 × 600 px. Радости было много, ведь интеграция и использование были слишком простыми, а полученная польза могла быть огромной.

После генерации пяти эталонных скриншотов мы запустили проверку в CI, как итог — билд развалился. Оказалось, что скриншоты, созданные с помощью команд open и run, отличаются. Решение было довольно простым: снимать скриншоты только в CI-режиме, для этого убрали снятие скриншотов в local-режиме, например так:

Cypress.Commands.overwrite(
   'matchImageSnapshot',
   (originalFn, subject, fileName, options) => {
       if (Cypress.env('ci')) {
           return originalFn(subject, fileName, options);
       }

       return subject;
   },
);

В данном решении мы смотрим на параметр env в Cypress, установить его можно разными путями.

Шрифты

Локально тесты начали проходить при повторном запуске, пытаемся снова запустить их в CI. Результат можно увидеть ниже:

Довольно просто заметить разницу в шрифтах на diff-скриншоте. Эталонный скриншот был сгенерирован на macOS, а в CI на агентах установлен Linux.

Неправильное решение

Подобрали один из стандартных шрифтов (вроде это был Ubuntu Font), который давал минимальный попиксельный diff, и применили этот шрифт для текстовых блоков (сделано в
index.html, который предназначался только для cypress-тестов). Затем повысили общий diff до 0,05% и попиксельный diff до 20%. С такими параметрами мы прожили неделю — до первого случая, когда потребовалось изменить текст в компоненте. В итоге билд остался зеленым, хотя скриншот мы не обновили. Текущее решение оказалось бесполезным.

Правильное решение

Исходная проблема была в разных окружениях, решение в принципе напрашивается само собой — Docker. Для Cypress уже есть готовые docker-образы. Там есть разные вариации образов, нас интересует included, так как Cypress в нем уже включен в образ и не будет каждый раз заново происходить скачивание и распаковка Cypress binary (GUI Cypress запускается через бинарный файл, и скачивание вместе с распаковкой занимает больше времени, чем скачивание docker-образа).
На основе included docker-образа делаем свой docker-контейнер, для этого у себя мы сделали файл integration-tests.Dockerfile с подобным содержимым:

FROM cypress:included:4.3.0
COPY package.json /app/
COPY package-lock.json app/
WORKDIR /app
RUN npm ci
COPY / /app/
ENTRYPOINT []

Хочется отметить обнуление ENTRYPOINT, это связано с тем, что он задан по умолчанию в образе cypress/included и указывает на команду cypress run, что не дает нам использовать другие команды. Также разбиваем наш dockerfile на слои, чтобы при каждом перезапуске тестов не выполнять повторно npm ci.

Добавляем .dockerignore файл (если его нет) в корень репозитория и в нем обязательно указываем node-modules/ и */node-modules/.

Для запуска в Docker наших тестов напишем bash-скрипт integration-tests.sh со следующим содержимым:

docker build -t integrations -f integration-tests.Dockerfile .
docker run --rm -v $PWD/projects/main-app-integrations/src:/app/projects/main-app-integrations/src integrations:latest npm run main-app:integrations

Краткое описание: билдим наш docker-контейнер integration-tests.Dockerfile и указываем volume на папку с тестами, чтобы была возможность получить созданные скриншоты из Docker.

Снова шрифты

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

Думаю, самые внимательные заметили, что не хватает заголовка в попапе. Причина очень простая — не успел загрузиться шрифт, так как не был подключен через assets, а находился на CDN.

Неправильное решение

Скачиваем шрифты с CDN, закидываем их в assets для cypress-конфигурации и в нашем кастомном
index.html для интеграционных тестов подключаем их. С таким решением мы прожили приличное время, пока у нас не изменился корпоративный шрифт. Второй раз проворачивать такую же историю желания не было.

Правильное решение

Было решено начать предзагружать все необходимые шрифты для теста в
index.html для cypress-конфигурации, выглядело это примерно так:

<link	
      rel="preload"
      href="...."	
      as="font"	
      type="font/woff2"	
      crossorigin="anonymous"
/>

Число падений тестов из-за не успевших загрузиться шрифтов снизилось до минимума, но не до нуля: все равно иногда шрифт не успевал загрузиться. На помощь пришло решение из KitchenSink самого Cypress — waitForResource.
В нашем случае, так как уже была подключена предзагрузка шрифтов, мы просто переопределили команду visit в Cypress, в итоге она не просто навигируется на страничку, но и ждет загрузку указанных шрифтов. Также хотелось бы дополнить, что waitForResource решает проблему не только шрифтов, но и любой загружаемой статики, например картинок (из-за них у нас также ломались скриншоты, и waitForResource отлично помогло). После применения данного решения проблем со шрифтами и любой загружаемой статикой не было.

Анимации

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

Первое решение

Самое простое, что нам пришло в голову на начальном этапе: перед созданием скриншота останавливать браузер на определенное время, чтобы анимации успели завершиться. Шли по цепочке 100ms, 200ms, 500ms и в итоге 1000ms. Оглядываясь назад, я понимаю, что это решение изначально было ужасным, но хотелось именно предостеречь вас от такого же решения. Почему ужасным? Время анимаций разное, агенты в CI тоже могут подтупливать иногда, из-за чего любое время ожидания стабилизации страницы от раза к разу было разным.

Второе решение

Даже с ожиданием в 1 секунду страница не всегда успевала стать стабильной. После небольшого ресерча нашли инструмент у Angular — Testability. Принцип основан на отслеживании стабильности ZoneJS:

Cypress.Commands.add('waitStableState', () => {
   return cy.window().then(window => {
       const [testability]: [Testability] = window.getAllAngularTestabilities();

       return new Cypress.Promise(resolve => {
           testability.whenStable(() => {
               resolve();
           }, 3000);
       });
   });
});

Таким образом при создании скриншотов у нас вызывались две команды: cy.wait(1000) и cy.waitStableState().

С тех пор не было ни одного рандомно упавшего скриншота, но давайте вместе посчитаем, сколько времени тратилось на простаивание браузера. Предположим, у вас в тесте делается 5 скриншотов, для каждого есть стабильное время ожидания в 1 секунду и какое-то рандомное время, предположим 1,5 секунды в среднем (я не замерял среднее значение в реальности, поэтому взял из головы по собственным ощущениям). В итоге для создания скриншотов в тесте мы тратим дополнительно 12,5 секунды. Представим, что вы уже написали 20 тестовых сценариев, где в каждом тесте не менее 5 скриншотов. Получаем, что переплата за стабильность ~4 минуты при имеющихся 20 тестах. 

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

Текущее решение

Начнем с Angular-анимаций. Наш любимый фреймворк во время анимации на элементе DOM навешивает класс ng-animating. Это и стало ключом нашего решения, ведь теперь надо убедиться, что на элементе сейчас нет класса анимации. В итоге это вылилось в такую функцию:

export function waitAnimation(element: Chainable<JQuery>): Chainable<JQuery> {
   return element.should('be.visible').should('not.have.class', 'ng-animating');
}

Кажется, ничего сложного, но именно это легло в основу наших решений. На что хочется обратить внимание в таком подходе: делая скриншот, вы должны понимать, анимация какого элемента может сделать ваш скриншот нестабильным, и перед созданием скриншота добавить assertion, который проверит, что элемент не анимируется. Но анимации также могут быть и на CSS. Как говорит сам Cypress, любые assertion на элементе ждут окончания анимации на нем — подробнее тут и тут. То есть суть подхода в следующем: у нас есть анимируемый элемент, добавляем на него assertion — should(‘be.visible’)/should(‘not.be.visible’) — и Cypress сам дождется окончания анимации на элементе (возможно, кстати, решение с ng-animating не нужно и достаточно только проверок Cypress, но мы пока что используем утилиту — waitAnimation).

Как сказано в самой документации, Cypress проверяет изменение позиции элемента на странице, но не все анимации про изменение позиции, есть также fadeIn/fadeOut-анимации. В этих случаях принцип решения тот же: проверяем, что элемент виден / не виден на странице. 

При переезде с решения cy.wait(1000) + cy.waitStableState() на waitAnimation и Cypress Assertion пришлось потратить ~2 часа времени на стабилизацию старых скриншотов, но как итог мы получили +20—30 секунд вместо +4 минут на время выполнения тестов. На данный момент мы тщательно подходим к ревью скриншотов: проверяем, что они выполнены не во время анимации элементов DOM и добавлены проверки в тесте на ожидание анимации. Например, мы часто добавляем отображение «скелетонов» на странице, пока данные не загрузились. Соответственно, на ревью сразу же прилетает требование, что при создании скриншотов в DOM не должен присутствовать скелетон, так как на нем находится анимация плавного исчезновения. 

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

Размер скриншотов

Возможно, вы заметили интересную особенность: разрешение скриншотов по умолчанию — 1000 × 600 px. К сожалению, есть проблема с размером окна браузера при запуске в Docker: даже если вы через Cypress поменяете размер viewport’а, это не поможет. Мы нашли решение для браузера Chrome (для Electron не удалось быстро найти работающее решение, а предложенное в данном issue у нас не завелось). Для начала надо сменить браузер для запуска тестов на Chrome:

  1. Не для NX делаем с помощью аргумента —browser chrome при запуске команды cypress open/run и для run-команды указываем параметр —headless.
  2. Для NX в конфигурации проекта в angular.json с тестами указываем параметр browser: chrome, и для конфигурации, которая будет запускаться в CI, указываем headless: true.

Теперь делаем правки в plugins и получаем скриншоты размером 1440 × 900 px:

module.exports = (on, config) => {
   on('before:browser:launch', (browser, launchOptions) => {
       if (browser.name === 'chrome' && browser.isHeadless) {
           launchOptions.args.push('--disable-dev-shm-usage');
           launchOptions.args.push('--window-size=1440,1200');

           return launchOptions;
       }

       return launchOptions;
   });
};

Даты

Тут все просто: если где-то отображается дата, связанная с текущей, снятый скриншот сегодня завтра упадет. Фиксим просто:

cy.clock(new Date(2025, 11, 22, 0).getTime(), ['Date']);

Теперь таймеры. Мы не стали заморачиваться и используем опцию blackout при создании скриншотов, например:

cy.matchImageSnapshot('salary_signing-several-payments', {
   blackout: ['.timer'],
});

Flaky-тесты

Используя приведенные выше рекомендации, вы сможете добиться максимальной стабильности тестов, но не 100%, ведь на тесты влияет не только ваш код, но и окружение, в котором они запускаются.

В итоге какой-то процент тестов будет изредка падать, например из-за проседаний производительности агентов в CI. В первую очередь стабилизируем тест с нашей стороны: добавляем необходимые assertion перед снятием скриншотов, но на период починки таких тестов можно использовать retry упавших тестов с помощью cypress-plugin-retries.

Прокачиваем CI

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

  1. Команда npm ci.
  2. Поднятие приложения в aot-режиме.
  3. Запуск интеграционных тестов.

Взглянем на первый и второй пункт и поймем, что похожие шаги выполняются в другом вашем билде в CI — билд со сборкой приложения.
Основное отличие — это запуск не ng serve, а ng build. Таким образом, если сможем получить уже собранное приложение в билде с интеграционные тестами и поднять сервер с ним, то мы сможем сократить время выполнения билда с тестами.

Почему нам это потребовалось? Просто приложение у нас большое и выполнение
npm ci + npm start в aot-режиме на агенте в CI занимало ~15 минут, что в принципе требовало больших усилий от агента, и еще поверх этого запускались интеграционные тесты. Предположим, у вас уже написано 20+ тестов и на 19-м тесте у вас падает браузер, в котором прогоняются тесты, из-за большой нагрузки на агент. Как вы понимаете, перезапуск билда — это снова ожидание установки зависимостей и запуска приложения.

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

Сервер со статикой

Нам нужна замена ng serve по поднятию сервера с нашим приложением. Вариантов много, начну с нашего первого — angular-http-server. В его настройке нет ничего сложного: устанавливаем зависимость, указываем, в какой папке лежит наша статика, указываем, на каком порту поднять приложение, и радуемся.

Этого решения нам хватило на целых 20 минут, а потом мы поняли, что какие-то запросы хотим проксировать на тестовый контур. Подключить проксирование для angular-http-server не получилось. Конечным решением стало поднятие сервера на Express. Для решения задачи использовался сам express и express-http-proxy. Раздавать нашу статику будем с помощью
express.static, в итоге получается скрипт, похожий на этот:

const express = require('express');
const appStaticPathFolder = './dist';
const appBaseHref = './my/app';
const port = 4200;
const app = express();

app.use((req, res, next) => {
   const accept = req
       .accepts()
       .join()
       .replace('*/*', '');

   if (accept.includes('text/html')) {
       req.url = baseHref;
   }

   next();
});
app.use(appBaseHref, express.static(appStaticPathFolder));
app.listen(port);

Интересным моментом здесь является то, что перед прослушиванием роута по baseHref приложения мы также обрабатываем все запросы и ищем запрос на index.html. Это сделано для случаев, когда в тестах осуществляется переход на страницу приложения, путь которой отличается от baseHref. Если не сделать данный трюк, то при переходе на любую страницу вашего приложения, кроме главной, будет прилетать 404 ошибка. Теперь добавим щепотку проксирования:

const proxy = require('express-http-proxy');

app.use(
   '/common',
   proxy('https://qa-stand.ru', {
       proxyReqPathResolver: req => '/common' + req.url,
   }),
);

Чуть подробнее рассмотрим происходящее. Есть константы:

  1. appStaticForlderPath — папка, где находится статика вашего приложения. 
  2. appBaseHref — возможно, у вашего приложения есть baseHref, если нет — можно указать ‘/’.

Проксируем же мы все запросы, начинающиеся на /common, причем при проксировании сохраняем тот же путь, который был у запроса, с помощью настройки proxyReqPathResolver. Если ее не использовать, то все запросы будут просто идти наhttps://qa-stand.ru.

Кастомизация index.html

Нам нужно было решить проблему с кастомным index.html, который мы использовали при ng serve приложения в режиме Cypress. Напишем простой скрипт на node.js. Исходными параметрами у нас был index.modern.html, требовалось превратить его в index.html и удалить оттуда ненужные скрипты:

const fs = require('fs');
const appStaticPathFolder = './dist';

fs.copyFileSync(appStaticPathFolder + '/index.modern.html', appStaticPathFolder + '/index.html');

fs.readFile(appStaticPathFolder + '/index.html', 'utf-8', (err, data) => {
   const newValue = data
       .replace(
           '<script type="text/javascript" src="/auth.js"></script>',
           '',
       )
       .replace(
           '<script type="text/javascript" src="/analytics.js"></script>',
           '',
       );

   fs.writeFileSync(appStaticPathFolder + '/index.html', newValue, 'utf-8');
});

Скрипты

Очень не хотелось для запуска тестов в CI делать еще раз npm ci всех зависимостей (ведь это уже делалось в таске с билдом приложения), поэтому появилась идея создать отдельную папку под все эти скрипты со своим package.json. Назовем папку, например, integration-tests-scripts и закинем туда три файла: server.js, create-index.js, package.json. Первые два файла были описаны выше, разберем теперь содержимое package.json:

{
 "name": "cypress-tests",
 "version": "0.0.0",
 "private": true,
 "scripts": {
   "create-index": "node ./create-index.js",
   "main-app:serve": "node ./server.js",
   "main-app:cy:run": "cypress run --project ./projects/main-app-integrations ",
   "main-app:integrations": "npm run create-index && start-server-and-test main-app:serve http://localhost:4200/my/app/ main-app:cy:run"
 },
 "devDependencies": {
   "@cypress/webpack-preprocessor": "4.1.0",
   "@types/express": "4.17.2",
   "@types/mocha": "5.2.7",
   "@types/node": "8.9.5",
   "cypress": "4.1.0",
   "cypress-image-snapshot": "3.1.1",
   "express": "4.17.1",
   "express-http-proxy": "^1.6.0",
   "start-server-and-test": "1.10.8",
   "ts-loader": "6.2.1",
   "typescript": "3.8.3",
   "webpack": "4.41.6"
 }
}

В package.json присутствуют только зависимости, необходимые для прогона интеграционных тестов (с поддержкой typescript и скриншот-тестирования) и скрипты по запуску сервера, созданию index.html и известный из главы по запуску интеграционных тестов в Angular Workspace start-server-and-test.

Запуск

Оборачиваем выполнение интеграционных тестов в новый Dockerfile — integration-tests-ci.Dockerfile:

FROM cypress/included:4.3.0
COPY integration-tests-scripts /app/
WORKDIR /app
RUN npm ci
COPY projects/main-app-integrations /app/projects/main-app-integrations
COPY dist /app/dist
COPY tsconfig.json /app/
ENTRYPOINT []

Суть проста: копируем и разворачиваем папку integration-tests-scripts в корень приложения и копируем все, что необходимо для запуска тестов (у вас этот набор может отличаться). Основные отличия от предыдущего файла в том, что мы не копируем все приложение внутрь docker-контейнера, просто минимальная оптимизация времени выполнения тестов в CI.

Создаем файл integration-tests-ci.sh со следующим содержимым:

docker build -t integrations -f integration-tests-ci.Dockerfile .
docker run --rm -v $PWD/projects/main-app-integrations/src:/app/projects/main-app-integrations/src integrations:latest npm run main-app:integrations

Во время запуска команды с тестами корневым станет package.json из папки integration-tests-scripts и в нем запустится команда main-app:integrations. Соответственно, так как эта папка развернется в корень, то и пути до папки со статикой вашего приложения нужно указывать с мыслью, что запускаться все будет из корня, а не из папки integration-tests-scripts.

Хочу также сделать небольшую ремарку: конечный bash-скрипт для запуска интеграционных тестов по мере его эволюции я называл по-разному. Так делать не нужно, это было сделано только для удобства чтения этой статьи. У вас должен всегда оставаться один файл, например integration-tests.sh, который вы уже развиваете. Если у вас несколько приложений в репозитории и их способы подготовки различаются, можно разруливать либо переменными в bash, либо разными файлами под каждое приложение — зависит от ваших потребностей.

Итог

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

  1. Ставим зависимость для Cypress.
  2. Подготавливаем папку с тестами:
    1. Angular Single Application — оставляем все в папке cypress.
    2. Angular Workspace — создаем папку имя приложения-integrations рядом с приложением, на которое будут гоняться тесты, и переносим в нее все из папки cypress.
    3. NX — переименовываем проект из имя приложения-e2e в имя-приложения-integrations.

  3. Кастомная cypress-сборка для поднятия приложения — делаем в build-разделе конфигурацию Cypress, указываем там aot, подмену на свой отдельный index.html, подмену файла environment на prod-файл и указываем в serve-разделе сборку Cypress (данный пункт необходим, если вам нужны какие-то отличия от prod-сборки, в противном случае можно пропустить).
  4. Скрипты по запуску и прогону тестов:
    1. Angular Single Application — готовим скрипт с serve-ом cypress-сборки и запуску тестов, комбинируем все это с помощью start-server-and-test.
    2. Angular Workspace — аналогично с Angular Single Application, только указываем путь до тестов при запуске cypress run/open.
    3. NX — запускаем тесты с помощью команд ng e2e.

  5. Подмешиваем скриншот-тестирование:
    1. Добавляем зависимость cypress-image-snapshot.
    2. Переопределяем стандартную команду по сравнению скриншотов для запуска ее только в CI.
    3. В тестах не делаем скриншотов на рандоме. Если скриншоту предшествует анимация, обязательно ждем ее — например, добавляем Cypress Assertion на анимируемый элемент.
    4. Дату мокируем через cy.clock либо используем опцию blackout при снятии скриншота.
    5. Любую подгружаемую статику в runtime ожидаем через кастомную команду cy.waitForResource (картинки, шрифты и т. д.).

  6. Оборачиваем все это в Docker:
    1. Готовим Dockerfile.
    2. Создаем bash-файл.

 Прогоняем тесты поверх собранного приложения:

  1. В CI учимся прокидывать между билдами артефакты собранного приложения (остается на вас).
  2. Готовим папку integration-tests-scripts:
    1. Скрипт по поднятию сервера вашего приложения.
    2. Скрипт по изменению вашего index.html (если вас устраивает изначальный index.html — можно скипать).
    3. Добавляем в папку package.json с необходимыми скриптами и зависимостями.
    4. Готовим новый Dockerfile.
    5. Создаем bash-файл.

Полезные ссылки

  1. Angular Workspace + Cypress + CI — в данном примере я создал базу для Angular Workspace приложения с накрученным CI на основе скриптов, описанных в статье (без поддержки typescript).
  2. Cypress — обратите внимание на раздел trade-offs.
  3. Start-server-and-test — запуск сервера, ожидание ответа и запуск тестов.
  4. Cypress-image-snapshot — библиотека для скриншот-тестирования.
  5. Cypress recipes — готовые наработки по Cypress, чтобы не изобретать свои велосипеды.
  6. Flaky Tests — статья про нестабильные тесты, в конце есть ссылка на статью от Google.
  7. Github Action — Cypress дает возможность использовать предустановленные конфиги для GitHub Action, в README куча примеров, из интересного — встроенная поддержка wait-on. Также есть пример с docker.

Интеграционное тестирование на примере реального проекта

Anna Ipatova, 14/06/2019

Интеграционное тестирование редко попадает в заголовки статей из раздела «Информационные технологии». Масштаб ошибок интеграции не сравнится по степени критичности и по размеру понесенных убытков с ошибками безопасности.

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

Снимок экрана 2019-06-14 в 15.20.17

Однако важность интеграционного тестирования недооценивать нельзя. Грамотное интеграционное тестирование – один из основных шагов на пути к выпуску надежного продукта.

Что же это за тестирование и как оно проводится?

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

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

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

Интеграционное тестирование: обзор проекта

Заказчик

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

Задача проекта

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

Для реализации функции подписки и ее управления заказчик использовал следующие программные решения:

  • CMS-решение eZ Publish. Функция – предоставлять любые данные о подписках с применением различных фильтров: типа подписки, ее продолжительности, применяемых скидок, бонусов и так далее.
  • Вебсайт, через который пользователь взаимодействует с системой.
  • CRM Salesforce. Функция – хранение данных о пользователях и приобретенных ими подписках. Дополнительная надстройка позволяет команде заказчика управлять приобретенными подписками, а также создавать новые и проверять старые подписки.
  • SaaS-решение Zuora для выставления счетов и обработки платежей.
  • Обмен данными между системами осуществляется с помощью сервисной шины Mule ESB.
  • База данных как инструмент Business Intelligence.
  • Salesforce Marketing Cloud – инструмент рассылки корреспонденции и коммуникации с пользователями.
  • Drupal ранее использовался вместо eZ Publish. На данный момент все еще остается системой, хранящей данные о зарегистрированных пользователях, и инструментом для публикации статей, видео- и аудио-контента.

Процесс оформления подписки был построен следующим образом:

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

Цель клиента

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

Задача тестирования

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

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

Стратегия проведения интеграционного тестирования A1QA

  1. Определены ключевые бизнес-процессы, которые должна выполнять система: создание, отмена, приостановка и возобновление подписки, изменение платежной информации для подписки и т.д.
  2. Разработана тестовая документация с учетом всех возможных вариаций. Вариации – различные альтернативные выполнения операций (например, отмена подписки может произойти по желанию заказчика, а может быть произведена автоматически, если платежные данные были отклонены банком), а также различные параметры (например, тип продукта). В документации требовалось учесть проверку того, например, что создание подписки пройдет успешно для всех продуктов в рамках каждого бизнес-процесса.
  3. Проведение тестирования, которое заключалось в пошаговом прохождении каждого бизнес-процесса со стартового компонента (где он был инициирован) через все промежуточные и до финального (или финальных) с проверкой того, что все данные передаются правильно, а ожидаемые события на самом деле случаются.

Большинство процессов включало в себя передачу данных из одного модуля (чаще всего из Salesforce) во все остальные. Если начальной точкой был не SF, то информация из модуля поступала в MuleESB, а потом в SF, а оттуда во все остальные (опять же, через MuleESB).

На проведение тестирования интеграции было потрачено порядка 40% всех трудозатрат QA-команды.

Трудности

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

Поясним. Изначально требования выглядели как набор пользовательских историй (User Story) в JIRA и содержали только заголовки без какого-либо пояснения. Создавали их чаще всего разработчики.

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

Автоматизация интеграционного тестирования

Автоматизация тестирования – непростой вопрос, который требует внимательного сопоставления всех «за» и «против».

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

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

Результаты

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

Подводя итоги

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

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

Источник: a1qa


Хотите обсудить интеграционное тестирование?

Запросите сейчас!

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

Интеграционное тестирование является одним из этапов тестирования. Этапы тестирования выглядят таким образом:

  1. Модульное тестирование.

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

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

  4. Приемочное тестирование.

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

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

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

Пример интеграционного тестирования. Допустим, у интернетмагазина есть 4 отдельных модуля:

  • страница товара;

  • страница корзины;

  • страница оформления подтверждения заказа;

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

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

  • если находиться на странице товара, он легко добавится в «корзину»;

  • если находиться на странице «корзины», можно оформить и подтвердить заказ этого товара;

  • если оформить и подтвердить заказ, он легко оплатится тут же на сайте.

Эти связи показывает и проверяет интеграционное тестирование.

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

Интеграционное тестирование многообразно в собственном исполнении. Подход и стратегия будут зависеть от тестируемого проекта и его сложности. Однако чаще всего применяются две стратегии проверок:

  1. Стратегия «Большого взрыва». При такой стратегии все программные модули объединяют вместе, а потом разом их тестируют. Эта стратегия удобна в небольших проектах, где нет большого количества модулей. Чем больше модулей в программе, тем сильнее возрастает риск пропуска ошибок.

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

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

Интеграционное тестирование работает по следующему алгоритму:

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

  2. Нужно разработать тестовые сценарии.

  3. Выполнить тестовые сценарии и зафиксировать ошибки.

  4. Отследить исправление ошибок и провести повторное тестирование.

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

Чтобы правильно задействовать алгоритм интеграционного тестирования, нужно:

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

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

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

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

Заключение

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

API-, интеграционное и E2E-тестирование

latest update of the page: 31-01-2023, 17:57 UTC

Определимся с предметом

Своими словами.

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

Интеграционное тестирование (Integration testing) = проверка корректности взаимодействия чего-то с чем-то, в зависимости от контекста:

  • либо проверка связи между модулями (компонентами) кода, а также взаимодействия с различными частями системы (операционной системой, оборудованием либо связи между различными системами)
  • либо проверка интеграции между сервисами/системами. В настоящей «статье» мы будем говорить об этом.

Сквозное тестирование (End-To-End, E2E или Chain testing) = проверка некоей ценной для Клиента функциональности (feature) по всей «цепочке» её исполнения:

от инициирующих действий Клиента (либо клиентского приложения) в CLI / TUI / GUI / API / filesystem / DB —>

—> через взаимосвязанные системы/сервисы нашего Продукта —>

—> до предоставления Клиенту (его приложению) результата в обозначенном требованиями формате и месте.

Чуть подробнее здесь

Вступление

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

Рассматривается тот участок процесса поставки, когда тестируемые системы/сервисы уже прошли своё «личное» функциональное тестирование, а теперь нам нужно убедиться, что они слаженно работают вместе как единый продукт.

Речь пойдёт о том как подступиться к интеграционному (системному) тестированию совокупности взаимодействующих систем/сервисов,

  • которое предполагается автоматизировать
  • при этом не используя заглушки (stubs) и моки (mocks), т.е. в максимально «живом и натуральном» окружении.

Какая интеграция вообще бывает?

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

  • прямыми вызовами API «точка-точка» по шаблону request-reply (запрос-ответ) или one-way (отправка в одну сторону).

    Обычно реализуется посредством REST API или RPC-взаимодействия.
  • обменом через слой среднего уровня, например через системы управления очередями (message brokers) типа RabbitMQ и Apache Kafka
    или при посредстве КСШ (ESB) (которая, по-сути тоже является собой совокупность тех же message brokers)
  • обменом файлами, складываемыми/читаемыми в условленном «месте».

    Таким местом может быть как некий общий локальный storage, так и удалённый, (от)куда файлы читаются/передаются по протколам FTP, SSH et cetera.
  • на уровне баз данных:

    1. одна общая БД для нескольких систем (привет монолитам и всяческому легаси)
    2. баз несколько, но связанных процессами ETL/ELT

Обследование

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

  1. Сценарии использования (use cases) и БТ (бизнес-требования) по ним
  2. Архитектура (общая, компонентная, интеграционная)
  3. Диаграммы последовательности (sequence diagram) интеграционных взаимодействий
  4. СТ (системные требования) к API, описание/контракты API
  5. Объектная модель (object model)
  6. Ролевая модель (role model)
  7. Диаграмма развёртывания (DD, deployment diagram), руководства/инструкции по установке и администрированию

Изучение их позволит обрести понимание по каждому сервису/системе:

  • список ВОПРОСОВ к команде сервиса/системы
  • для чего вообще нужен, какие его самые важные фичи
  • входящие интеграционные потоки
  • исходящие интеграционные потоки
  • как производит аутентификацию запросов
  • как происходит авторизация
  • какие протоколы где использует (требуются ли сертификаты и ключи)
  • какими сущностями оперирует
  • какие действия в GUI и/или запросы к API инициируют исходящие запросы в другие сервисы/системы
  • для каких ролей эти действия должны быть доступны
  • при каких способах развёртывания и каких именно настройках интеграция будет идти тем или иным образом

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

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

Абстрактный пример обследуемой архитектуры

Предположим, мы имеем некий Продукт, состоящий из Системы А, Системы Б, Системы В. Продукт предоставляет Клиенту некое публичное API, в которое клиентское приложение постоянно пишет некие данные. Продукт также предоставляет Клиенту web-UI. Основным юзкейсом нашего Продукта является приём данных от Клиента, обработка их каким-то образом и предоставление в некотором человекочитаемом виде в web-UI. Также наш Продукт для каких-то внутренних потребностей собирает какие-то данные со своих Систем.

Для упрощения опустим интеграцию с системами аутентификации и авторизации.

Абстрактный пример обследуемой архитектуры

скачать схему в формате diagrams.net (бывший draw.io)

В этом случае с точки зрения API / integration / E2E тестирования нас будут интересовать следующие элементы архитектуры:

  • API 4 шт.
  • Message broker 1 шт.
  • Интеграционные потоки 4 шт.

Какие же тестовые сценарии?

Интеграционные тестовые сценарии можно поделить, например, на следующие группы (по возрастанию сложности):

  1. Auth.

    Тестовые сценарии на проверку возможности аутентификации и/или авторизации запросов к API. mTLS, basic-auth, JWT,… вот это вот всё. Можно совмещать с базовыми проверками ролевой модели, если это возможно сделать запросами к (web-UI) API.
  2. API.

    • В этой группе проверяем такие методы API, которые НЕ инициируют подзапросов в другие Системы, в противном случае это уже будут integration-тесты.
    • Тесты, имеющие целью проверки того, что на запросы к методам API, составленные на основе требований и/или контракта этого API, приходит ответ, соответствующим требованиям/контракту.
    • Тесты, имеющие целью проверки того, что обращения к API позволяют успешно производить CRUD операции наl сущностями, которыми оперирует система-владелец API.

      Успешность создания/изменения/удаления сущностей можно проверять как и цепочкой обращений к методам API (при наличии соответствующих), так и запросами к БД, если есть такая возможность.

    Чуть подробнее

    Может включать в себя тестирование:

    1. WADL/WSDL SOAP API (XML)
    2. HTTP(S) REST API, сиречь HTTP-request вида:

      • POST host:9090/entity с body, содержащим text/json/xml
      • GET host:9090/entity/1
      • PUT host:9090/entity/1 с body, содержащим text/json/xml
    3. HTTP(S) non-REST API, сиречь HTTP-request вида:

      • POST host:9090/CreateEntity с body, содержащим text/json/xml
      • GET host:9090/GetEntity?id=1
      • POST host:9090/UpdateEntity с body, содержащим text/json/xml
    4. RPC-взаимодействия с сервером, например, в виде java-call клиентской библиотеки, см. gRPC

  3. Integration.

    Тесты корректности интеграции двух систем. «Дёргаем» (по API / web-UI) первую систему, проверяем, что она корректно «пообщалась» с другой системой.

    Порой, такой тест м.б. частным случаем E2E, если в реализации фичи (бизнес-процессе) задействовано всего 2 системы.
  4. E2E.

    Проверка такого бизнес-процесса (фичи, если угодно) ИТ-Продукта, в процессе выполнения которой задействовано 3 и более системы: система, в которой было совершено инициирующее воздействие (обращение к API, манипуляция в UI), «промежуточные» системы, система, в которой «даются» даные для итогового результата.

    Рекомендуется соотношение одна фича = один E2E-тест (базовый положительный случай), потому что E2E — самые долгие и дорогие по автоматизации и поддержке тесты.

    Чуть подробнее

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

    • E2E-тестов не должно быть много
    • Время прохода E2E-тестов должно исчисляться минутами, а не часами
    • E2E-тесты должны кореллировать с CJM
    • E2E-тесты следует делать независимыми от предподготовленных данных, отсутствие или плохое качество которых часто является причиной ошибок. Если есть сервисы (воззможно, среди тестируемвых), которые предоставляют API по созданию объектов сущностей, то следует использовать его. Если такого нет, то нужные данные следует импортировать на уровне БД.

Какие же тесты по группам будут для нашего примера

API

Системнное тестирование — API тесты

скачать схему в формате diagrams.net (бывший draw.io)

Integration

Системнное тестирование — integration-тесты

скачать схему в формате diagrams.net (бывший draw.io)

E2E

Системнное тестирование — E2E-тесты

скачать схему в формате diagrams.net (бывший draw.io)

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

экраны.jpg

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

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

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

Почему мы проводим интеграционные тесты?

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

  1. Юнит тест (также называется модульным тестом).
  2. Интеграционный тест.
  3. Системный тест.
  4. вступительный тест.

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

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

Стратегии интеграционного тестирования

Программная инженерия определяет различные стратегии для выполнения интеграционного теста, а именно:

  •  Большой взрыв.
  •  Инкрементальный подход. Далее он подразделяется на следующие:
    • Нисходящий подход.
    • Восходящий подход.
    • Пошаговый подход — сочетание сверху вниз и снизу вверх.

Подход Большого взрыва:

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

Преимущества:

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

минусы:

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

Инкрементальный подход

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

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

  • Снизу вверх.
  • Сверху вниз.

Что такое заглушка и что такое драйвер?

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

Заглушка: вызывается из других модулей модулем, который мы тестируем.

Драйвер: работайте наоборот и, таким образом, вызывайте модуль, который мы тестируем.

Восходящий интеграционный тест

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

Преимущества:

  • Обнаружение ошибок проще.
  • Мы не тратим время на ожидание кодирования всех модулей.

минусы:

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

Интеграционный тест сверху вниз

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

Использует заглушки для тестирования.

Преимущества:

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

минусы:

  • Нам нужно много окурков.
  • Мы недостаточно тестируем модули на более низком уровне.

Гибридный интеграционный тест

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

Как мы проводим интеграционные тесты?

Независимо от стратегии тестирования программного обеспечения процесс тестирования выглядит так:

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

Разработка плана интеграционных испытаний

План содержит следующие компоненты:

  • Выбранная стратегия и подход к тестированию (как обсуждено выше).
  • Интеграционные тесты области применения: какие смежные системы мы также тестируем?
  • Роли и обязанности тестеров.
  • Требования для интеграционных испытаний.
  • Тестовая среда с учетом места в DTAP.
  • Риски и меры.

Критерии для интеграционного тестирования

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

Начальные критерии:

  • Компоненты / модули прошли модульное тестирование.
  • Все дефекты с высоким приоритетом были устранены и закрыты.
  • Все модули готовы и могут быть интегрированы.
  • План интеграционного тестирования, тестовые сценарии и тестовые примеры также готовы и проверены.
  • Тестовая среда также готова к интеграционному тестированию.

Критерии остановки:

  • Интегрированное приложение было успешно протестировано.
  • Все проведенные тесты задокументированы.
  • Все дефекты с высоким приоритетом были решены и закрыты.
  • Была предоставлена ​​техническая документация, включая примечания к выпуску.

Лучшие практики и рекомендации по интеграционному тестированию

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

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

Группа LinkedIn

Обсудить с нами LinkedIn.

резюме

Что такое интеграционное тестирование и почему мы это делаем?

статья

Что такое интеграционное тестирование и почему мы это делаем?

Описание

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

Автор

Имя издателя

ITpedia

Издательство Логотип

ITpedia

Меня зовут Вячеслав Аксёнов, я имею большой опыт разработки веб сервисов на Java / Kotlin с использованием Spring Framework. В своей работе я регулярно встречаюсь с задачами, в которых требуется настроить и протестировать интеграцию веб сервиса с базой данных. Также среди людей, которых я обучаю, я вижу большое количество вопросов на эту тему. Так что я решил, что будет полезно сделать подробную статью, где на синтетическом приложении рассмотрю основные нюансы, которые нужно иметь в виду при написании тестов на свой код, взаимодействующий с базой данных.

Цель статьи: рассмотрение различных подходов к тестированию интеграции веб сервиса на Kotlin с базой данных с помощью библиотеки H2. Рассмотрение примеров тестирования синтетического приложения и разбор позитивных и негативных сценариев.

Забегая вперед, источники можно найти в моем GitHub репозитории: ​​pokemon-app

Предыстория.

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

Со стороны сервиса любая база данных выглядит как внешняя интеграция. И если сервис покрывается тестами, то значит, что эту интеграцию нужно тестировать. Способов для тестирования этой интеграций присутствует множество, ниже мы разберем самый часто встречавшийся в моей практике, а именно Spring Boot + H2.

Описание инфраструктуры.

Для примера используется базовый CRUD веб сервис на основе Spring Boot, написанный на Kotlin.

CRUD веб-сервисом называется сервис, который предоставляет функционал для создания (C), чтения (R), обновления (U), удаления (D) сущностей из базы данных через HTTP запросы. 

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

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

Описание функционала веб сервиса.

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

Для основного функционала сервиса используются следующие зависимости:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.module</groupId>
    <artifactId>jackson-module-kotlin</artifactId>
</dependency>
<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-reflect</artifactId>
</dependency>
<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-stdlib-jdk8</artifactId>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>

Структура базы данных.

Таблица для хранения информации о весе покемона в базе данных приложения выглядит следующим образом:

CREATE TABLE pokemon (
    id     integer primary key auto_increment,
    name   varchar(25),
    weight integer
);

id — поле идентификатора
name — поле для хранения информации об имени покемона (имеет ограничение в 25 символов)
weight — поле с информацией о весе покемона.

Требования к тестированию.

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

Это значит, что в данном случае идеально будет использовать интеграционные тесты, либо end-to-end тесты.

Интеграционные тесты предназначены для тестирования интеграций с любыми внешними системами — базами данных в том числе.

Схема 1. Покрываемая область при интеграционном тестировании

Схема 1. Покрываемая область при интеграционном тестировании

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

End-to-end тестирование в свою очередь предназначено для тестирование всего приложения со всеми интеграциями.

Схема 2. Область покрытия при end-to-end тестировании.

Схема 2. Область покрытия при end-to-end тестировании.

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

Применение требований тестирования к веб сервису.

В данном примере был выбран end-to-end способ тестирования для двух сценариев — для сохранения покемона в базу данных и для чтения покемонов из базы данных. 

Это значит, что будет проверен как HTTP контракт, так и интеграция с базой данных.

Для тестирования сервиса используются следующие библиотеки:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>io.kotest</groupId>
    <artifactId>kotest-bom</artifactId>
    <version>5.2.2</version>
    <type>pom</type>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>io.kotest.extensions</groupId>
    <artifactId>kotest-extensions-spring</artifactId>
    <version>1.0.1</version>
</dependency>

Функционал, который будет протестирован.

Веб сервис имеет два эндпоинта с функционалом, который требуется покрыть тестами.

POST /pokemon — сохранение модели в базу данных.
В модели есть 2 обязательных поля — name (String) и weight (Integer).

Пример вызова:

###
POST http://localhost:8080/pokemon
Content-Type: application/json

{
  "name": "bulbasaur",
  "weight": 69
}

GET /pokemon — эндпоинт, который отвечает за предоставление всех записей из базы данных.

В ответе метод возвращает массив моделей с 3 полями — id (Long), name (String), weight (Integer).

Пример вызова:

###
GET http://localhost:8080/pokemon

Ответ вызова:

[
  {
    "id": 1,
    "name": "bulbasaur",
    "weight": 69
  },
  {
    "id": 2,
    "name": "ditto",
    "weight": 40
  }
]

Код контроллера:

@RestController
@RequestMapping("/pokemon")
class PokemonController(private val pokemonDao: PokemonDao) {

    @PostMapping
    fun savePokemon(@RequestBody pokemon: Pokemon) {
        pokemonDao.save(pokemon)
    }

    @GetMapping
    fun getAll(): List<Pokemon> = pokemonDao.getAll()

    @ExceptionHandler
    fun handleException(exception: Exception): ResponseEntity<*> {
        return ResponseEntity(exception.message, HttpStatus.BAD_REQUEST)
    }
}

Описание слоя DAO.

Слой DAO (Data Access Object) отвечает исключительно за интеграцию с хранилищем. Для описания интеграций с базой данных используется jdbcTemplate. JdbcTemplate — библиотека Spring, которая позволяет писать запросы в нативном SQL.

Для простоты маппинга сущностей используется обычный objectMapper. В большой нагрузке такой маппинг может быть изменен на более оптимальный.

Dao слой выглядит следующим образом:

@Service
class PokemonDao(private val jdbcTemplate: JdbcTemplate, private val objectMapper: ObjectMapper) {

    fun save(pokemon: Pokemon) {
        jdbcTemplate.update(SAVE_POKEMON, pokemon.name, pokemon.weight)
    }

    fun getAll(): List<Pokemon> =
        jdbcTemplate.queryForList(SELECT_ALL_POKEMONS)
            .map { objectMapper.readValue(objectMapper.writeValueAsString(it), Pokemon::class.java) }
}

@Language("Sql")
private const val SAVE_POKEMON = "insert into pokemon values(default, ?, ?)"

@Language("Sql")
private const val SELECT_ALL_POKEMONS = "select * from pokemon"

Описание структуры тестов.

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

Вызовы на HTTP эндпоинты описываются с использованием MockMvc. MockMvc — также является одной из библиотек Spring, имеющей довольно неплохой апи для тестирования HTTP эндпоинтов веб сервисов. А также позволяет тестировать веб сервисы без их непосредственного запуска.

Тестирование сценария сохранения покемона.

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

"save pokemon - success" {
            mockMvc.perform(
                MockMvcRequestBuilders
                    .post("/pokemon")
                    .content(objectMapper.writeValueAsString(
                      Pokemon(name = "saved pokemon name", weight = 1)
                    ))
                    .contentType(MediaType.APPLICATION_JSON)
            )

            pokemonDao.getAll().first { it.name == "saved pokemon name" } should {
                it.weight shouldBe 1
                it.id shouldNotBe null
            }
        }

Как мы видим, в случае сохранения покемона с полями name = "saved pokemon name" и weight = 1, мы можем получить его из списка покемонов в базе данных.

Скриншот 1. Результат работы теста позитивного сценария POST /pokemon.

Скриншот 1. Результат работы теста позитивного сценария POST /pokemon.

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

"save pokemon - too long name" {
            mockMvc.perform(
                MockMvcRequestBuilders
                    .post("/pokemon")
                    .content(objectMapper.writeValueAsString(
                        Pokemon(name = "saved pokemon name - just too long name to save", weight = 1))
                    )
                    .contentType(MediaType.APPLICATION_JSON)
            )
                .andExpect(MockMvcResultMatchers.status().is4xxClientError)
        }

В данном случае длина поля name окажется слишком длинной для максимально допустимой в 25 символов и в процессе insert будет выброшено исключение со стороны драйвера базы данных:

PreparedStatementCallback; SQL [insert into pokemon values(default, ?, ?)]; Value too long for column "name VARCHAR(25)": "'saved pokemon name - just too long name to save' (47)";

Это исключение будет обработано в @ExceptionHandler в контроллере и вернет код ошибки 400 клиенту.

Скриншот 2. Результат работы теста негативного сценария POST /pokemon.

Скриншот 2. Результат работы теста негативного сценария POST /pokemon.

Тестирование сценария получения покемонов.

Для позитивного сценария достаточно проверить, что ответе на запрос GET возвращается запись, которая хранится в базе данных. 

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

Скрипт, который будет выполнен во время запуска приложения хранится в /resources/data.sql
и выглядит следующим образом:

insert into pokemon VALUES (default, 'test-pokemon', 45);

В позитивном сценарии мы проверяем, что покемон с именем test-pokemon действительно хранится в базе данных.

"get all pokemons" {
            val pokemons = mockMvc
                .get("/pokemon")
                .andReturn()
                .response
                .contentAsString
                .let { objectMapper.readValue(it, List::class.java) }
                .map { objectMapper.convertValue(it, Pokemon::class.java) }

            pokemons.first {it.name == "test-pokemon"} should {
                it.weight shouldBe 45
                it.id shouldNotBe null
            }
        }

Скриншот 3. Результат работы теста позитивного сценария GET /pokemon

Скриншот 3. Результат работы теста позитивного сценария GET /pokemon

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

"get all pokemons - not found" {
            val pokemons = mockMvc
                .get("/pokemon")
                .andReturn()
                .response
                .contentAsString
                .let { objectMapper.readValue(it, List::class.java) }
                .map { objectMapper.convertValue(it, Pokemon::class.java) }

            pokemons.firstOrNull { it.name == "test-pokemon not found"} shouldBe null
        }

Скриншот 4. Результат работы теста негативного сценария GET /pokemon

Скриншот 4. Результат работы теста негативного сценария GET /pokemon

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

Скриншот 5. Результат работы всех тестов, запущенных единовременно

Скриншот 5. Результат работы всех тестов, запущенных единовременно

Обратите внимание на время выполнения. Если запускать тесты по одному, тогда Spring контекст будет подниматься для каждого теста — это занимает время.

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

Выводы.

Итого, мы рассмотрели основные способы тестирования интеграции веб сервиса с базой данных на примере H2 + Spring Boot. Были рассмотрены отличия интеграционного и end-to-end тестирования, а также рассмотрен пример интеграции веб сервиса базой данных с последующим тестированием и подробным разборов позитивных и негативных сценариев.

Как вы тестируете интеграции с базами данных? Буду рад вашим мыслям в комментариях.

Исходники можно найти в моем GitHub репозитории: ​​pokemon-app

Фото от @jannisbrandtна Unsplash

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