Создание микросервисов — страница 39 из 69

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

Более интеллектуальный сервис-заглушка

Обычно я создаю сервисы-заглушки самостоятельно. Для подобного тестирования мне приходилось использовать все, от Apache или Nginx до Jetty-контейнеров или даже запускаемых из командной строки веб-сервисов на Python. И, возможно, при создании этих заглушек я снова и снова занимался одним и тем же. А вот мой коллега Брендон Брайарс (Brandon Bryars) из ThoughtWorks создал сервер заглушек-имитаторов под названием Mountebank, чем потенциально освободил многих из нас от части работы.

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

Следовательно, если нужно запустить тесты сервисов только для одного клиентского сервиса, то можно запустить клиентский сервис и экземпляр Mountebank, работающий как банк бонусных баллов. И если намеченные тесты будут пройдены, я могу с легкой душой развернуть клиентский сервис! Или все же не могу? А как в таком случае быть с теми сервисами, которые вызывают клиентский сервис, — с сервисом технической поддержки и веб-магазином? Знаем ли мы, что внесенное изменение не станет причиной нарушения работы этих сервисов? Ну конечно же, мы упустили из виду весьма важные тесты, находящиеся на вершине пирамиды, — сквозные тесты.

Сложности, связанные со сквозными тестами

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

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

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

Рис. 7.7. Правильно ли мы поступаем, добавляя сюда этап сквозных тестов?


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

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

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

Рис. 7.8. Стандартный способ проведения сквозных тестов в отношении сервисов

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

Недостатки сквозного тестирования

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


Тесты со странностями, не дающие четкого представления об источнике сбоя

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

Чем больше активных компонентов, тем более капризными и менее информативными в заданных для них областях могут стать наши тесты. Если есть такие тесты, которые время от времени дают сбой, но все просто их перезапускают, поскольку они могут быть пройдены при последующих запусках, значит, это тесты со странностями. Ими могут быть не только тесты, охватывающие множество различных процессов, которые могут в данной ситуации стать виновниками сбоя. Часто проблемный характер приобретают тесты, которые охватывают функции, выполняемые в нескольких потоках, где сбой может означать возникновение конфликтной ситуации, истечение времени ожидания или настоящий сбой той или иной функции. Тесты со странностями — ваши враги. Когда они дают сбой, они не вскрывают всю его подоплеку. Мы перезапускаем CI-сборки в надежде, что позже они все же будут пройдены, только для того, чтобы увидеть, как накапливаются отметки о прохождении, и неожиданно оказываемся у разбитого корыта.

Когда проявляются странности теста, важно приложить все силы, чтобы от него избавиться. В противном случае мы начнем терять веру в набор тестов, «который всегда так сбоит». Набор, содержащий тесты со странностями, может попасть под определение того, что Диана Воган (Diane Vaughan) называет нормализацией отклонения, то есть идеи, что со временем мы можем настолько привыкнуть к тому, что все идет не так, что примем это за нормальное, беспроблемное положение вещей[2]. Такое свойственное человеку восприятие действительности означает, что нам нужно выявить тесты со странностями и как можно скорее от них избавиться, не дожидаясь привыкания к тому, что тесты, проявляющие порой свой сбойный характер, нужно воспринимать как норму.

В работе Eradicating Non-Determinism in Tests Мартин Фаулер высказал свое отношение к тестам со странностями и сказал, что их нужно отслеживать и, если они не смогут быть немедленно исправлены, просто убирать из набора тестов. Посмотрите, нельзя ли их переписать таким образом, чтобы избежать выполнения тестируемого кода в нескольких потоках. Посмотрите, нельзя ли исходную среду сделать более стабильной. А еще лучше посмотрите, можно ли заменить тест со странностями тестом с меньшей областью действия, при использовании которого появление проблем менее вероятно. В некоторых случаях правильнее будет внести изменения в тестируемый код, упростив таким образом его тестирование.

Кто создает все эти тесты

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

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