Что такое интеграционный тест
Интеграционное тестирование
Интеграционное тестирование (в общем случае) — это вид тестирования, при котором проверяется взаимодействие модулей между собой, а также интеграция подсистем в одну общую систему.
Для интеграционного тестирования используются компоненты, уже проверенные с помощью модульного тестирования.
Модули соединяются между собой с помощью так называемых интерфейсов. Интерфейс — это граница между двумя функциональными модулями, например:
Основная цель интеграционного тестирования — проверить интерфейсы между модулями.
Важно понимать, что в рамках интеграционного тестирования не проверяются end-to-end бизнес сценарии.
Так как в процессе тестирования у нас нет потребности рассматривать внутреннюю структуру каждого компонента в отдельности, можно утверждать, что интеграционное тестирование выполняется методом «черного ящика».
Уровни интеграционного тестирования
Различают два основных уровня интеграционного тестирования:
На компонентном уровне интеграционного тестирования проверяется взаимодействие между компонентами системы после проведения компонентного тестирования. Другими словами, проверяется, насколько корректно взаимодействуют протестированные в отдельности модули между собой.
На системном уровне проверяется взаимодействие между разными системами после проведения системного тестирования.
Подходы к интеграционному тестированию
Снизу вверх (Bottom Up Integration)
Все низкоуровневые модули, процедуры или функции собираются воедино и затем тестируются. После чего собирается следующий уровень модулей для проведения интеграционного тестирования. Это продолжается до тех пор, пока не будут интегрированы все модули и конечная система не образует единый модуль.
В случае, представленном на изображении выше, модули B1C1, B1C2, B2C1 и B2C2 являются самыми «низкими» модулями и протестированы отдельно друг от друга с помощью модульного тестирования. Модули B1 и B2 еще не разработаны. В связи с тем, что разработка модулей B1 и B2 находится в процессе, то для тестирования необходима программа, которая обращалась бы к функциям модулей B1C1 и B2C2. Такие программы называются драйверами и представляют собой функции, которые обращаются к функциям более низких уровней. Драйверы необходимы для того, чтобы с помощью интерфейсов вызывать в рамках тестирования более низкие модули.
Данный подход считается полезным, если все (или практически все) модули разрабатываемого уровня готовы. Также данный подход помогает определить уровень готовности приложения по результатам тестирования.
Подход «Снизу-Вверх» позволяет обнаружить дефекты на ранних этапах и позволяет просто локализовать сами дефекты и причины их возникновения.
Недостатком такого подхода является то, что приходится тестировать модули еще до того, как будет реализована «главная» программа, что, соответственно, требует технических навыков.
Сверху вниз (Top Down Integration)
Вначале тестируются все высокоуровневые модули, и постепенно, один за другим, добавляются низкоуровневые. Все модули более низкого уровня симулируются заглушками с аналогичной функциональностью, затем, по мере готовности, они заменяются реальными активными компонентами. Таким образом, мы проводим тестирование сверху вниз.
Все (или практически все) разработанные модули собираются вместе в виде законченной системы или ее основной части и затем проводится интеграционное тестирование. Другими словами, тестирование начинается от середины схемы модулей (для картинки выше) и двигается в обе стороны одновременно.
Такой подход очень хорош для сохранения времени. Однако если тест кейсы и их результаты записаны не верно, то сам процесс интеграции сильно осложнится, что станет преградой для команды тестирования при достижении основной цели интеграционного тестирования. Так же данный подход требует больше ресурсов, в связи с его сложностью.
В целом, для проведения хорошего интеграционного тестирования необходимо:
Интеграционное тестирование
Что такое интеграционное тестирование
Предположим, что есть несколько небольших систем, каждая из которых работает хорошо.
Разработчики провели модульное тестирование и убедились, что все необходимые юнит тесты (Unit Tests) пройдены.
Эти системы нужно объединить в одну. Логичный вопрос:
Будет ли новая большая система работать так же хорошо как и её части?
Чтобы ответить на него нужно провести тестирование системы (System Testing).
Оно обычно требует значительных ресурсов, поэтому появляются другие вопросы:
Есть ли смысл тестировать систему целиком в данный момент?
Взаимодействуют ли части между собой правильно?
Ответить на эти вопросы можно только после интеграционного тестирования (Integration Testing).
Лирическое отступление
Рассмотрим аналогию далёкую от IT. У Вас есть склад и два отряда новобранцев: пожарные и крестьяне. Нужно проверить насколько быстро пожарные носят воду, а крестьене сеют пшеницу. Результатом будет, например тысяча литров в сутки и один гектар в день. Это аналог системного тестирования: поле засеяно, вода перенесена.
Но что если подходя ко складу каждый пожарный будет брать сито вместо ведра а крестьянам придётся пользоваться оставшимися вёдрами?
Чтобы избежать проблем нужно на выходе из склада поставить человека, который будет проверять, правильное оборудование берут новобранцы или нет.
Это и будет интеграционным тестированием взаимодействия новобранцев со складом.
Определение
ИНТЕГРАЦИОННОЕ ТЕСТИРОВАНИЕ определяется как тип тестирования, при котором программные модули интегрируются логически и тестируются как группа.
Типичный программный проект состоит из нескольких программных модулей, закодированных разными программистами.
Целью данного уровня тестирования является выявление дефектов взаимодействия между этими программными модулями при их интеграции.
Интеграционное тестирование фокусируется на проверке обмена данными между этими модулями. Следовательно, его также называют «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 | Нажать кнопку «На карте» и убедиться, что отели видны. | Карта открыта и на ней можно увидеть отели |
Теперь разберём действия пошагово.
Нужно зайти на сайт Aviasales и выбрать какой-то маршрут.
Изображение с сайта Aviasales
Переход на новую страницу осуществлён, но я по-прежнему на том же сайте.
Нужно нажать кнопку «Найти отели»
Изображение с сайта Aviasales
Изображение с сайта Booking.com
Изображение с сайта Booking.com
Надеюсь Вам стало чуть понятней, что такое интеграционное тестирование. Конечно, приведённый пример очень сильно упрощён. В реальном мире тестировать пришлось бы гораздо детальнее.
Главное, на что был бы сделан акцент это проверка прохождения комиссий то есть денег. А это гораздо сложнее чем прокликать вручную пару страниц.
Продолжим разбираться с интеграционным тестированием, сфокусировавшись на его различных видах.
Подходы, стратегии, методологии интеграционного тестирования
Подход Большой Взрыв
В подходе Большого взрыва большинство разработанных модулей соединяются вместе, образуя либо всю необходимую систему либо её большую часть.
Затем начинается тестирование.
Преимущества
Если всё работает, то таким спобом можно сэкономить много времени.
Недостатки
Однако если что-то пошло не так, будет сложно наити причину. Особенно тяжело разбираться в результатах большого взрыва когда тесты и/или их результаты не записаны достаточно подробно.
Весь процесс интеграции может стать гораздо более сложным чем при тестировании снизу вверх или сверху внизу.
Всё это может помешать достичь цели интеграционного тестирования в разумные сроки.
Из всего вышеперечисленного можно сделать вывод о том, что подход Большого взрыва это потенциально быстрый но рискованный подход.
Инкрементальный подход
При таком подходе тестирование выполняется путем соединения двух или более логически связанных модулей.
Затем добавляются и проверяются на правильность функционирования другие соответствующие модули.
Процесс продолжается до тех пор, пока все модули не будут соединены и успешно протестированы.
Инкрементный подход, в свою очередь, осуществляется двумя различными методами:
Заглушки и драйверы
Инкрементный подход осуществляется с помощью фиктивных программ, называемых заглушками и драйверами. Заглушки и драйверы не реализуют всю логику программирования программного модуля, а просто имитируют передачу данных с вызывающим модулем.
Заглушка: вызывается тестируемым модулем.
Драйвер: вызывает модуль для тестирования.
Как делать заглушки?
Конечно, всё зависит от того, для чего Вы делаете заглушку. Кругому люку нужна круглая крышка.
Изображение с сайта bestluki.ru
Если Вам нужна заглушка для REST API Вы можете прочитать подробные инструкции в следующих статьях:
В SOAP UI для обозначения заглушек используется термин Mock Service
Подход Снизу Вверх
Требуется помощь драйверов для тестирования
Преимущества
Локализовать ошибки намного проще. Сразу видно какой из-за какого модуля проваливается тест.
Не тратится время на ожидание разработки всех модулей, в отличие от подхода Большого взрыва. Для продвижения тестирования достаточно наличия только определённых модулей на один уровень выше.
Недостатки
Критические модули (на верхнем уровне архитектуры программного обеспечения), которые контролируют поток приложения, тестируются последними и могут быть подвержены дефектам.
То есть всё может работать хорошо, но небольшая ошибка в реализации бизнес логики на верхнем уровке вынудит провести всё тестирование заново.
Ранний прототип невозможен поэтому если MVP Вам нужен быстро и наличие каких-то ошибок некритично, то с Bottom-Up тестированием можно немного подождать и провести хотя бы несколько тестов сразу на более высоком уровне.
Метод Сверху Вниз
При подходе сверху вниз тестирование выполняется сверху вниз, следуя потоку управления программной системы.
Пользуется заглушками для тестирования.
Преимущества
Локализация неисправностей проще.
Возможность получить ранний прототип.
Критические Модули тестируются в соответствии с их приоритетом. Основные недостатки дизайна могут быть найдены и исправлены в первую очередь.
Ошибки в реализации бизнес-логики будут видны в самом начале тестирования.
Недостатки
Нужно много заглушек. Если на более низких уровнях реализованы ещё не все модули, их нужно имитировать. Это дополнительная работа для разработчика или тестировщика.
Модули на более низком уровне тестируются неадекватно. Какие-то ошибки особенно в маловероятных сценариях и пограничных случаях (Corner Cases) могут быть до определённого момента не видны.
Модуль самого высокого уровня тестируется отдельно.
Модули нижнего уровня тестируются по схеме снизу вверх.
Преимущества
Даёт уверенность в модулях нижнего уровня плюс сразу виден общий уровень готовности софта к релизу.
Хорош для больших проектов в которых нужно ставить реалистичные сроки выполнения.
Недостатки
Нужно дополнительно время на координацию и вовлечение потенциально большего числа участинков тестировани.
Как организовать интеграционное тестирование
Краткое описание интеграционных тест планов
Включает в себя следующие атрибуты:
Входные и выходные критерии интеграционного тестирования
Критерии входа и выхода на этап интеграционного тестирования в любой модели разработки программного обеспечения
Входные критерии :
Выходные критерии:
Руководства и советы
Для чего нужно интеграционное тестирование?
Эта статья является конспектом книги «Принципы юнит-тестирования». Материал статьи посвящен интеграционным тестам.
Юнит-тесты прекрасно справляются с проверкой бизнес-логики, но проверять эту логику «в вакууме» недостаточно. Необходимо проверять, как разные ее части интегрируются друг с другом и внешними системами: базой данных, шиной сообщений и т. д.
В этой статье рассматривается роль интеграционных тестов, когда их следует использовать и когда лучше положиться на классические юнит-тесты. Также затронем эффективное написание интеграционных тестов.
Что такое интеграционный тест?
Юнит-тест удовлетворяет следующим трем требованиям:
проверяет правильность работы одной единицы поведения;
и в изоляции от других тестов.
Тест, который не удовлетворяет хотя бы одному из этих трех требований, относится к категории интеграционных тестов. На практике интеграционные тесты почти всегда проверяют, как система работает в интеграции с внепроцессными зависимостями.
Если все внепроцессные зависимости заменить моками, никакие зависимости не будут совместно использоваться между тестами, благодаря чему эти тесты останутся быстрыми и сохранят свою изоляцию друг от друга и становятся юнит-тестами. Тем не менее во многих приложениях существует внепроцессная зависимость, которую невозможно заменить моком. Обычно это база данных — зависимость, не видимая другими приложениями.
Важно поддерживать баланс между юнит- и интеграционными тестами. Работа напрямую с внепроцессными зависимостями замедляет интеграционные тесты. Кроме того, их сопровождение также обходится дороже.
С другой стороны, интеграционные тесты проходят через больший объем кода, что делает их более эффективными в защите от багов по сравнению с юнит-тестами. Они также более отделены от рабочего кода, а, следовательно, обладают большей устойчивостью к его рефакторингу.
Соотношение между юнит- и интеграционными тестами зависит от особенностей проекта, но общее правило выглядит так: проверьте как можно больше пограничных случаев бизнес-сценария юнит-тестами; используйте интеграционные тесты для покрытия одного позитивного пути, а также всех граничных случаев, которые не покрываются юнит-тестами.
Для интеграционного теста выберите самый длинный позитивный путь, проверяющий взаимодействия со всеми внепроцессными зависимостями. Если не существует одного пути, проходящего через все такие взаимодействия, напишите дополнительные интеграционные тесты — столько, сколько потребуется для отражения взаимодействий с каждой внешней системой.
Какие из внепроцессных зависимостей должны проверяться напрямую
Все внепроцессные зависимости делятся на две категории.
Управляемые зависимости (внепроцессные зависимости, находящиеся под вашим полным контролем): эти зависимости доступны только через ваше приложение; взаимодействия с ними не видны внешнему миру. Типичный пример — база данных.
Неуправляемые зависимости (внепроцессные зависимости, которые не находятся под вашим полным контролем) — результат взаимодействия с такими зависимостями виден извне. В качестве примеров можно привести сервер SMTP и шину сообщений.
Взаимодействия с управляемыми зависимостями относятся к деталям имплементации. И наоборот, взаимодействия с неуправляемыми зависимостями являются частью наблюдаемого поведения вашей системы.
Рис. 1 – Взаимодействия с зависимостями
Это различие приводит к тому, что такие зависимости по-разному обрабатываются в интеграционных тестах. Взаимодействия с управляемыми зависимостями являются деталями имплементации. Использовать их следует в исходном виде в интеграционных тестах. Взаимодействия с неуправляемыми зависимостями являются частью наблюдаемого поведения системы. Такие зависимости должны заменяться моками.
Требование о сохранении схемы взаимодействий с неуправляемыми зависимостями обусловлено необходимостью поддержания обратной совместимости с такими зависимостями. Моки идеально подходят для этой задачи. Они позволяют обеспечить неизменность схемы взаимодействий при любых возможных рефакторингов. Поддерживать обратную совместимость во взаимодействиях с управляемыми зависимостями не обязательно, потому что никто, кроме вашего приложения, с ними не работает. Внешних клиентов не интересует, как устроена ваша база данных; важно только итоговое состояние вашей системы. Использование реальных экземпляров управляемых зависимостей в интеграционных тестах помогает проверить это итоговое состояние с точки зрения внешних клиентов.
Иногда встречаются внепроцессные зависимости, обладающие свойствами как управляемых, так и неуправляемых зависимостей. Хорошим примером служит база данных, доступная для других приложений.
База данных — не лучший механизм для интеграции между системами, потому что она связывает эти системы друг с другом и усложняет дальнейшую их разработку. Используйте это решение только в случае, если других вариантов нет. Правильнее осуществлять интеграцию через API (для синхронных взаимодействий) или шину сообщений (для асинхронных взаимодействий).
В этом случае следует рассматривать таблицы, видимые для других приложений, как неуправляемую зависимость. Такие таблицы фактически выполняют функции шины сообщений, а их строки играют роль сообщений. Используйте моки, чтобы гарантировать неизменность схемы взаимодействий с этими таблицами. В то же время следует рассматривать остальные части базы данных как управляемую зависимость и проверять ее итоговое состояние, а не взаимодействия с ней.
Рис. 2 – БД, доступная для внешних приложений
Основные приемы интеграционного тестирования
Существует несколько общих рекомендаций, которые помогут извлечь максимальную пользу из интеграционных тестов:
явное определение границ доменной модели (модели предметной области);
сокращение количества слоев в приложении;
устранение циклических зависимостей.
Явное определение границ модели предметной области. Доменная модель представляет собой совокупность знаний о предметной области задачи, для решения которой предназначен ваш проект. Данная практика помогает с тестированием. Юнит-тесты должны ориентироваться на доменную модель и алгоритмы, тогда как интеграционные тесты — на контроллеры. Таким образом, четкое разграничение между доменными классами и контроллерами также помогает отделить юнит-тесты от интеграционных.
Сокращение количества слоев. Многие разработчики стремятся к абстрагированию и обобщению кода путем введения дополнительных уровней абстракции.
Рис. 3 – Типичное корпоративное приложение с несколькими слоями
В некоторых приложениях находится столько уровней абстракции, что разработчик уже не может разобраться в коде и понять логику даже простейших операций.
«Все проблемы в программировании можно решить путем добавления нового уровня абстракции (кроме проблемы наличия слишком большого количества уровней абстракции)».
Дэвид Дж. Уилер
Лишние абстракции также затрудняют юнит- и интеграционное тестирование. Кодовые базы со слишком большим количеством слоев обычно не имеют четкой границы между контроллерами и моделью предметной области. Старайтесь ограничиться минимально возможным количеством уровней абстракции. В большинстве серверных систем можно обойтись всего тремя: слоем доменной модели, слоем сервисов приложения (контроллеров) и слоем инфраструктуры.
Исключение циклических зависимостей. Циклическая зависимость возникает в том случае, если два или более класса прямо или косвенно зависят друг от друга. Типичный пример циклической зависимости — обратный вызов:
Как и в случае с избыточными уровнями абстракции, циклические ссылки создают дополнительную когнитивную нагрузку при попытке прочитать и понять код. Циклические зависимости также усложняют тестирование. Вам часто приходится использовать интерфейсы и моки, для того чтобы разбить граф классов и изолировать одну единицу поведения.
Что же делать с циклическими зависимостями? Лучше всего совсем избавиться от них. Отрефакторить класс ReportGenerationService, чтобы он не зависел от CheckOutService и сделать так, чтобы ReportGenerationService возвращал результат работы в виде простого значения вместо вызова CheckOutService:
Использование нескольких секций действий в тестах
Как вы, возможно, помните из предыдущего конспекта «Анатомия юнит-тестов», наличие более одной секции подготовки, действий или проверки в тесте — плохой признак. Он указывает на то, что тест проверяет несколько единиц поведения, что, в свою очередь, ухудшает сопровождаемость теста. Например, если имеются два связанных сценария использования (допустим, регистрация и удаление пользователя).
подготовка — подготовка данных для регистрации пользователя;
действие — вызов UserController.RegisterUser();
проверка — запрос к базе данных для проверки успешного завершения регистрации;
действие — вызов UserController.DeleteUser();
проверка — запрос к базе данных для проверки успешного удаления.
Лучше всего разбить тест, выделив каждое действие в отдельный тест. На первый взгляд это может показаться лишней работой, но эта работа окупается в долгосрочной перспективе. Фокусировка каждого теста на одной единице поведения упрощает понимание и изменение этих тестов при необходимости.
Исключение из этой рекомендации составляют тесты, работающие с внепроцессными зависимостями, трудно приводимыми в нужное состояние. Например, регистрация пользователя приводит к созданию банковского счета во внешней банковской системе. Банк предоставил вашей организации тестовую среду, которую вы хотите использовать для сквозных тестов. К сожалению, тестовая среда работает слишком медленно; также возможно, что банк ограничивает количество обращений к этой тестовой среде. В таком сценарии удобнее объединить несколько действий в один тест, чтобы сократить количество взаимодействий с проблемной внепроцессной зависимостью.
Выводы
Интеграционные тесты проверяют, как ваша система работает в интеграции с внепроцессными зависимостями.
Интеграционные тесты покрывают контроллеры; юнит-тесты покрывают алгоритмы и доменную модель.
Интеграционные тесты обеспечивают лучшую защиту от багов и устойчивость к рефакторингу; юнит-тесты более просты в поддержке и дают более быструю обратную связь.
«Порог» для написания интеграционных тестов выше, чем для юнит-тестов: их эффективность по метрике защиты от багов и устойчивости к рефакторингу должна быть выше, чем у юнит-тестов, для того чтобы скомпенсировать дополнительную сложность в поддержке и медленную обратную связь. Пирамида тестирования отражает этот компромисс: большинство тестов должны составлять быстрые и простые в поддержке юнит-тесты при меньшем количестве медленных и более сложных в поддержке интеграционных тестов, проверяющих правильность системы в целом.
Управляемые зависимости представляют собой внепроцессные зависимости, доступ к которым осуществляется только через ваше приложение. Взаимодействия с управляемыми зависимостями не видимы извне. Типичный пример — база данных приложения.
Неуправляемые зависимости — внепроцессные зависимости, доступные для других приложений. Взаимодействия с неуправляемыми зависимостями видны снаружи. Типичные примеры — сервер SMTP и шина сообщений.
Взаимодействия с управляемыми зависимостями являются деталями имплементации; взаимодействия с неуправляемыми зависимостями являются частью наблюдаемого поведения вашей системы.
Иногда внепроцессная зависимость обладает свойствами как управляемых, так и неуправляемых зависимостей. Типичный пример — база данных, доступная для других приложений. Наблюдаемую часть такой базы следует интерпретировать как неуправляемую зависимость; заменяйте ее моками в тестах. Рассматривайте остальную часть зависимости как управляемую — проверяйте ее итоговое состояние, а не взаимодействия с ней.
Выделите явное место для модели предметной области в коде. Четкая граница между классами предметной области и контроллерами помогает отличать юниттесты от интеграционных.
Лишние уровни абстракции отрицательно влияют на вашу способность понимать код. Постарайтесь свести количество этих уровней к минимуму. В большинстве бэкенд-систем достаточно всего трех слоев: предметной области, сервисов приложения и инфраструктуры.
Циклические зависимости увеличивают когнитивную нагрузку при попытках разобраться в коде. Типичный пример — обратный вызов (когда вызываемая сторона уведомляет вызывающую о результате своей работы).
Множественные секции действий в тестах оправданны только в том случае, если тест работает с внепроцессными зависимостями, которые трудно привести в нужное состояние. Никогда не включайте несколько действий в юнит-тест, потому что юнит-тесты не работают с внепроцессными зависимостями. Многофазные тесты почти всегда принадлежат к категории сквозных.