Эргономичный код
795 subscribers
76 photos
3 videos
20 files
385 links
Канал о разработке поддерживаемых бакэндов - про классическую школу TDD, прагматичное функциональное программирование и архитектуру и немного DDD.

Группа: https://t.me/+QJRqaHI8YD

https://azhidkov.pro
Download Telegram
Даже провод дотянул
Но, кажется, что-то упустил 😂
Please open Telegram to view this post
VIEW IN TELEGRAM
😁7
Привет!

Так, ну большая веха пройдена, расскажу про свои ближайшие планы.

И главный ближайший план - отдохнуть. До марта.
У меня с июля идёт жёсткий спринт, где я жонглирую домом с младенцем и старшим ребёнком, который за 3 месяца сходил в сад 7 дней, выступлением и проектом Р, где дикая жопа тянется с июня.

Но перед тем как начать уже отдыхать я планирую:
1. На следующей недели съездить на оффлайн часть конфы и сделать там лайтенинг толк с кратким пересказом изначального доклада про Spring Data JDBC
2. Допричёсывать и скинуть ссылки на демо проекты для доклада про SDJ
3. Выбрать и опубликовать тестовые прогоны всех трёх докладов: оригинальной полной версии доклада про SDJ, финального доклада про структурный дизайн и лайтенинг толка про SDJ
4. Протегать все свои посты и сделать "домшнюю страницу" со ссылками на тематические подборки
5. У меня есть предвfрительная договорённость зайти в гости к @javaswag - надеюсь там ещё лицом поторгую

На это всё, думаю, у меня уйдёт месяц-полтора.
А после отпуска я планирую несколько поменять подход к проработке ЭП - хочу завести сайт-вики и там для начала просто собрать и описать список всех паттернов и моделей, которые использую в своей работе.

Вот такие дела, стейтюнед, мы найдём способ делать системы так, чтобы работать было по кайфу:)
16🔥5
Хейтеры такие хейтеры:)

Относительно прошлого выступления обратная связь неоднозначния.

Отзывов больше (27 вс 23) и лучше (4.4 вс 4.1). Но досмотрели доклад до конца сильно меньше (40% вс 74%). С просмотрами, возможно, разница в значительной степени обусловлена онлайном и оффлайном
👍3
Привет!

История ужасов про мутабельное состояние.

Есть у нас тут в Проекте Р задачка по загрзуке файла. Отдали её юниору. Он вчера провозился весь день - не осилил загрузить, спринг ругается что не видит multipart part.

Сегодня подключился я. Сделал мастер-класс по ТДД, докинул пару параметров, тест (поверх MockMvc) прошёл. На всякий случай (не верю я этим мокам) пошёл перепроверить курлом - не работает.

Засучил рукава. Перелазил весь инет. Отдебажил эту хрень вплоть до org.apache.tomcat.util.http.fileupload.util.LimitedInputStream - я в запросе тело отправляю, а когда начинает работать StandardServletMultipartResolver - тело куда-то исчезает 😱🤯

Провозился три часа. Потом каким-то чудом, вспомнил, что у нас есть фильтр, который вычитывает тело и прокидыввает дальше обёртку, которая переопределяет getInputStream. А getParts - не переопределяет. И getParts пытается вычитать данные из инпутстрима оригинального HttpServletRequest. Из которого мы уже всё вычитали в фильтре. Занавес.

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

#whyfp@ergonomic_code #project_r@ergonomic_code
👍10😱2
Привет!

Обещанные демо-проекты.

Project Moby - демо того, как я выкручиваюсь на Spring Data JDCB с энергичной загрузкой, когда она нужна.

Project Sherlok - демо того, как я выкручиваюсь с динамиеческими запросами.

Обе техники используются в Trainer Advisor.

#spring_data_jdbc@ergonomic_code #trainer_advisor@ergonomic_code #demo_projects@ergonomic_code
🔥10
Куда выложить записи репетиций докладов?
Anonymous Poll
64%
Youtube
16%
Rutube
14%
VK Video
5%
Что-то ещё?
Прошу прощения, со стримом был фальстарт - организаторы не разрешили
Похоже лайтенинг будет на расслабоне :)
😁3
Привет!

Дайджест первого дня

Лайтенинг удался
Судя по всему.

На докладе был Михаил Поливаха (контрибьютор в SDJ) и после доклада он сказал, что я всё правильно сказал.
А потом ко мне подошёл один из кураторов основного доклада и сказал, что основной доклад был очень хороший, а лайтенинг - ещё лучше.
Ещё на докладе был Владимир Плизга, который после него подписался на канал - Вова, привет! 👋

Олды, которых мы заслужили

Передо мной выступал другой мужик с 20+ опыта и рассказывал о том, как гугл лёгким движением руки превратил их 150+ приложений в легаси убив GWT.
А после моего доклада ведущий выдал что-то в духе "А вы заметили, что про боль в основном говорят мужчины за 30?"

