Comments 9
Очень классная статья, прям чувствуется, что написана человеком, который реально этим пользуется, а не просто пересказывает доку. И видно, что большой труд проделан. Спасибо автору!
Есть вопрос. Насколько ваш репо готов к использованию в качестве образца «как надо» для LLM-моделей?
Вижу в некоторых местах есть комментарии (напр. AbstractIntegrationTest), и выглядит так, что они обеспечивают вариантивность (ИЛИ-ИЛИ). Потенциально хотел бы использовать при вайбкодинге, как opinionated репо со стилистикой.
P.S. Кстати, а под какой лицензией ваш код — можно ли его использовать в личных проектах?)
По первому вопросу - у меня мало опыта "натравливания" LLM на репозитории)) Наверно наличие фрагментов кода под комментами (ИЛИ-ИЛИ) действительно может создать некоторые затруднения. Но мне реально не хотелось делать две разные ветки, которые бы отличались буквально четырьмя строками кода и наличием/отсутствием одного-двух файлов. Я оставил все в одной куче специально, чтобы было сразу видно в чем собственно отличия. Наверно есть смысл форкнуть, удалить лишнее и уже после этого натравить LLM.
По второму вопросу - можно использовать в любых проектах.
Статья интересная, но полностью противоречит идеям Хорикова.
Возможно вы слишком категоричны. Можете развернуть комментарий?
Насколько я понял идеи Хорикова, он жестко разделяет слои доступа к базе данным и бизнес-логики. доступ к базе данных (и доступ к другим сервисам, если не путаю) необходимо реализовывать в виде "Простых объектов" - не содержащих бизнес-логики классов. Настолько простых, что там нечего тестировать. Что необходимо покрывать тестами - это расчеты, решения, бизнес-логику.
Если мы расчитываем продажи, то покрывать тестами надо классы, которые умножают количество проданного товара на цену, суммирует записи, и/или накладывает скидки и прочее по каким-то условиям. Вот этот класс, кроме как вычисляет, не должен обращаться к базе данных и прочим сервисам. Его и кроем тестами. Этот класс возвращает данные в виде простого объекта с полями и массивами чего-то. Данные возвращаются в "простой класс", который один к одному сохраняет данные в базу. Вот этот простой объект мы уже НЕ КРОЕМ тестами. Там нечего тестировать и нет смысла покрывать тестами.
Вот основная мысль Хорикова - мы разделяем класс на два класса - класс бизнес-логики, который кроем, и простой класс, сохраняющий в базу - который не кроем.
А вы кроете свои сложные классы (я знаю, что это такое, когда по ходу расчетов наш класс обращается к базе) тестами, прикручиваете прорву моков, которые на запросы к базе возвращают кучу данных и на методы сохранения в базу прикручиваете моки, которые проверяют, что вы пытаетесь сохранить а базу. Еще и пытаетесь наверное, протестировать, как sql-ки формируются.
Прекрасно понимаю, что это полная жопа.
И как решение, предлагаете тест-контейнеры... Они вам не помогут.
Апд. В идеальном случае, кстати, при таком подходе, результат расчета сохраняется в виде документа в NoSQL базу или в JSON-поле за один вызов.
Ок, давайте подискутируем. У Хорикова описывается довольно много различных подходов, а также их плюсы и минусы. Как мне показалось, одна из ключевых мыслей Хорикова - что необходимо уйти от хрупких тестов Лондонской школы, когда мокается все и вся и по возможности использовать тесты, которую тестируют ваше приложение по принципу "черного ящика" и тестируют не классы, а определенный функционал. Но повторюсь - в книге разобрано много кейсов и мой ключевой вывод это только мой вывод.
Я не ставил себе целью показать все подходы, описанные у Хорикова. Я упомянул его книгу потому что а) она реально очень крутая и б) в ней основная мысль про то, что тестировать "черный ящик" гораздо лучше, чем "белый ящик", если можно так выразиться.
Основная цель статьи - научить людей пользоваться тест-контейнерами и сделать это быстро. Из моего опыта чтения других туториалов - как правило все требуется дорабатывать напильником, часто очень и очень много, моя же цель была дать реально production-ready инструмент, чтобы можно было сосредоточиться на тестах, а не мудохаться неделю с первичной настройкой.
Теперь про подход.
Пишем интеграционные тесты на все happy path. С поднятием инфры через тест-контейнеры и так далее. Так ты точно знаешь, что все связки отрабатывают насквозь, по крайней мере по одному положительному сценарию. Это кстати уже обычно дает примерно 65-70% покрытия кода.
Затем по мере возможности ты выносишь бизнес-логику в отдельные методы. Цель - метод без внешних зависимостей, без внешних сервисов. Такие методы от и до тестируешь через юнит-тесты. Подобные тесты неплохо пишет ИИ, с обработкой кучи корнер-кейсов (только за ИИ обязательно все надо перепроверить). Таким образом прекрасно тестируется вся логика, если вам повезло практиковать DDD-подход. То есть если у тебя есть бизнес-логика без зависимостей, то ее все так же тестируем через юниты. Это очень годно работает для всяких библиотек, которые что-нибудь считают, или там регулярками обрабатывают. Там прямо юниты рулят.
Что касается тестирования простых объектов. Этого нет в докладе и коде, но да, объекты типа dto, репозитории и тому подобное необходимо вообще исключать из анализа тестового покрытия, включать их в исключения JaCoCo/Сонара. Это не вошло в реализацию и в доклад.
Что касается "прикручиваете прорву моков, которые на запросы к базе возвращают кучу данных и на методы сохранения в базу прикручиваете моки, которые проверяют, что вы пытаетесь сохранить а базу. Еще и пытаетесь наверное, протестировать, как sql-ки формируются." Тут уж извините, но вам стоит повнимательнее еще раз прочитать доклад и посмотреть код. В том то и дело, что я не прикручиваю моки. Я поднимаю реальную инфру, которая нужна для функционирования сервиса. Реальный Пострес, реальную Кафку, реальный Редис (тут его нет) и так далее. И потом тестирую максимально реальное поведение сервиса. Мне не надо тестировать "sql-ки формируются" потому что я тестирую конечный результат. Дал на вход определенные параметры, в рест или кафку, и жду определенного результата - появления/изменения данных в базе, исходящего сообщения в очередь, вызова внешнего сервиса... Исходя из того, что вы пишите, создается впечатление, что как в анекоде из СССР - "Пастернака не читал. Но осуждаю". Так же и вы невнимательно по диагонали прочитали статью и судя по всему не удосужились посмотреть код. Пожалуйста, дайте статье и коду второй шанс - приглядитесь. Возможно, вы в итоге до конца не согласитесь с моим подходом и реализацией, но совершенно точно ваша критика будет другой.
Спасибо за статью)
Не всегда Вам в pipeline дадут доступ к докеру. В этом случае могу порекомендовать Zonky который поднимет postgresQL в памяти без всяких контейнеров. Удобно и ощутимо быстрее чем с контейнерами.
Кроме того поголовное покрытие каждого метода интеграционными тестами приведет вас в Ад)) (был, видел) поэтому советую придерживаться пирамиды тестирования - много юнитов, чуть меньше модульных без контекста и мало интеграционных.
Я был в такой ситуации и с Zonky, он же EmbeddedPostgres, Embedded... работал.
По времени работы не соглашусь. Если вы присмотритесь, то обнаружите, что у него под капотом те же тестконтейнеры. Одним словом, шило на мыло в условиях корпоративных ограничений.
По покрытию. Посмотрите пожалуйста ответ на коммент выше, там где много букв. Я не предлагаю все методы покрывать интеграционниками. Достаточно happy path по всем сутевым кейсам. Юнит-тесты никто не отменял, через них прекрасно тестируется бизнес логика, если она не содержит зависимостей (DDD, утильные классы и библиотеки, просто отдельные методы с вынесенной в них логикой, ветвления с exception для повышения покрытия по параметру branch). Веду к тому, что и интеграционных тестов и юнитов должно быть ровно столько сколько нужно, чтобы покрыть все, что реально надо покрыть и что-бы эта история была устойчива к рефакторингу, насколько это возможно.
Внедряем Testcontainers за два дня или как перестать бояться рефакторинга и начать доверять своим тестам