Согласен, интересный вариант, и в таком виде его можно масштабировать. Но тогда получается, что у нас есть тесты, которые проверяют PromoService.calculateDiscount, promoService.validatePromo итд., что, по идее, определяет их как единицу модуля. Но тогда DiscountServiceTest использует несколько модулей, т.е. строго из определения - это интеграционный тест, который мы почему-то называем юнитом (ну, или считаем, что каждый тест сам определяет, что для него юнит... Но как при этом не сказать, что e2e у нас тоже юнит - не понятно).
Более того, это лишает нас возможности установить четкие правила и границы, когда нужно писать тест, а когда нет (фактически, на усмотрение разработчика, что не надежно и заставляет думать, а не просто действовать по инструкции). Ну, по крайней мере, пока мы не начнем возводить атомарность такого тестирования PromoService.calculateDiscount, promoService.validatePromo и других компонент в абсолют. Но это как раз соответствует тому, что "каждый класс нужно тестировать в изоляции" плюс есть какие-то тесты (пусть мы их тоже будем называть юнитами, не важно), у которых скоуп больше.
Еще один момент в том, что если где-то все же проскакиевает ошибка, у нас падает не 1 тест, который показывает "ошибка вот здесь" (например promoService.validatePromo), а так же падают все тесты (в данном контексте DiscountServiceTest), в которых он как-то используется. Если кто-то еще будет использовать DiscountServiceTest или PromoService.calculateDiscount то он тоже упадет... И те, кто использует их по каскаду. Как в этой толпе сваленных тестов с ходу понять, что смотреть нужно вот сюда - не особо понятно. Можно конечно открыть все 20 файлов, посмотреть, его поля, какие методы вызываются, и найти, но кажется, это не очень удобно.
есть изолированное поведение - подсчет скидки по промокоду
Да, когда у нас демонстрационный или простой и понятный пример - так и происходит. С ростом же бизнес логики, в реальных условиях, это вполне нормальное явление, что количество шагов, чтоб выполнить некоторое действие увеличивается. И тогда перед нами встает выбор: мы либо начинаем атомизировать понятие "модуль" (которое и определяет, что мы называем юнитом, а где уже интеграционный тест), чем в итоге возвращаемся обратно к лондонской школе, либо принимаем, что у нас вот такая бизнес логика и никуда мы от неё не денемся. И нам нужно посмотреть эти 20 файлов (пусть и по 5 строк кода)
Вы можете, глядя на тест, обсудить поведение с аналитиком/продакт-менеджером.
Нууу... Может и можно, а надо ли? Если меня интересует название, входные и выходные данные, то можно что-то ближе к e2e-ам взять (заодно будем уверены, что и апи при этом отрабатывает, и в бд все нормально записывается/достается, и с внешними системами интегрировались, а не только "у нас тут какой-то код в вакууме"). А если прямо обсуждать логику, то это уже в сторону BDD. Для юнита, как-то перебор на мой взгляд.
это просто разные типы тестов. Понятно, что написать какой-то интеграционный тест, который проверит пол бизнес логики проще, чем писать тест на каждый класс. Но какого будет искать потом по всей бизнес логики, а что именно пошло не так? (особенно, если этим занимается человек, который не особо погружен в то, как эта система должна работать).
① был вызван метод проверки, надо ли применять промо ② затем был (либо не был) вызван метод применения с корректными параметрами ③ ну и всякое такое.
Т.е. фактически ты не тестируешь код. Ты тестируешь, что ты написал свой метод так, как ты его написал. И надеешься, что никогда не придется здесь хоть что-то менять, потому что вместе с этим сразу нужно переписывать большую часть тестов. Это элемент хрупкости, о которой сказано выше.
И да, не смотря на то, что статья написана именно так, не надо возводить никакое мнение в абсолют. Я не предлагаю сейчас всем придти и выпилить все моки из всех своих рабочих проектов. Моки - стандарт де-факто, спорить с этим глупо. И естественно они повсеместно используются. Цель статьи еще раз подсветить эти проблемы и рассказать об альтернативах. Да, в своеобразной форме. Но именно такая форма может донести смысл, в отличии от попыток подбирать какие-то комформистские нейтральные позиции, постоянные дисклеймеры, что думайте своей головой и прочее.
Идея в том, что фейк является полноценной (пусть и очень простой) имплементацией.
Если вдруг изменился вызываемый метод того же класса (который уже есть в интерфейсе), то фейк продолжит работать.
Если изменилась сигнатура метода - упадет билд с явной ошибкой, которую нужно поправить в 1 месте - фейке и она продолжит работать со всеми тестами.
Всё ровно так, как и в ситуации с моком.
Ну как продолжит? В моке нужно явно указать сигнатуру вызываемого метода и поддерживать его на постоянной основе. И да, опять же, некоторые (!) мок фреймворки, или если разобраться и их правильно сконфигурировать, хотя бы честно скажут, что вот тут ожидался вызов этого и не вызвался, а вот тут попытались вызвать вот это, но мок не сконфигурирован... А есть, например, mockito, которая будет возвращать дефолтный респонс. Да, где-то там, ниже, у тебя может быть проверка, что каждый мок вызван, столько-то раз, с такими-то аргументами (опять же, не всегда), но чаще там где-то null залетит, как-то пролетит через пол теста, а потом вдруг NPE, или какая-то другая ошибка из-за этого null. И иди ищи от куда он взялся. Не используется половина моков? Ну и ладно, бывает. А у тебя это мертвый код, который усложняет восприятие этого теста... Да, можно донастроить, учитывать нюансы, но какого черта об этом нужно думать? В больших проектах и так большая сложность. От лишней когнитивной нагрузки нужно избавляться, а не пытаться ее нормализовывать.
Юнит-тест и спринговый контекст...
А кто говорил о поднятии всего спрингового контекста?
Тестировать надо всё, а не половину.
В норме, количество юнит тестов, для которых этот контекст не нужен - огромно. Интеграционных, где можно говорить о тестировании более значительного объема кодобазы - гораздо меньше.
Да щас, дёргал я. Ответ был один: разбирайся. Тогда я этот подход не понял, но потом заметил этот паттерн и дальше...
- А что будет, если...
- Пробуй
- А если...
- Пробуй
- Тут какая-то фигня, не понятно
- Разбирайся
Естественно, речь не шла о таком походе, когда запара была серьёзная: лежит прод, клиенты жалуются итд...
Но ты раз что-то предложил, попробовал, не получилось. Второй раз. Третий раз... Зато ты не боишься этого. Не учишься ни "ваше мнение тут никого не интересует" ни "инициатива сношает инициатора". Нет, ты попробовал, получил какой-то результат, понял, идём дальше. Никто в тебя пальцем не тыкает... Что-то тут не понятное - идём и разбираемся.
И по итогу, когда через года ты находишься перед выбором "а что если попробовать...", когда у тебя по факту может получиться, когда эти решения, собранные из идеи на коленке за несколько часов может решить серьёзные проблемы, позволяя бизнесу двигаться дальше - у тебя не будет вопроса "стоит ли пробовать?", при том, что может там придётся потыкаться и ни к чему не придти по итогу или лучше заткнуться и сидеть не высовываться.
А фейковые заглушки, предлагаемые вами в статье, не на этом основаны?
Нет. Я изначально надеялся только зафиксировать идею, но видимо придется показывать в отдельной части... Идея в том, что фейк является полноценной (пусть и очень простой) имплементацией. И тогда получаем систему, в которой
Если вдруг изменился вызываемый метод того же класса (который уже есть в интерфейсе), то фейк продолжит работать.
Если изменилась сигнатура метода - упадет билд с явной ошибкой, которую нужно поправить в 1 месте - фейке и она продолжит работать со всеми тестами.
Если изменился сам используемый класс - опять же, это явно можно будет увидеть падающим билдом с нормальной ошибкой
Да, при этом возникает вопрос, что "не хочу я какой нибудь репозиторий реализовывать со всеми его методами"... Но оно и не надо. Гранулярность интерфейсов - штука достаточно важная и ее можно поддерживать.
Почему эти объекты просто не создать через new с нужными значениями атрибутов?
Отличный вопрос. В целом, фикстура так и делает. Но в одном месте и с набором дефолтных параметров. Соответственно, когда у тебя изменяются параметры в этих объектах, нам нужно исправить это в самой фикстуре + в тех тестах, где они явно использовались, а не лазать по множеству мест копипастом расставляя параметры, которые вообще не значимы для этого теста и получать мр-ы, где реальных изменений на пару десяток строк, и сотни изменений в тестах, с параметрами.
Здесь в моке попытка в похожий подход, чтоб не приходилось изменять это лишний раз. И опять же, объект может быть достаточно сложный, с множеством параметров и целой простыней кода, которые нужно для этого написать. А потом такой объект становится не 1.
Моки в тестах должны принадлежать к одному слоою. А тут, очевидно, и слой хранения, и слой бизнес-логики.
Хороший вертикальный слайсинг в рамках фичи - конечно, штука хорошая... Вот только на практике, почти невозможная, когда логика становится сложнее чем круд. Да, в теории, можно собрать слой абстракции наверху, который будет "более правильно" координировать эти задачки, чтоб запросы аккуратно и красиво шли только в следующий слой... И понадеется, что нам не понадобится через какое-то время еще слой абстракции, чтоб как-то запросы в этот слой координировать и не заниматься при этом копипастой... Ну, или у нас юнит тест становится не юнитом, а пол слоя тестирует...
Моки - это в принципе костыли для тестов.
Это конечно костыль, но это наш костыль... И в целом то, ничего в этом сильно плохого нет... Пока такие костыльные решения не называют нормой. Да, вариант с фейками более развесистый, там придется много кода своего писать, а не навешать аноташку. Соответственно, на старте это дольше. Там какие-то свои имплементации, в которых тоже баги могут быть. Да, это не привычно. Но, набор проблем он не из воздуха взят, как и набор костылей (и надежд), чтоб это все таки как-то заработало. Но на сколько бы спорным не был описанный вариант, он своеобразно, но тем не менее, пытается их решить (и на мой взгляд, у него не плохо получается)
Не все проблемы. Только с просачиванием моков. Который, ещё раз, нужно везде написать...
Это было скорее введение, с которого началось наблюдение, а не единственная проблема. Проблема же, что они создают очень хрупкую тестовую систему основную скорее на вайтбоксе. Т.е. больше часть теста, на самом деле, не проверка выполнения, а проверка, что моки правильно сконфигурированы.
Опять же, вот эти перезапуски в разных контекстах, моки в бейс классе, обязательный afterEach/beforeEach... это точно показатель, что с тестами все в порядке? Выглядит как набор костылей, по которому нужно пройтись, чтоб это все более менее адекватно работало (нет), а не как надёжная адекватная система as-is
В следующих частях) Добавить параметры к тесту - это уже больше чем-то на учебник для самых маленьких похоже, а не на сборник статей, где я пытаюсь систематизировать свой опыт и идеи
Параметризированные тесты не вчера придуманы. Я... Как-то надеялся по верхам быстрее проскочить к чему то более интересному, а не дублировать то, что и так легко ищется по первой вкладке гугла
В первом примере у вас тесты с моками влезли на один экран. Во втором - на два, и это без определения всех зависимостей
Да, но там и 2 теста, когда в 1 примере лишь один. Согласен, фейки, фикстуры ещё нужно написать, это тоже занимает место. Да, больше кода. Больше адекватного привычного нам кода который ведёт себя так, как от него это ожидают.
Кривые моки и грязный тестовый код - это технический долг.
В чем-то согласен. Проблем действительно можно избежать "если делать все правильно", "хорошо прочитать доку (и релиз ноутсы на каждый апдейт) и нигде ничего не пропустить" и вообще "просто уделяя какое-то время дизайну тестового кода". Вот только "нигде ничего не забыть" легко пропустить, а исправлять ситуацию не так тривиально. Ещё видел примеры, где по аналогии с фикстурами пытались делать человеческие конфигурации моков... Но где-то кроме пары демонстрационных примеров в сети я этого не видел. И ключевая проблема: как не пытайся настраивать моки, с ними нельзя работать как с чёрным ящиком. Нет, укажи конкретно, на какую сигнатуру запроса ты хочешь свой ответ. И если где-то меняется хоть что-то (от используемого метода до некоторого дополнительного параметра), вместо того, чтоб исправить это при необходимости в одном месте, в случае изменения сигнатуры, и написать один тест, который это проверяет, мы должны пройти по всем тестам, где он используется в попытке все исправить (что далеко не всегда нужно)
Сколько времени займёт "причёсывание" одного экрана с моками и четырёх экранов с самописными фейками при необходимости?
Никто ведь и не спорит. Моки на старте, в режиме "один раз написал, забыл и надеешься, что тебе не придётся в этом разбираться" быстрее. А вот с поддержкой... Я бы не сказал. С ростом сложности и моков, поддерживать это куда сложнее, в то время как в фейках большая часть работы легко выносится в довольно простой дженерик, и из собственной реализации только матчеры (при необходимости) или простые и говорящие верифай методы. Там просто нечему становиться сложным
1) как будет работать маппер не entity/dto, что довольно примитивно само по себе и тоже не является хорошей практикой, а связка dto-доменный объект (агрегирующий другие доменные объекты и объекты-значения)-entity.
2) моделька используется локальная? Или пользовательская кодобаза таки утекает через такой плагин?
Или по доменам: весь код, относящийся к конкретному объекту отправляем в один пакет Это называется либо вертикальный слайсинг, либо разделение bounded контекстов в зависимости от используемого подхода. "По доменам" это называть не совсем правильно. Домен - он один. В рамках баундед контекста выделяется часть домена выделяется в необходимую для сервиса доменную модель
Как следствие, внедрение новых сотрудников занимает гораздо больше времени, чем могло бы. Так могло бы быть, в случае идеальной картины мира, когда разработчики представляют, что такое гексагональные типы агрикультур и как их готовить. На практике, у нас это очень редкое явление, в то время как про трехслойку знают все. И обычно онбординг из-за этого занимает все же больше времени. Другой вопрос, что когда ты уже разобрался с той интерпретаций соответствующего подхода в команде, то разобраться в следующем сервисе действительно становится проще.
В директории гексагонального проекта вы увидите такую структуру... 1) Нет, не увидите. Это уже гораздо более поздние интерпретации. Как вариант луковичная или чистая. Гексагональная архитектура говорила о физическом разделении адаптеров. Каждый адаптер поставлялся в отдельном джарнике, в то время, пока аппликейшн завязана на порты. И это имело строго техническое применение. На дворе, сюрприз, 2005 год. Спринг только год назад в релиз вышел, про ваши девопсы никто даже не слышал. Скорости инета не такие большие. Люди работали на ee стеке. Собирали нужные джарки, по какому нибудь фтп/ссх закидывали их на серв где крутится большой и страшный аппликейшн сервер и благодаря такому подходу могли хот релоуднуть нужный адаптер заменив на новый, в то время как остальное приложение фактически продолжало работать... Идея, что тот же подход можно использовать и в рамках одного проекта появилась на несколько лет позже. Всякие спринги с идеей тупо поднять отдельный сервлет контейнер и перенаправить на него трафик вместо этих сложностей с ее серверами к тому времени активно набирало популярность. И собственно луковичная архитектура как раз об этом. Да, мы не делаем хот релоуды, да, у нас сейчас одна запакованная джарка и все хорошо... Но идея, что во главе стоит бизнес логики, а не контроллеры с репозиториями - здравая и давайте продолжать ее использовать... 2) 1 common и на application и на адаптер... Даже не знаю, что кроме портов с их дто-шками туда можно положить. Но тогда почему бы их так не назвать? Exception-ы ещё, но они обычно тоже отдельно как-то отдельно выносят. Но судя по схеме порты лежат в аппликейшне... Но тогда я совсем не понимаю, что там находится. Они разные и шарить между собой ничего не должны.
| ├── domain/ │ │ ├── model/ │ │ └── service/ Да откуда вы блин все это берёте? На тех схемах, на которые вы же и ссылаетесь домен лежит внутри аппликейшн слоя, показывая, что есть доменная модель, и на базе ее строится аппликейшн. Таким помещением в рамках внутреннего слоя подразумевалось, что домен, как самая внутренняя часть проекта, его сердце, ни на кого не зависит а обозначает сущности, существующие (простите за тавтологию) вне зависимости от существования твоего приложения. Т.е. если бы его не было, все равно люди бы бегали вокруг и на условных бумажках это все делали... А аппликейшн слой - то уровень логики твоего приложения, которая, естественным образом зависит от домена... Что блин это за попытки переложить бл в домен? Причём повальная...
Но простого способа понять утекли ли зависимости я не нашёл Плохо искал. Закон протекающих абстракций не вчера был придуман и пока это были физически разные проекты, которые просто не имели зависимостей друг на друга и ты не мог обратиться к спринговому классу в бизнес логике просто потому, что спринга там не было, то с луковой архитектурой эта проблема уже появилась... И ее учились решать. Сначала каким-то самописными системами проверки, потом появился ArchUnit... и кстати, идея о том, что бл не должна зависеть от фреймворков сформирована только уже только в чистой архитектуре, первые заметки о которой были в 12 году... Быстро вы 7 лет эволюции конечно :D
На это нужно обращать внимание при ревью нового кода. Такая себе идея. Во время ревью кода нужна верхнеуровневая проверка. Автоматика не знает особенностей ваших систем и ревьюеры проверяют, можно ли делать то, что ты делаешь. А проверять стили/зависимости, наличие всяких очевидных плохих практик и пр. должна автоматика.
Гексагональная модель пушит нас для ядра приложения использовать свою внутреннюю модель. Это не гексагональная модель пушит... Люди и наигравшись со слоенкой приходят к тому, что это не адекватно, что когда у тебя меняется апи - падает работа с бд, потому что какое-то поле по другому завется... Но почему? Я ведь апи меняю... А если тебе придётся на бд другую съезжать, а если вдруг у этих бд окажется нужно по разному схемы строить потому что какие-то свои особенности... Не, я понимаю, что конвертеры уже наплохокодили, но... Если уже наступили в лужу и какие-то костыли заиспользовали - окей, но может лучше не наступать?...
Согласен, интересный вариант, и в таком виде его можно масштабировать. Но тогда получается, что у нас есть тесты, которые проверяют
PromoService.calculateDiscount,promoService.validatePromoитд., что, по идее, определяет их как единицу модуля. Но тогдаDiscountServiceTestиспользует несколько модулей, т.е. строго из определения - это интеграционный тест, который мы почему-то называем юнитом (ну, или считаем, что каждый тест сам определяет, что для него юнит... Но как при этом не сказать, что e2e у нас тоже юнит - не понятно).Более того, это лишает нас возможности установить четкие правила и границы, когда нужно писать тест, а когда нет (фактически, на усмотрение разработчика, что не надежно и заставляет думать, а не просто действовать по инструкции). Ну, по крайней мере, пока мы не начнем возводить атомарность такого тестирования
PromoService.calculateDiscount,promoService.validatePromoи других компонент в абсолют. Но это как раз соответствует тому, что "каждый класс нужно тестировать в изоляции" плюс есть какие-то тесты (пусть мы их тоже будем называть юнитами, не важно), у которых скоуп больше.Еще один момент в том, что если где-то все же проскакиевает ошибка, у нас падает не 1 тест, который показывает "ошибка вот здесь" (например
promoService.validatePromo), а так же падают все тесты (в данном контекстеDiscountServiceTest), в которых он как-то используется. Если кто-то еще будет использоватьDiscountServiceTest или PromoService.calculateDiscountто он тоже упадет... И те, кто использует их по каскаду. Как в этой толпе сваленных тестов с ходу понять, что смотреть нужно вот сюда - не особо понятно. Можно конечно открыть все 20 файлов, посмотреть, его поля, какие методы вызываются, и найти, но кажется, это не очень удобно.Да, когда у нас демонстрационный или простой и понятный пример - так и происходит. С ростом же бизнес логики, в реальных условиях, это вполне нормальное явление, что количество шагов, чтоб выполнить некоторое действие увеличивается. И тогда перед нами встает выбор: мы либо начинаем атомизировать понятие "модуль" (которое и определяет, что мы называем юнитом, а где уже интеграционный тест), чем в итоге возвращаемся обратно к лондонской школе, либо принимаем, что у нас вот такая бизнес логика и никуда мы от неё не денемся. И нам нужно посмотреть эти 20 файлов (пусть и по 5 строк кода)
Нууу... Может и можно, а надо ли? Если меня интересует название, входные и выходные данные, то можно что-то ближе к e2e-ам взять (заодно будем уверены, что и апи при этом отрабатывает, и в бд все нормально записывается/достается, и с внешними системами интегрировались, а не только "у нас тут какой-то код в вакууме"). А если прямо обсуждать логику, то это уже в сторону BDD. Для юнита, как-то перебор на мой взгляд.
это просто разные типы тестов. Понятно, что написать какой-то интеграционный тест, который проверит пол бизнес логики проще, чем писать тест на каждый класс. Но какого будет искать потом по всей бизнес логики, а что именно пошло не так? (особенно, если этим занимается человек, который не особо погружен в то, как эта система должна работать).
Т.е. фактически ты не тестируешь код. Ты тестируешь, что ты написал свой метод так, как ты его написал. И надеешься, что никогда не придется здесь хоть что-то менять, потому что вместе с этим сразу нужно переписывать большую часть тестов. Это элемент хрупкости, о которой сказано выше.
И да, не смотря на то, что статья написана именно так, не надо возводить никакое мнение в абсолют. Я не предлагаю сейчас всем придти и выпилить все моки из всех своих рабочих проектов. Моки - стандарт де-факто, спорить с этим глупо. И естественно они повсеместно используются. Цель статьи еще раз подсветить эти проблемы и рассказать об альтернативах. Да, в своеобразной форме. Но именно такая форма может донести смысл, в отличии от попыток подбирать какие-то комформистские нейтральные позиции, постоянные дисклеймеры, что думайте своей головой и прочее.
Может все же будут какие-то замечания по существу?
🤣.
Даже приятно, что за меня так переживают. Не волнуйтесь, как нибудь справлюсь 🤣🤣🤣
Ну как продолжит? В моке нужно явно указать сигнатуру вызываемого метода и поддерживать его на постоянной основе. И да, опять же, некоторые (!) мок фреймворки, или если разобраться и их правильно сконфигурировать, хотя бы честно скажут, что вот тут ожидался вызов этого и не вызвался, а вот тут попытались вызвать вот это, но мок не сконфигурирован... А есть, например, mockito, которая будет возвращать дефолтный респонс. Да, где-то там, ниже, у тебя может быть проверка, что каждый мок вызван, столько-то раз, с такими-то аргументами (опять же, не всегда), но чаще там где-то null залетит, как-то пролетит через пол теста, а потом вдруг NPE, или какая-то другая ошибка из-за этого null. И иди ищи от куда он взялся. Не используется половина моков? Ну и ладно, бывает. А у тебя это мертвый код, который усложняет восприятие этого теста... Да, можно донастроить, учитывать нюансы, но какого черта об этом нужно думать? В больших проектах и так большая сложность. От лишней когнитивной нагрузки нужно избавляться, а не пытаться ее нормализовывать.
А кто говорил о поднятии всего спрингового контекста?
В норме, количество юнит тестов, для которых этот контекст не нужен - огромно. Интеграционных, где можно говорить о тестировании более значительного объема кодобазы - гораздо меньше.
Да щас, дёргал я. Ответ был один: разбирайся. Тогда я этот подход не понял, но потом заметил этот паттерн и дальше...
Естественно, речь не шла о таком походе, когда запара была серьёзная: лежит прод, клиенты жалуются итд...
Но ты раз что-то предложил, попробовал, не получилось. Второй раз. Третий раз... Зато ты не боишься этого. Не учишься ни "ваше мнение тут никого не интересует" ни "инициатива сношает инициатора". Нет, ты попробовал, получил какой-то результат, понял, идём дальше. Никто в тебя пальцем не тыкает... Что-то тут не понятное - идём и разбираемся.
И по итогу, когда через года ты находишься перед выбором "а что если попробовать...", когда у тебя по факту может получиться, когда эти решения, собранные из идеи на коленке за несколько часов может решить серьёзные проблемы, позволяя бизнесу двигаться дальше - у тебя не будет вопроса "стоит ли пробовать?", при том, что может там придётся потыкаться и ни к чему не придти по итогу или лучше заткнуться и сидеть не высовываться.
Нет. Я изначально надеялся только зафиксировать идею, но видимо придется показывать в отдельной части...
Идея в том, что фейк является полноценной (пусть и очень простой) имплементацией. И тогда получаем систему, в которой
Если вдруг изменился вызываемый метод того же класса (который уже есть в интерфейсе), то фейк продолжит работать.
Если изменилась сигнатура метода - упадет билд с явной ошибкой, которую нужно поправить в 1 месте - фейке и она продолжит работать со всеми тестами.
Если изменился сам используемый класс - опять же, это явно можно будет увидеть падающим билдом с нормальной ошибкой
Да, при этом возникает вопрос, что "не хочу я какой нибудь репозиторий реализовывать со всеми его методами"... Но оно и не надо. Гранулярность интерфейсов - штука достаточно важная и ее можно поддерживать.
Отличный вопрос. В целом, фикстура так и делает. Но в одном месте и с набором дефолтных параметров. Соответственно, когда у тебя изменяются параметры в этих объектах, нам нужно исправить это в самой фикстуре + в тех тестах, где они явно использовались, а не лазать по множеству мест копипастом расставляя параметры, которые вообще не значимы для этого теста и получать мр-ы, где реальных изменений на пару десяток строк, и сотни изменений в тестах, с параметрами.
Здесь в моке попытка в похожий подход, чтоб не приходилось изменять это лишний раз. И опять же, объект может быть достаточно сложный, с множеством параметров и целой простыней кода, которые нужно для этого написать. А потом такой объект становится не 1.
Хороший вертикальный слайсинг в рамках фичи - конечно, штука хорошая... Вот только на практике, почти невозможная, когда логика становится сложнее чем круд. Да, в теории, можно собрать слой абстракции наверху, который будет "более правильно" координировать эти задачки, чтоб запросы аккуратно и красиво шли только в следующий слой... И понадеется, что нам не понадобится через какое-то время еще слой абстракции, чтоб как-то запросы в этот слой координировать и не заниматься при этом копипастой... Ну, или у нас юнит тест становится не юнитом, а пол слоя тестирует...
Это конечно костыль, но это наш костыль... И в целом то, ничего в этом сильно плохого нет... Пока такие костыльные решения не называют нормой.
Да, вариант с фейками более развесистый, там придется много кода своего писать, а не навешать аноташку. Соответственно, на старте это дольше. Там какие-то свои имплементации, в которых тоже баги могут быть. Да, это не привычно. Но, набор проблем он не из воздуха взят, как и набор костылей (и надежд), чтоб это все таки как-то заработало. Но на сколько бы спорным не был описанный вариант, он своеобразно, но тем не менее, пытается их решить (и на мой взгляд, у него не плохо получается)
Не все проблемы. Только с просачиванием моков. Который, ещё раз, нужно везде написать...
Это было скорее введение, с которого началось наблюдение, а не единственная проблема. Проблема же, что они создают очень хрупкую тестовую систему основную скорее на вайтбоксе. Т.е. больше часть теста, на самом деле, не проверка выполнения, а проверка, что моки правильно сконфигурированы.
Опять же, вот эти перезапуски в разных контекстах, моки в бейс классе, обязательный afterEach/beforeEach... это точно показатель, что с тестами все в порядке? Выглядит как набор костылей, по которому нужно пройтись, чтоб это все более менее адекватно работало (нет), а не как надёжная адекватная система as-is
В следующих частях)
Добавить параметры к тесту - это уже больше чем-то на учебник для самых маленьких похоже, а не на сборник статей, где я пытаюсь систематизировать свой опыт и идеи
Параметризированные тесты не вчера придуманы. Я... Как-то надеялся по верхам быстрее проскочить к чему то более интересному, а не дублировать то, что и так легко ищется по первой вкладке гугла
Да, но там и 2 теста, когда в 1 примере лишь один. Согласен, фейки, фикстуры ещё нужно написать, это тоже занимает место. Да, больше кода. Больше адекватного привычного нам кода который ведёт себя так, как от него это ожидают.
В чем-то согласен. Проблем действительно можно избежать "если делать все правильно", "хорошо прочитать доку (и релиз ноутсы на каждый апдейт) и нигде ничего не пропустить" и вообще "просто уделяя какое-то время дизайну тестового кода". Вот только "нигде ничего не забыть" легко пропустить, а исправлять ситуацию не так тривиально. Ещё видел примеры, где по аналогии с фикстурами пытались делать человеческие конфигурации моков... Но где-то кроме пары демонстрационных примеров в сети я этого не видел. И ключевая проблема: как не пытайся настраивать моки, с ними нельзя работать как с чёрным ящиком. Нет, укажи конкретно, на какую сигнатуру запроса ты хочешь свой ответ. И если где-то меняется хоть что-то (от используемого метода до некоторого дополнительного параметра), вместо того, чтоб исправить это при необходимости в одном месте, в случае изменения сигнатуры, и написать один тест, который это проверяет, мы должны пройти по всем тестам, где он используется в попытке все исправить (что далеко не всегда нужно)
Никто ведь и не спорит. Моки на старте, в режиме "один раз написал, забыл и надеешься, что тебе не придётся в этом разбираться" быстрее. А вот с поддержкой... Я бы не сказал. С ростом сложности и моков, поддерживать это куда сложнее, в то время как в фейках большая часть работы легко выносится в довольно простой дженерик, и из собственной реализации только матчеры (при необходимости) или простые и говорящие верифай методы. Там просто нечему становиться сложным
Из статьи не увидел некоторых моментов:
1) как будет работать маппер не entity/dto, что довольно примитивно само по себе и тоже не является хорошей практикой, а связка dto-доменный объект (агрегирующий другие доменные объекты и объекты-значения)-entity.
2) моделька используется локальная? Или пользовательская кодобаза таки утекает через такой плагин?
Или по доменам: весь код, относящийся к конкретному объекту отправляем в один пакет
Это называется либо вертикальный слайсинг, либо разделение bounded контекстов в зависимости от используемого подхода. "По доменам" это называть не совсем правильно. Домен - он один. В рамках баундед контекста выделяется часть домена выделяется в необходимую для сервиса доменную модельКак следствие, внедрение новых сотрудников занимает гораздо больше времени, чем могло бы.Так могло бы быть, в случае идеальной картины мира, когда разработчики представляют, что такое гексагональные типы агрикультур и как их готовить. На практике, у нас это очень редкое явление, в то время как про трехслойку знают все. И обычно онбординг из-за этого занимает все же больше времени. Другой вопрос, что когда ты уже разобрался с той интерпретаций соответствующего подхода в команде, то разобраться в следующем сервисе действительно становится проще.
В директории гексагонального проекта вы увидите такую структуру...1) Нет, не увидите. Это уже гораздо более поздние интерпретации. Как вариант луковичная или чистая. Гексагональная архитектура говорила о физическом разделении адаптеров. Каждый адаптер поставлялся в отдельном джарнике, в то время, пока аппликейшн завязана на порты. И это имело строго техническое применение. На дворе, сюрприз, 2005 год. Спринг только год назад в релиз вышел, про ваши девопсы никто даже не слышал. Скорости инета не такие большие. Люди работали на ee стеке. Собирали нужные джарки, по какому нибудь фтп/ссх закидывали их на серв где крутится большой и страшный аппликейшн сервер и благодаря такому подходу могли хот релоуднуть нужный адаптер заменив на новый, в то время как остальное приложение фактически продолжало работать... Идея, что тот же подход можно использовать и в рамках одного проекта появилась на несколько лет позже. Всякие спринги с идеей тупо поднять отдельный сервлет контейнер и перенаправить на него трафик вместо этих сложностей с ее серверами к тому времени активно набирало популярность. И собственно луковичная архитектура как раз об этом. Да, мы не делаем хот релоуды, да, у нас сейчас одна запакованная джарка и все хорошо... Но идея, что во главе стоит бизнес логики, а не контроллеры с репозиториями - здравая и давайте продолжать ее использовать...
2) 1 common и на application и на адаптер... Даже не знаю, что кроме портов с их дто-шками туда можно положить. Но тогда почему бы их так не назвать? Exception-ы ещё, но они обычно тоже отдельно как-то отдельно выносят. Но судя по схеме порты лежат в аппликейшне... Но тогда я совсем не понимаю, что там находится. Они разные и шарить между собой ничего не должны.
|
├── domain/│ │ ├── model/
│ │ └── service/
Да откуда вы блин все это берёте? На тех схемах, на которые вы же и ссылаетесь домен лежит внутри аппликейшн слоя, показывая, что есть доменная модель, и на базе ее строится аппликейшн. Таким помещением в рамках внутреннего слоя подразумевалось, что домен, как самая внутренняя часть проекта, его сердце, ни на кого не зависит а обозначает сущности, существующие (простите за тавтологию) вне зависимости от существования твоего приложения. Т.е. если бы его не было, все равно люди бы бегали вокруг и на условных бумажках это все делали... А аппликейшн слой - то уровень логики твоего приложения, которая, естественным образом зависит от домена... Что блин это за попытки переложить бл в домен? Причём повальная...
Но простого способа понять утекли ли зависимости я не нашёлПлохо искал. Закон протекающих абстракций не вчера был придуман и пока это были физически разные проекты, которые просто не имели зависимостей друг на друга и ты не мог обратиться к спринговому классу в бизнес логике просто потому, что спринга там не было, то с луковой архитектурой эта проблема уже появилась... И ее учились решать. Сначала каким-то самописными системами проверки, потом появился ArchUnit... и кстати, идея о том, что бл не должна зависеть от фреймворков сформирована только уже только в чистой архитектуре, первые заметки о которой были в 12 году... Быстро вы 7 лет эволюции конечно :D
На это нужно обращать внимание при ревью нового кода.Такая себе идея. Во время ревью кода нужна верхнеуровневая проверка. Автоматика не знает особенностей ваших систем и ревьюеры проверяют, можно ли делать то, что ты делаешь. А проверять стили/зависимости, наличие всяких очевидных плохих практик и пр. должна автоматика.
Гексагональная модель пушит нас для ядра приложения использовать свою внутреннюю модель.Это не гексагональная модель пушит... Люди и наигравшись со слоенкой приходят к тому, что это не адекватно, что когда у тебя меняется апи - падает работа с бд, потому что какое-то поле по другому завется... Но почему? Я ведь апи меняю... А если тебе придётся на бд другую съезжать, а если вдруг у этих бд окажется нужно по разному схемы строить потому что какие-то свои особенности... Не, я понимаю, что конвертеры уже наплохокодили, но... Если уже наступили в лужу и какие-то костыли заиспользовали - окей, но может лучше не наступать?...