[Перевод] Интеграционные тесты
Интеграционные тесты помогают определить, правильно ли работают после соединения разные юниты программного обеспечения, которые были разработаны независимо друг от друга. Термин «интеграционные тесты» стал размытым даже в рамках неточных стандартов индустрии программного обеспечения, поэтому я использую его в своих работах с осторожностью. В частности, многие считают, что интеграционные тесты обязательно должны быть широкими по охвату, в то время как при более узком охвате они могут быть даже более эффективными.
Как часто бывает в подобных случаях, лучше всего начать с истории. Впервые об интеграционном тестировании я узнал в 1980-х годах, а тогда в мышлении разработчиков преобладала каскадная модель разработки («Waterfall»). В крупном проекте мы сначала проходили этап проектирования, на котором утверждали интерфейс и поведение различных модулей системы. Затем модули передавались разработчикам для программирования. Нередко один программист отвечал за один модуль, но модуль был достаточно большим, и на его создание могли уйти месяцы. Вся эта работа выполнялась изолированно, и, когда программист считал её законченной, он передавал её в QA-отдел для тестирования.
Первой частью тестирования является модульное тестирование, в ходе которого проверяется отдельно сам модуль — в соответствии со спецификацией, составленной на этапе проектирования. После этого мы переходим к выполнению интеграционного тестирования, в ходе которого различные модули объединяются либо в целую систему, либо в значимые подсистемы.
Смысл интеграционного тестирования, как следует из названия, заключается в проверке того, работает ли множество отдельно разработанных модулей вместе так, как ожидалось. Оно проводится путём активации множества модулей и запуска тестов более высокого уровня. Эти модули могут быть как частями одного исполняемого файла, так и отдельными.
Если посмотреть на это через призму 2010-х годов, то здесь смешиваются две разные вещи:
- проверка корректной совместной работы отдельно разработанных модулей;
- проверка того, что система из нескольких модулей работает так, как ожидалось.
Эти два пункта было легко спутать, ведь как ещё можно протестировать модули корзины и каталога, не активировав их оба в едином окружении и не запустив тесты, выполняющие оба модуля?
Опыт 2010-х годов предлагает альтернативный вариант, который редко рассматривался в 1980-х. В этом случае мы проверяем интеграцию модулей корзины и каталога, выполняя ту часть кода корзины, которая взаимодействует с каталогом, на тестовом двойнике каталога. Если тестовый двойник является точным двойником каталога, то мы можем протестировать все поведение каталога без активации полного экземпляра каталога. Это может быть не очень важно, если это отдельные модули монолитного приложения;, но очень важно, если каталог является отдельным сервисом, который требует собственных инструментов сборки, окружения и сетевых подключений. Для сервисов такие тесты могут выполняться с тестовым двойником на строне приложения или тестовым двойником на другом конце «провода», с использованием чего-то вроде mountebank.
Очевидной загвоздкой при интеграционном тестировании с двойником является то, является ли этот двойник действительно точным. Но мы можем проверить это отдельно с помощью контрактных тестов.
Используя эту комбинацию узких интеграционных тестов и контрактных тестов, я могу быть уверен в интеграции с внешним сервисом, никогда не запуская тесты с реальным экземпляром этого сервиса — что значительно облегчает мне процесс сборки. Команды, которые так поступают, всё равно могут проводить сквозное тестирование системы со всеми реальными сервисами;, но если это так, то это лишь финальное дымовое тестирование с очень ограниченным набором проверяемых путей. Это также помогает иметь развитые возможности QA в продакшене, и, если они достаточно развиты, то сквозное тестирование системы может вообще не проводиться.
Проблема в том, что есть по меньшей мере два разных представления о том, что такое интеграционный тест.
Узкие интеграционные тесты:
- проверяют только ту часть кода в моём сервисе, которая обращается к отдельному сервису;
- используют тестовые двойники этих сервисов — либо в процессе, либо удалённо;
- таким образом, они состоят из множества узкоспециализированных тестов, часто не превышающих по объёму модульный тест (и обычно выполняются с помощью того же тестового фреймворка, который используется для модульных тестов).
Широкие интеграционные тесты:
- требуют живых версий всех сервисов, что требует значительного тестового окружения и доступа к сети;
- проверка веток выполнения кода всех сервисов, а не только кода, отвечающего за взаимодействие.
Для большого числа разработчиков «интеграционный тест» означает только «широкие интеграционные тесты» — и это приводит к путанице, когда они взаимодействют с людьми, которые имеют в виду узкие тесты.
Если вы используете только широкие интеграционные тесты, вам следует рассмотреть возможность использования узких тестов — это может значительно повысить скорость тестирования, простоту использования и отказоустойчивость. Поскольку узкие интеграционные тесты ограничены по объёму, они часто выполняются очень быстро, поэтому их можно запускать на ранних стадиях конвейера развёртывания, обеспечивая более оперативную обратную связь в случае возникновения ошибок.
Если этой терминологической путаницы недостаточно, то в конце 2010-х годов ситуация усугубилась ещё одним использованием термина «интеграционный тест». Это произошло из-за расхождений в понимании термина Unit Test. Некоторые люди определяют юнит-тест (или, другими словами, модульный тест) как то, что я называю одиночным юнит-тестом, в котором все элементы программы, кроме тестируемого, заменены тестовыми двойниками. Учитывая это узкое определение, некоторые авторы, говоря «интеграционные тесты», подразумевают под ним «общительные» юнит-тесты.
Именно поэтому я с опаской отношусь к «интеграционному тесту». Когда я читаю его, я стараюсь найти больше контекста, чтобы знать, какой вид теста автор действительно имеет в виду. Если речь идёт о широких интеграционных тестах, я предпочитаю применять термины «системный тест» или «сквозной тест». Я не смог найти более подходящее название для «узких интеграционных тестов», поэтому я так и говорю (но с уточнением «узкий», чтобы помочь читателю понять природу этих тестов). Я продолжаю использовать название «unit test» для обоих видов, прибегая к терминам «одинокий» и «общительный» (solitary / sociable), когда нужно сделать различие.
Узнать больше о сфере тестирования можно на специализации «QA Automation Engineer».