Это тесты, помогающие нам, разработчикам, и поэтому в соответствии с терминологией, предложенной Мариком, должны иметь технологическую, а не бизнес-направленность. В ходе их проведения мы надеемся отловить основную часть своих ошибок. Итак, в нашем примере, когда мы занимаемся клиентским сервисом, блочные тесты будут охватывать небольшую изолированную часть кода (рис. 7.4).
Основной целью этих тестов является получение очень быстрых ответных результатов, говорящих о качестве функционирования кода. Тесты могут играть весьма важную роль в поддержке разбиения кода на части, позволяя проводить реструктуризацию кода по мере выполнения работы. При этом мы будем знать, что имеющие весьма ограниченную область действия тесты тут же нас остановят, если будет допущена ошибка.
Рис. 7.4. Область применения блочных тестов в нашей взятой для примера системе
Тесты сервиса разрабатываются для того, чтобы в обход пользовательского интерфейса выполнять непосредственное тестирование сервиса. В монолитном приложении могут тестироваться коллекции классов, предоставляющие сервис пользовательскому интерфейсу. В системе, содержащей несколько сервисов, тест сервиса используется для тестирования возможностей отдельного сервиса.
Причина, по которой требуется протестировать отдельно взятый сервис, состоит в улучшении изолированности теста с целью более быстрого обнаружения и устранения проблем. Для достижения изолированности нужно заглушить все внешние сотрудничающие компоненты, чтобы в область действия теста попадал только сам сервис (рис. 7.5).
Рис. 7.5. Область применения тестов сервиса в нашей взятой для примера системе
Некоторые из этих тестов могут выполняться так же быстро, как и небольшие тесты, но, если задумать тестирование с участием реальной базы данных или с переходом по сети к заглушенным нижестоящим сотрудничающим компонентам, время проведения теста может увеличиться. Кроме того, эти тесты имеют более широкую область действия, чем простой блочный тест, поэтому, если тест не будет пройден, причину окажется найти труднее, чем при проведении блочного теста. Тем не менее в их область действия попадает гораздо меньше активных компонентов, чем при проведении широкомасштабного тестирования, поэтому проходят проще.
Сквозные тесты проводятся в отношении всей системы. Зачастую управляют ими через графический интерфейс пользователя, имеющийся у браузера, но с возможностью без каких-либо затруднений имитировать другие виды взаимодействия с пользователем, например выкладывание файла.
Как показано на рис. 7.6, этими тестами охвачен большой объем кода, предназначенного для работы в производственном режиме. Следовательно, при прохождении этих тестов возникает удовлетворенность: с большой долей уверенности можно сказать, что протестированный код будет работать в производственном режиме. Но этой увеличенной области действия присущи некоторые недостатки, и, как мы вскоре увидим, в контексте микросервисов они могут иметь весьма запутанный характер.
Рис. 7.6. Область применения сквозных тестов в нашей взятой для примера системе
При изучении пирамиды ключевым моментом, который требуется усвоить, является то, что по мере движения к ее вершине область действия тестов увеличивается вместе с уверенностью в том, что протестированные функциональные возможности окажутся вполне работоспособными. Однако время ожидания отдачи от тестов увеличивается, поскольку времени на их выполнение уходит больше, и когда тест дает сбой, установить отказавшую функцию может быть намного сложнее. При движении сверху вниз по направлению к основанию пирамиды тесты выполняются, как правило, намного быстрее, поэтому мы получаем намного более быстрые циклы обратной реакции. Быстрее обнаруживаются отказавшие функции, быстрее создаются сборки непрерывной интеграции, и уменьшается вероятность того, что мы перейдем к другой задаче, прежде чем обнаружится, что мы что-нибудь вывели из строя. Когда сбой происходит при проведении таких имеющих весьма ограниченную область действия тестов, мы стремимся обнаружить место сбоя, зачастую вплоть до отдельной строки кода. В то же время, если тестируется отдельная строка кода, мы не получаем никакой уверенности в работоспособности системы в целом!
Когда сбой происходит при проведении тестов с широкой областью действия вроде тестов сервиса или сквозных тестов, мы стараемся написать быстрый блочный тест, для того чтобы в будущем с его помощью определить причину возникшей проблемы. Таким образом, мы постоянно пытаемся сократить время циклов обратной реакции.
Фактически каждая команда, с которой мне приходилось работать, по-разному называла то, что Кон использует в своей пирамиде. Но как бы все это ни называть, главное — понять, что вам понадобятся различные тесты, проводимые с разными целями.
Итак, если при проведении всех этих тестов не обойтись без компромиссов, то в каком объеме понадобится проведение каждого из них? Опыт показывает, что по мере продвижения по пирамиде сверху вниз понадобится, наверное, на порядок больше тестов, но при этом важно знать, что в вашем распоряжении имеется множество различных автоматизированных тестов, и чувствовать момент, когда текущий баланс того и другого превращается в серьезную проблему!
К примеру, мне приходилось участвовать в разработке одной монолитной системы, где у нас было 4000 блочных тестов, 1000 тестов сервисов и 60 сквозных тестов. Мы решили, что с точки зрения получения ответных результатов у нас было слишком много тестов сервисов и сквозных тестов (и последние были наиболее критикуемыми в плане продолжительности циклов получения ответов), поэтому упорно работали над переходом к тестам, имеющим менее широкую область действия.
Известным антишаблоном является то, что часто называют тестированием рожка с мороженым, или перевернутой пирамидой. Этот неверный подход характерен практическим отсутствием тестов с ограниченной областью действия, и все сводится к тестам, имеющим широкую область действия. Такие проекты зачастую характеризуются «замороженным» выполнением тестов и очень длинными циклами получения ответных результатов. Если такие тесты запускаются в рамках непрерывной интеграции, то количество получаемых сборок не будет большим и сроки, характерные для создания сборок, будут означать, что сами сборки в случае каких-либо сбоев могут находиться в нерабочем состоянии на протяжении весьма длительного времени.
В целом реализация блочных тестов дается намного проще всего остального, и существует множество различных документов, описывающих порядок их создания. Нам же намного интереснее будет разобраться в создании сервисных и сквозных тестов.
Тесты сервисов предназначены для тестирования той доли функциональности, которая распространяется на весь сервис, но, для того, чтобы изолироваться от других сервисов, нужно найти некий способ, позволяющий заглушить всех соучастников процесса. Следовательно, если нужно написать подобный тест для клиентского сервиса, показанного на рис. 7.3, следует развернуть экземпляр клиентского сервиса и, как уже говорилось, заглушить все взаимодействующие сервисы.
Первое, что нужно сделать при изготовлении сборки в рамках непрерывной интеграции, — создать для сервиса артефакт двоичного кода, поскольку его развертывание проводится довольно просто. Но как справиться с подделкой работы взаимодействующих сервисов?
Сервисный тестовый набор нуждается в запуске сервисов-заглушек для любого взаимодействующего с ним участника (или в гарантии того, что они запущены) и в настройке тестируемого сервиса на подключение к сервисам-заглушкам. Затем нужно настроить заглушки на отправку обратных ответов с целью имитации работы настоящих сервисов. Например, можно настроить заглушку банка бонусных баллов на возвращение заранее известного количества баллов, скопленных конкретными клиентами.
Когда речь заходит о создании заглушек взаимодействующих участников, подразумевается создание сервиса-заглушки, выдающей заранее заготовленные ответы на известные запросы того сервиса, который тестируется. Например, можно задать сервису-заглушке банка бонусных баллов при запросе баланса клиента 123 возвращать значение 15 000. Количество вызовов заглушки, будь то 0, 1 или 100, никакого влияния на прохождение теста не оказывает. Как вариант, вместо заглушки можно использовать имитатор.
При применении имитатора дело заходит чуть дальше и обеспечивается совершение вызова. Если ожидаемый вызов не сделан, тест считается непройденным. Реализация такого подхода требует более интеллектуального приема при создании имитации взаимодействующих сотрудников, а в случае слишком интенсивного использования имитатора могут возникнуть трудности при прохождении теста. А заглушку, как уже говорилось, можно вызывать 0, 1 и более раз.
Но иногда при желании получить ожидаемые побочные эффекты имитаторы могут принести существенную пользу. Например, мне может понадобиться проверить, что при создании нового клиента для него устанавливается новый остаток бонусных баллов. Соблюдение баланса между вызовами заглушек и имитаторов — дело тонкое и требующее серьезного отношения к себе при проведении как тестов сервисов, так и блочных тестов. Но фактически в тестах сервисов заглушки я использую значительно чаще имитаторов. Связанные с их использованием компромиссы более подробно рассматриваются в книге Стива Фримена (Steve Freeman) и Ната Прайса (Nat Pryce) Growing Object-Oriented Software, Guided by Tests (Addison-Wesley).
Вообще-то я редко использую имитаторы для тестов подобного рода. Но наличие инструментальных средств, способных быть как заглушками, так и имитаторами, считаю полезным.