А на следующем докладе девушка сказала, что-то в духе "А вот например жуткие олды с 20+ лет опыта...".
Она говорила про мастеров тату, но всё равно было обидно:)

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

В Питере пить

После того как нас выгнали с площадки, затусил с тремя пацанами из Amplicode. Сначала 1.5 часа шарохались по Питеру в поисках открытого после 12 бара/ресторана. Кое-как нашли, выпили по бокалу пива с бургером за 505 рублей с носа и разошлись спать.

Мир, дружба, жвачка

Не смотря на то, что пацанов из Amplicode было больше, я храбро заявил, что не люблю JPA. На что внезапно получил ответ, что из тех кто работает с JPA никто его не любит, а если говорит, что любит - это вызывает вопросики 🤔
Договорились, что зайду на стенд, обещали значки:)

Начинаю отдыхать

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

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

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

Код двух подписчиков уже есть в TA - так что это вполне реально:)

Если интересно - пишите в личку, договоримся о звонке для онбоардинга
👍10🔥6
Ещё пункт забыл

Неожиданная встреча
Случайно встретил бывшего студента.

Он рассказал, что следующему поколению советовал идти ко мне на курс по БД. А я не пришел - взял "декрет".
Но все равно приятно - снова задумался вернутся в следующем году.

А ещё (к вопросу про JPA в комментах) рассказал, что у них в большой корпорации пришел СТО, сказал что они не умеют в JPA и теперь они то ли все новое пишут, то ли старое переписывают на SDJ.

#spring_data_jdbc@ergonomic_code
👍2
Привет!

Фух, "папа вырвался на свободу" наконец-то закончилось. С понедельника спал часов 15 максимум, а не пью первый день.
Но было круто - конфа глазами спикера выглядит намного интереснее, чем участника - советую попробовать.

Я весной попробую податься на snowone или codefest с вариацией на тему одного из докладов Джокера, а осенью попробую опять на Джокере ченить про тестирование рассказать.
👍17
Привет!

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

Я - задавался.

А сейчас начал читать матчасть по юзкейсам и узнал почему.

Оказывается, юзкейсы рекурсивны.

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

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

Ну и книгу я ещё прочитал только процентов на 50, но уже готов её порекомендовать.

#books@ergonomic_code #ergo_approach@ergonomic_code #ergo_arch@ergonomic_code
👍6🔥1
Forwarded from Spring АйО
🔗 Создание Deep Stubs в Mockito

При юнит-тестировании часто требуется проверить методы, которые используют цепочки вызовов, например, в WebClient из Spring WebFlux.

⚠️ Если задача позволяет, лучше воспользоваться MockWebServer или WireMock для гибкого тестирования HTTP-клиентов. Здесь WebClient выбран лишь из-за своей наглядности и популярности.

Представим клиент, запрашивающий случайную цитату у удалённого сервиса:


public class InspirationalQuotesClient {
private final WebClient webClient;

public InspirationalQuotesClient(WebClient webClient) {
this.webClient = webClient;
}

public String fetchRandomQuote() {
try {
return this.webClient
.get()
.uri("/api/quotes")
.retrieve()
.bodyToMono(String.class)
.block();
} catch (WebClientException e) {
return "Every time a mock returns, a mock a fairy dies.";
}
}
}


Для тестирования такого клиента стандартным способом нужно замокировать каждый вызов в цепочке, чтобы избежать NullPointerException. Это может сделать тест слишком громоздким:


@ExtendWith(MockitoExtension.class)
class InspirationalQuotesClientTest {

@Mock private WebClient webClient;
@InjectMocks private InspirationalQuotesClient cut;

@Test
void shouldReturnInMockingHell() {
WebClient.RequestHeadersUriSpec requestHeadersUriSpec = Mockito.mock(WebClient.RequestHeadersUriSpec.class);
WebClient.ResponseSpec responseSpec = Mockito.mock(WebClient.ResponseSpec.class);

when(webClient.get()).thenReturn(requestHeadersUriSpec);
when(requestHeadersUriSpec.uri("/api/quotes")).thenReturn(requestHeadersUriSpec);
when(requestHeadersUriSpec.retrieve()).thenReturn(responseSpec);
when(responseSpec.bodyToMono(String.class)).thenReturn(Mono.just("We've escaped hell"));

String result = cut.fetchRandomQuote();
assertEquals("We've escaped hell", result);
}
}


Решение? Deep Stubs в Mockito. С ними можно упростить тест, описав всю цепочку вызовов сразу в конструкции when. Это значительно уменьшит количество кода:


