Продолжая тему тестирования, сегодня речь о юнит-тестах.



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



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



Юнит-тесты можно условно поделить на те, что тестируют состояние объекта и те, что тестируют поведение/взаимодействие, используя тест-дублеры.



Первые находятся в центре дизайна, слое доменной логики. Его особенность в том, что если удалось провести качественное проектирование на уровне предметной области, то изменения в него будут вносится все реже и реже(и, скорее всего, точечно), — это стабильная область (в отличие, например, от UI). Следствие из этого — код и нюансы его работы будут забываться, тесты станут стабильнее и выступать в том числе актуальной документацией, а их запуск в рамках CI является формальным подтверждением того, что в сборку не попал регрес.



Вторая категория, — на уровне портов и адаптеров и здесь используются тесты с использованием тест-дублеров. Это другой слой стратегии тестирования. В нем проверяется любая логика, подготавливающая запросы или обрабатывающая ответы от внешних систем (в том числе баз данных), используя вместо них тест-дублеры. Это позволяет обспечить надежную, быструю и повторяемую проверку циклов «запрос-ответ».



Если планируется переход от монолита к микросервисам, то юнит-тесты с использованием дублеров могут разделиться на две части. Чтобы лучше это понять, следует обратиться к шаблону «Микросервисное шасси/Фреймворк микросервиса» (Microservices Chassis - https://microservices.io/patterns/microservice-chassis.html) — от вынесение общей инфраструктурной функциональности в отдельный фреймворк. Тесты общего назначения могут уйти в кодовую базу фреймворка и поддерживаться отдельно, что снижает общую сложность управления тестами и сокращает время их выполнения (в CI сервиса выполняются юнит-тесты сервиса, а юнит тесты фреймворка выполняются в своем CI).



В сухом остатке имеем набор микросервисов, каждый со своим набором юнит-тестов, выполняющихся в собственном пайплайне, часть из которых может быть вынесена в отдельный пайплайн микросервисного фреймворка. В рамках пайплайна тесты на домен более стабильные, чем тесты портов и адаптеров и мы держим их отдельно (не смешивая в коде самих тестов), чтобы не привнести более низкую стабильность в тесты доменной логики.



Иногда код настолько плох, что юнит-тест не пишется, с какой стороны к нему не подойди. Был случай, когда команде буквально приказали начать писать юнит-тесты, которых никто никогда не писал, а системе (это была java) больше 10 лет. За два месяца не было написано ни одного теста. Это тот случай, когда типовая стратегия тестирования для greefield не подходит и следует рассмотреть альтернативы. О них напишу после еще нескольких постов о видах тестирования в применимости к микросервисам (хотя, это актуально вне зависимости от архитектурного стиля).