что такое интеграционный тест
Автоматизация системного интеграционного тестирования
Хочу поделиться с вами личным опытом в системном интеграционном тестировании. Наша команда занимается разработкой интеграционного слоя, через который связаны все системы в банке. Задач у нас много, времени не хватает, и вопрос тестирования интеграции всегда откладывался.
Как же происходит тестирование интеграции? Самый короткий ответ — никак, хотя у нас больше сотни систем, которые взаимодействуют через интеграционную шину Oracle Service Bus(OSB). У этого продукта есть инструмент OSB Console, который позволяет послать тестовый запрос и отображает полученный ответ. После того как разработчик реализует на шине новый сервис, сервис вручную проверяется через OSB Console. Если проверка успешна, то сервис объявляется работающим и меняется, только если на него начинают жаловаться разработчики внешних систем.
Поддержка используемой нами OSB подходила к концу, и возникла необходимость перехода на новую версию. Хотя сама миграция больших проблем не вызывала, встал вопрос, а как проверить работоспособность смигрированного решения? И тут наша команда в очередной раз задумалась о внедрении автоматического тестирования.
Как мы себе это представляли
Картинка получилась простая и сразу всем понравилась.
В самом деле, нам нужно просто автоматизировать то, что мы делаем руками. Так давайте создадим тест, который будет хранить пары сообщений (запрос, ответ). После запуска наш тест пошлет запрос, получит ответ и сравнит его с хранящимся у него ответом. Если ответы совпали, то тест прошел успешно.
Виртуализация сервисов (mocks, stubs)
Большие надежды возлагались на Soap UI, который умеет запускать mock-сервисы, как web-приложение. Но, оказалось, что SOAP UI уж очень «UI». Во-первых, он не thread-safe, а во-вторых, использует очень много памяти и работает нестабильно.
В процессе исследований выяснилось, что в нашем банке уже есть аж четыре(!) самописных симулятора web-сервисов. Один из них оказался очень даже ничего, был взят за основу и по мере надобности дописан. Так в тестовом окружении у нас появился симулятор — web-приложение, которое по определенным HTTP-запросам возвращает определенные HTTP-ответы.
Каждый виртуальный сервис — это maven проект. В конфигурации проекта (service-descriptor.xml, response-selector.xml) написано как на основании HTTP-запроса определить, какой вызван сервис, какая нужна операция и по какому правилу вернуть HTTP-ответ.
После изменений исходников проект автоматически собирается на Jenkins и при помощи maven wagon деплоится в работающее приложения симулятора.
А теперь пишем тест
Следующим шагом нам нужно написать тест, который фактически будет симулятором front-end системы. То есть нам нужно написать web-service клиент.
Наш тест является maven проектом. Внутри проекта находятся пары файлов (запрос, ответ), которые собственно и являются исходниками. Билд проекта состоит в том, что наш самописный maven plugin вызывает web-сервис передает ему тестовый запрос, получает ответ и сравнивает его с тестовым ответом.
Тесты запускаются автоматически каждую ночь на Jenkins.
Создание тестовых данных
Поскольку основная задача состояла в тестировании миграции, тестовые запросы и ответы были экспортированы из аудитной базы данных, которая работает в production. По этим данным были сгенерированы соответствующие maven-проекты.
В случае разработки новых сервисов, данных из живых систем еще нет. Для таких случаев мы написали свой инструмент на базе Eclipse. В несколько кликов мы можем создать новый проект, заполнить его тестовыми данных, положить в систему контроля версий и сделать новый проект на Jenkins.
Что получилось
Планы на будущее
Нам понравилось, и мы будем продолжать. В ближайшем будущем планируется добавить симуляторы для JMS, поддержку бизнес-процессов и придумать, как проводить тесты производительности.
А вы тестируете интеграцию? Поделитесь, своим опытом!
Интеграционное тестирование
Интеграционное тестирование (в общем случае) — это вид тестирования, при котором проверяется взаимодействие модулей между собой, а также интеграция подсистем в одну общую систему.
Для интеграционного тестирования используются компоненты, уже проверенные с помощью модульного тестирования.
Модули соединяются между собой с помощью так называемых интерфейсов. Интерфейс — это граница между двумя функциональными модулями, например:
Основная цель интеграционного тестирования — проверить интерфейсы между модулями.
Важно понимать, что в рамках интеграционного тестирования не проверяются 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) могут быть до определённого момента не видны.
Модуль самого высокого уровня тестируется отдельно.
Модули нижнего уровня тестируются по схеме снизу вверх.
Преимущества
Даёт уверенность в модулях нижнего уровня плюс сразу виден общий уровень готовности софта к релизу.
Хорош для больших проектов в которых нужно ставить реалистичные сроки выполнения.
Недостатки
Нужно дополнительно время на координацию и вовлечение потенциально большего числа участинков тестировани.
Как организовать интеграционное тестирование
Краткое описание интеграционных тест планов
Включает в себя следующие атрибуты:
Входные и выходные критерии интеграционного тестирования
Критерии входа и выхода на этап интеграционного тестирования в любой модели разработки программного обеспечения
Входные критерии :
Выходные критерии:
Руководства и советы
Виды тестирования и подходы к их применению
Блочное (модульное, unit testing) тестирование наиболее понятное для программиста. Фактически это тестирование методов какого-то класса программы в изоляции от остальной программы.
Не всякий класс легко покрыть unit тестами. При проектировании нужно учитывать возможность тестируемости и зависимости класса делать явными. Чтобы гарантировать тестируемость можно применять TDD методологию, которая предписывает сначала писать тест, а потом код реализации тестируемого метода. Тогда архитектура получается тестируемой. Распутывание зависимостей можно осуществить с помощью Dependency Injection. Тогда каждой зависимости явно сопоставляется интерфейс и явно определяется как инжектируется зависимость — в конструктор, в свойство или в метод.
Для осуществления unit тестирования существуют специальные фреймворки. Например, NUnit или тестовый фреймфорк из Visual Studio 2008. Для возможности тестирования классов в изоляции существуют специальные Mock фреймворки. Например, Rhino Mocks. Они позволяют по интерфейсам автоматически создавать заглушки для классов-зависимостей, задавая у них требуемое поведение.
По unit тестированию написано много статей. Мне очень нравится MSDN статья Write Maintainable Unit Tests That Will Save You Time And Tears, в которой хорошо и понятно рассказывается как создавать тесты, поддерживать которые со временем не становится обременительно.
Интеграционное тестирование
Интеграционное тестирование, на мой взгляд, наиболее сложное для понимания. Есть определение — это тестирование взаимодействия нескольких классов, выполняющих вместе какую-то работу. Однако как по такому определению тестировать не понятно. Можно, конечно, отталкиваться от других видов тестирования. Но это чревато.
Если к нему подходить как к unit-тестированию, у которого в тестах зависимости не заменяются mock-объектами, то получаем проблемы. Для хорошего покрытия нужно написать много тестов, так как количество возможных сочетаний взаимодействующих компонент — это полиномиальная зависимость. Кроме того, unit-тесты тестируют как именно осуществляется взаимодействие (см. тестирование методом белого ящика). Из-за этого после рефакторинга, когда какое-то взаимодействие оказалось выделенным в новый класс, тесты рушатся. Нужно применять менее инвазивный метод.
Подходить же к интеграционному тестированию как к более детализированному системному тоже не получается. В этом случае наоборот тестов будет мало для проверки всех используемых в программе взаимодействий. Системное тестирование слишком высокоуровневое.
Идея простая. У нас есть входные данные, и мы знаем как программа должна отработать на них. Запишем эти знания в текстовый файл. Это будет спецификация к тестовым данным, в которой записано, какие результаты ожидаются от программы. Тестирование же будет определять соответствие спецификации и того, что действительно находит программа.
Проиллюстрирую на примере. Программа конвертирует один формат документа в другой. Конвертирование хитрое и с кучей математических расчетов. Заказчик передал набор типичных документов, которые ему требуется конвертировать. Для каждого такого документа мы напишем спецификацию, где запишем всякие промежуточные результаты, до которых дойдет наша программа при конвертировании. 1) Допустим в присланных документах есть несколько разделов. Тогда в спецификации мы можем указать, что у разбираемого документа должны быть разделы с указанными именами: $SectionNames = Введение, Текст статьи, Заключение, Литература 2) Другой пример. При конвертировании нужно разбивать геометрические фигуры на примитивы. Разбиение считается удачным, если в сумме все примитивы полностью покрывают оригинальную фигуру. Из присланных документов выберем различные фигуры и для них напишем свои спецификации. Факт покрываемости фигуры примитивами можно отразить так: $IsCoverable = true |
Понятно, что для проверки подобных спецификаций потребуется движок, который бы считывал спецификации и проверял их соответствие поведению программы. Я такой движок написал и остался доволен данным подходом. Скоро выложу движок в Open Source. (UPD: Выложил)
Данный вид тестирования является интеграционным, так как при проверке вызывается код взаимодействия нескольких классов. Причем важен только результат взаимодействия, а не детали и порядок вызовов. Поэтому на тесты не влияет рефакторинг кода. Не происходит избыточного или недостаточного тестирования — тестируются только те взаимодействия, которые встречаются при обработке реальных данных. Сами тесты легко поддерживать, так как спецификация хорошо читается и ее просто изменять в соответствии с новыми требованиями.
Системное тестирование
Системное — это тестирование программы в целом. Для небольших проектов это, как правило, ручное тестирование — запустил, пощелкал, убедился, что (не) работает. Можно автоматизировать. К автоматизации есть два подхода.
Первый подход — это использовать вариацию MVC паттерна — Passive View (вот еще хорошая статья по вариациям MVC паттерна) и формализовать взаимодействие пользователя с GUI в коде. Тогда системное тестирование сводится к тестированию Presenter классов, а также логики переходов между View. Но тут есть нюанс. Если тестировать Presenter классы в контексте системного тестирования, то необходимо как можно меньше зависимостей подменять mock объектами. И тут появляется проблема инициализации и приведения программы в нужное для начала тестирования состояние. В упомянутой выше статье Scenario Driven Tests об этом говорится подробнее.
Тестируем интеграцию с внешними сервисами
Современные приложения редко работают в изоляции, чаще всего они интегрируются с множеством сторонних сервисов. Поэтому остро встает вопрос тестирования этой интеграции в рамках интеграционного или функционального тестирования. И тут можно наступить на многие грабли, о которых я и собираюсь поговорить. Для кого эта тема актуальна, прошу под кат.
В чем собственно проблема?
Первое решение, которое приходит в голову — тестировать напрямую на реальном сервисе. В некоторых случаях это может быть обосновано, но часто приводит к следующим проблемам:
Описанные проблемы заставляют вас искать альтернативные решения и они существуют.
Заглушки правят миром тестирования!
На помощь вам могут придти разнообразные варианты заглушек (не нашел лучшего перевода термина mock objects). Есть несколько вариантов заглушек: mock, stub, dummy, spy, fake. Знать и различать их нужно и важно, поэтому стоит почитать и разобраться в специфике, но речь пойдет не об этом. Нам понадобится только один из видов заглушек, который наиболее подходит для работы с внешними сервисами — fake реализация.
Fake реализация представляет из себя упрощенный вариант настоящего сервиса, который работает полностью предсказуемо, локально, дает полный доступ к данным и настройкам, а также не требует никакой дополнительной работы в каждом тесте. Таким образом, мы сможем прозрачно заменить настоящий сервис в тестах на fake реализацию и решить все проблемы. Звучит просто? Не очень!
Во-первых, нам нужно будет реализовать этот упрощенный вариант сервиса, на что понадобится время и усилия разработчиков. Вы счастливчик, если ваш внешний сервис предоставляет fake реализацию для локального тестирования. Тогда вы просто скачиваете библиотеку, настраиваете, запускаете и пользуетесь на здоровье. Некоторые сервисы позволяют получить доступ к специальной версии внешнего сервиса для тестирования, которая не подвержена перечисленным в первой части проблемам. Но таких сервисов единицы. Почти всегда вам придется писать fake реализацию самостоятельно.
Во-вторых, вам придется позаботиться о том, чтобы ваша fake реализация ведет себя так же как реальный сервис. Это одни из самых частых граблей, на которые наступают сторонники описанного подхода. Вы делаете fake реализацию сегодня, а через неделю реальный сервис начинает вести себя по-другому. Ваши тесты по-прежнему проходят и вы спокойно живете пока не задеплоите ваш код на «живые» сервера. И вот там вас ждет неприятный сюрприз!
Эту проблему очень легко исправить, но придется приложить еще немного усилий. Вам нужно написать еще один набор тестов, теперь уже на реальный сервис, целью которого будет контроль и поддержание надежности протокола между вашим приложением и сторонним сервисом. Это некоторый вариант smoke тестов на внешний сервис, которые можно запускать раз в час, день или неделю в зависимости от стабильности вашего внешнего сервиса. Тогда вы действительно контролируете и тестируете интеграцию с внешним сервисом и избегаете сюрпризов при его изменении.
Я не готов к таким изменениям!
Описанное решение подходит не всем (в принципе как и любое другое). Поэтому я дам несколько советов, с помощью которых вы можете немного сгладить проблемы из первой части этой статьи.
Кому-то даже этот набор мер позволит существенно улучшить свое тестирование.
Заключение
А вообще, самое главное — это понимать описанные проблемы и иметь желание их разрешить. Тогда можно придумать множество альтернативных решений и подходов, которые будут работать еще лучше приведенных в статье для вашего конкретного случая. И тогда ваши тесты снова будут быстрыми, надежными и еще более полезными для вашей команды.