@ExtendWith(MockitoExtension.class)
class DeepStubClientTest {

@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private WebClient webClient;

@InjectMocks private InspirationalQuotesClient cut;

@Test
void shouldReturnQuoteFromRemoteSystem() {
Mockito.when(webClient.get().uri("/api/quotes").retrieve().bodyToMono(String.class))
.thenReturn(Mono.just("Less setup hell - but not better"));

String result = cut.fetchRandomQuote();
assertEquals("Less setup hell - but not better", result);
}
}


Используя параметр answer = Answers.RETURNS_DEEP_STUBS, мы возвращаем заглушку для каждого метода в цепочке. Но помните, что этот подход увеличивает зависимость теста от конкретной структуры вызовов, и любые изменения в цепочке потребуют изменений в тесте.

P.S. А как вы мокируете вызовы внешних API?

Ставьте 🔥 если считаете функциональность полезной.
Please open Telegram to view this post
VIEW IN TELEGRAM
Привет!

Говорят, чёрный пиар - это тоже пиар, поэтому надеюсь пацаны из Spring АйО (Саша, привет! 👋 ) не сильно обидятся, что я их пост разнесу чутка:)

Пример теста в посте имеет две проблемы

Проблема первая - непонятно, что тестирует этот тест
Вообще, вроде как, он должен тестировать клиента внешней системы. Но фактически, он тестирует не клиента внешней системы, а просто фиксирует вызовы Spring-овго АПИ (читай: детали реализации).

И если внешняя система поменяет формат ответа на json - код сломается, а тест продолжит проходить.
А если я перепишу клиента на restTemplate (и проверю руками) - код будет работать, а тест сломается.

Проблема вторая - он нарушает хрестоматийное правило тестов с моками
Мокайте только собственные типы (см. Growing Object-Oriented Software, Guided by Tests, Chapter 8)



Как это надо было делать

Вариант №1: идеальный

Поднять в тестконтейнере latest-версию внешней системы и написать тест, который бы работал с ней.
Такой тест:
1. ни как бы не был завязан на реализацию - вы с внешней системой могли бы хоть на protobuf перейти и в самом тесте вообще ничего менять бы не пришлось
1.1. Как следствие он бы подстраховал вас и если бы вы решили поменять либу для работы с хттп (на тот же RestTemplate, например)
2. поймает ошибки обратной совместимости внешнего АПИ, особенно если его регулярно на CI запускать
3. поймает регрессии в Spring
4. Ну и, очевидно, поймает если нарушите контракт (например, при рефакторинге идея затупит и поменяет слово в пути)
5. а ещё вы сможете его использовать в процессе разработки и соответственно быстро итерироваться с кодом реализации клиента, не рестаруя всё приложение целиком или не костыляя какой-то отдельный main-метод для этих целей

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

Но это (внешняя система в докере) - практически нереальный вариант.

Вариант №2: реальный
Соответственно следующий вариант - написать тест, который работает с тестовым контуром внешней системы. Скорее всего, его придётся засунуть под флаг и запускать только на CI.
Он продолжит быть вашим другом, но и принесёт ряд проблем:
1. скорее всего он будет медленный
2. возможно он будет мигать
3. сообщать о проблемах он будет позже, чем хотелось бы

Вариант №3: cомнительно, но окей
Если варианты выше не возможны, либо не приемлемы (слишком медленно, слишком часто мигает) - можно взять WireMock.
В этом варианте вы потеряете контроль соблюдения контракта вами и внешней системой, но хотя бы останется контроль, соблюдения контракта вами и Spring-ом. Соответственно такой тест подстрахует вас, если вы решите поменять либу работы с ХТТП, либо обновите версию Spring.

Но я бы на такие тесты вообще не заморачивался - это всё будет покрыто тестами, клиентов нашего клиента, которым для работы надо будет, чтобы InspirationalQuotesClient смог взять данные.

Вариант №4: кто-то в интернете считает, что так правильно, но он не прав
Собственно мокисты считают, что правильно:
1. InspirationalQuotesClient покрыть интеграционным тестом (варианты 1-3 выше), либо не покрывать тестами вообще
2. А в тесте клиента InspirationalQuotesClient замокать уже сам InspirationalQuotesClient, а не WebClient

Но на самом деле правильно:
1. операцию, в которой задействован InspirationalQuotesClient покрыть одним интеграционным тестом, который:
1.1. всю управляемую инфраструктуру (БД, очередь сообщений, внутренние сервисы) поднимает в тест контейнерах и работает с ней
1.2. везде где возможно для неуправляемой инфраструктуры поднимает фейки на транспортном уровне (WireMock)
1.3. В самом крайнем случае мокает свои типы, которые абстрагируют внешнюю инфру, которую по другому не симулировать никак
2. Эту операцию сделать сбалансированной
3. И получить возможность всю бизнес-логику (которая даже косвенно через интерфейсы не зависит от внешних систем) покрыть чистыми юнит тестами, без спринга, моков, тестконтейнеров и т.д.

#ergo_testing@ergonomic_code
👍43