Здравствуйте, ·, Вы писали:
P>>Это я имею ввиду начинку — то, что вы заложите в мок. С параметрами у нас ровно самый минимум — только таблица истинности, и сам код. У вас к этому добавляются еще и моки.
·>И пусть добавляются. Это просто способ передачи значений.
Это дополнительные издержки по сравнению с простыми функциями. Вы это сами показали на тестах календаря.
P>>>>Проверка склейки — такого не будет. А вот поддерживает ли приложение такую функцию — нужно проверять интеграционным тестым.
P>>·>Не всегда нужно, в этом и суть. Интеграционный тест проверяет интеграцию, что компоненты работают вместе. А не каждую функцию приложения.
P>>То-то и оно. Вот вы снова сами себе и ответили
·>Причём тут тогда "такую функцию"? Интеграция это не "такая функция".
Ощущение что для вас функциональные требования и функция в джаве это одно и то же. Как вы смоделируете, например, функцию удаления пользователя в системе — дело десятое. Это всего одна строчка в функциональных требованиях. А в вашей архитектуре это может быть процесс растянутый на месяц — пишете в очередь, читаете из нее, удаляет порцию, пишете в очередь... и тд и тд.
И у вас вполне вероятно не будет ни одной функции в джаве "удалить пользователя". А будет например "дропнуть док по хешкоду", "дропнуть записи по номеру телефона", "дропнуть сообщения по емейлу", "отозвать сертификат", "пометить спейс юзера к удалению через месяц", "подготовить выгрузку всех данных юзера", "закинуть зип на aws s3 c ttl 1 месяц"
P>>Нисколько. Моки прибивают вас к конкретному дизайну — см пример про инлайн кода.
·>Вас может прибивают, нас — не прибивают.
Вас тоже прибивают, только вы в этом видите пользу какую то.
P>>Этот пример демонстрирует особенность — даже ИДЕ перестает помогать.
·>Помогать в чём? Ещё раз — инлайнинг и т.п. — это средство, а не цель. С какой целью что инлайнить-то?
Рефакторинг, очевидно. Его не раз в год нужно делать, а непрерывно — требования меняются постоянно.
P>>А вот если вы отказываетесь от моков, соотвественно, ИДЕ вам в помощник — вдруг рефакторинг inline начинает работать сам собой
·>Ок, расскажи от каких моков надо избавиться, чтобы рефакторинг inline для str.length() заработал?
Элементарно — замена вызова на константу. Это работает, т.к. str у вас иммутабельная. А вот текущее время такой особенностью не обладает.
Другая проблема — ваша реализация с зависимостью внутри кешированию не поддаётся.
То есть, проблемы с вашей реализацией мы видим, а вот бенефиты какие то мифические, и вы никак не можете их описать.
P>>Дизайн вообще должен меняться постоянно, непрерывно. А вы выбираете такой подход, что изменения раз в год-два. О том я и говорю — вы пилите монолит.
·>Зачем?! Это ты из мира js говоришь, где каждую неделю новый фреймворк выходит и надо всё переписать? А каждые пол года очередной самый правильный дизайн появляется от фаулеров и надо срочно всю архитектуру перекраивать?!
Из вас технологический расизм прёт непрерывно. Извините, но на мой взгляд вместе с телепатией это признак неполной профессиональной компетенции.
Дизайн меняется по простой причине — требования меняются непрерывно. Вот появилось нефункциональное требование "перформанс" и мы видим, что CalendarLogic сидит в hotpath и вызывается сто раз с одним и теми же значениями. Опаньки — меняем дизайн. Как это сделать в вашем случае — не ясно. Разве что прокинуть к вашему CalendarLogic еще и MRU, LRU кеш с конфигом "вот такие значения хранить пять минут". Вот уж простор для моков — теперь можно мокать не одно время, а целых три, а можно MRU, LRU обмокать.
P>>Я ж объяснил — таблица истинности рядом с тестом и кодом, моки-стабы тоньше некуда. Вы рассматриваете систему в отрыве от тестов. Это категрически неверно. Даже конкретный деплоймент не имеет смысла без тестов — чемодан без ручки. Нужно проверить, а все ли путём, без тестов это никак. Более того — поменялись внешние зависимости — снова нужны тесты.
·>Это мы уже обсуждали, в проде мы не тестируем, это дичь какая-то из прошлого века.
Наоборот. В прошлом веке правило было "не трогай прод!!!!!!!1111111ййййкуккуку".
P>>Соответсвенно нужно выбирать такое решение, которое проще мейнтейнить, с учетом тестов.
·>Угу. И моки тут помогают.
Что бы просто так, без контекста — это слишком сильное утверждение. Но в целом оно отражает ваш подход к тестированию
P>>Вот код теста — как только вы его написали, решение о том, как будут протягиваться зависимости, уже принято
·>ТДД диктует необходимость того, чтобы код был тестируемый, а не про то куда как тянуть зависимости. Единственно что он может диктовать, что зависимостями надо управлять явно, чтобы можно было прокидывать тестовые.
Как только вы написали тест, то из него однозначно следует, какой будет АПИ и как будут прокидываться зависимости. Вы поймите — код теста это выражение наших намерений
1 апи
2 зависимости
3 поведение
4 связывание
5 входы-выходы.
По большому счету, идеально свести тест к п5. А вы сюда втаскиваете лошадь размером со слона.
P>> 1,2,3,4,5,6 // и так на каждую строчку в табличке
·>Не понимаю. Ты, похоже, стиль кода путаешь с дизайном. А дизайн с управлением зависимостями. Ну сделай себе таблицу истинности и с моками, и с параметрами:
·>·> when(dep.getX()).thenReturn(x);
·>
Лишняя строчка
·>Связывание компонент это не то же, что связывание запросов с логикой.
P>> Только еще вводите понятие wiring code.
·>Ок. Возможно я использовал неизвестную тебе терминологию. Это из мира управления зависимостей. "wiring up dependencies", "autowiring", под я под wiring code здесь имел в виду "composition root".
Вот-вот. Статься от 2011го, ссылается на книгу, которую начали писать в 00х на основе опыта накопленного тогда же.
Мы только что выяснили, кто же из нас двоих вещает из нулевых
P>>Часть кода связывания будет вне контролера, и правила вы водите сами, это и есть тот дизайн. Прокидываете через dependency injection — значит внутри контролера этого не будет.
·>Да похрен на дизайн. Это лишь меняет место где "волшебство" происходить будет.
Вам похрен — это и ежу понятно. Отличия в дизайне позволяют применить более эффективные способы, например, тестирования.
P>>Прокидываете now в контролере — эту часть логично исключить из dependency injection.
·>В контроллере now откуда возьмётся?
Вы уже много раз задавали этот вопрос, и я вам много раз на него отвечал. Отмотайте да посмотрите.
P>>Смысл помещения кода связывания в контроллер в том, что бы легче было интеграцию тестировать, делая самое важно явным.
·>Не понял, чем легче?
Самое важное сразу в коде метода, а не раскидано абы где по всеей иерархии dependency injection.
P>>У вас CalendarLogic принимает зависимость к конструкторе. Технически, эта зависимость может быть и null, и предзаписаной, и привязаной к контексту — не принципиально.
·>Это тут причём? Технически передавая через параметр твой now — тоже может быть null и т.п. Всё то же самое.
Нет, не может быть. Это вам нужно делать явно в самом контроллере, который зафейлится сразу же, на первом тесте который его вызовет.
·>В твоём же случае впихивание null будет происходить только при вызове функции приложения. Поэтому вам и требуются тесты всего уже после "успешгого" деплоя в проде.
Я ж вам объяснил — тесты прода нужны для самых разных кейсов.
·>А как ты будешь такой и-тест писать, если у тебя будет Time.Now? Что ассертить-то будешь? Как отличать два разных вызова Time.Now — клиентский от серверного? Впрочем, ты уже ответил "никак". Или я что-то пропустил?
Согласно бл. А вы как собираетесь отличать два разных вызова, клиентский от серверного? Вам для этого придется намастырить кучу кода с инжекцией в конструктор, только всё важное будет "где то там"
P>>Вы похоже сами свой код не читали, т.к. у вас недоумение, откуда может быть null. И ежу понятно — будет передан в конструктор или явно, или неявно. Правильно это или нет — скажут тесты.
·>Так же можно явно или неявно передать now=null в твой nextFriday(now).
Теоретически — можно. Практически — первый интеграционный тест сваливается, когда вызывает этот контроллер.
P>>А щас вы расказываете басню, что компилер как то отличит один провайдер времени от другого времени с таким же интерфейсом
·>Компилятор может отличать имена символов. Это же в коде видно и на ревью всё очень очевидно — где какое поле куда передаётся.
Вот вы сами себе и ответили. Ну или объясните, почему код ревью перестанет работать с параметром в контроллере.
P>>Прямой вызов — ваша функция вызовется прямо сейчас. Отложеный — потом. Например, вы сначала делаете запись, что за функция вызовется, с какими параметрами и тд, а в нужный момент сервис делает собственно сам вызов.
·>Какое это имеет отношение к разговору?
Вы же спрашиваи, что такое отложенный вызов. Если у нас нет зависимости внутри, то отложеный вызов делается как два пальца об асфальт. Это снова про гибкость, которой у вас почему то нет.
P>>Просто разные аргументы.
·>Аргументы чего? Откуда ты знаешь, что значения этих аргументов именно Time.Now и как это проверишь в тесте?
С вашего аккаунта похоже разные люди пишут, да еще и меняются во время написания одного и того же сообщения. Вот некто с вашего аккаунта чуть выше ответил на ваш вопрос:
Компилятор может отличать имена символов. Это же в коде видно и на ревью всё очень очевидно — где какое поле куда передаётся.
P>>·>Что синглтоны — плохо учат ещё в детском садике, ясельной группе.
P>>Вы наверное не поняли — никаких синглтонов, только параметры.
·>Откуда возьмутся значения этих параметров во всех местах вызова?
Через параметры функции. Дизайн то плоский, а не глубокий, как у вас.
P>>Мне не надо время тестировать. Мне нужно тестировать логику, в которой один или несколько параметров — время. Вы это время протаскиваете через конструктор косвенно, в виде провайдера. Я протаскиваю через параметр функции, явно.
·>Надо где-то тестировать, что ты протаскиваешь именно Time.Now а не что-то другое.
Вам тоже надо тестировать что вы привязали в проде пров клиенского времени, а не пров серверного или предзаписаного. Похоже, у вас там снова смена автора на вашем аккаунте.
P>>Интеграционным, которых ровно столько же, сколько и у вас.
·>Нет. У меня пирамида тестов. А у тебя — либо цилиндр, либо дырявое решето.
Для пирамиды нужно большое количество дешовых юнит тестов. У вас много полу-интеграционных, на моках, и мало собственно чистых юнитов. У меня — наоброт. Потому именно ваша пирамида видится мне ромбиком.
P>>·>Если при этом нужно плодить копипасту, то это явно что-то не то. И неясно причём тут инлайн и гибкость кода. str.length() — это негибкий код?!
P>>Вы сейчас перевираете.
·>Неперивираю, ты просто свои слова забыл. Напоминаю: " гибкость, которая проявляется в т.ч. в том, что код можно трансформировать, инлайнить". А дальше чистая логика:
·>"гибкий код → можно инлайнить" => "инлайнить нельзя → не гибкий код" см. https://en.wikipedia.org/wiki/Contraposition
С str.length всё в порядке — инлайнится с переводом в константу. У вас что то еще?
P>>Речь про инлайн вашего nextFriday(). Об ваш супер-крутой подход почему то ИДЕ спотыкается и вы проблемы в этом не видите.
·>Я спрашиваю "зачем инлайн"? Какую задачу-то решаем этим инлайном?!
Рефакторинг, оптимизации.
P>>Ваши тесты проверяют только тривиальные кейсы, на которых все зелено. В проде как правило кучка всего подряд.
·>Ну добавь эту твою кучку в тесты. Вопрос-то в чём?
Эта кучка называется data complexity. Я, например, билдером могу гарантировать, что данные будут запрашиваться постранично. Или что всегда будет фильтр по времени, где период не более трех месяцев. Соответсвено тестами можно проверить что структура запроса та, что нам надо
P>>О том, что фильтр может вернуть много записей, вам может сказать или сам запрос, или его результат.
·>Как запрос может что-то сказать?
Элементарно — всегда есть шанс, что ваш запрос вернет чего нибудь лишнее. Тестами эта задача не решается, принципиально.
Что бы написать корректный запрос, вам надо
1. гарантировать, что бы на "тех" данных вернется что надо. Это можно решить тестами — накидать данных, обработать, сравнить результат
2. гарантировать, что на других данных, которых у вас нет, тоже вернется ровно то, что надо. И тестами на основе данных это не решается — т.к. данных нет.
Итого — ваши тесты данных не являются решением, т.к. п 2 у нас обязательный. А вот дополнительные методы, например, инварианты, пред, пост условия, контроль построения запроса, код ревью основанное на понимании работы бд — это дает более-менее осязаемые данные.
P>>Второй вариант у вас отпадает, т.к. в тестах у вас не будет хрензнаетсколькилион записей в тестах
·>Зачем мне столько записей? В простом случае понадобится две записи — та которая я ожидаю, что она должна вернуться и та которая я ожидаю, что не должна вернуться.
Я вам выше рассказал. Нужна гарантия, что не вернется вдруг чего нибудь не то. Вариантов "не того" у вас по скромной оценке близко к бесконечности.
P>>>> Кое что, для аналитики — возможно. Но это большей частью всё равно перегоняется в орм или билдер. Есть орм — можно проверять построение логики запросов орм.
P>>·>В смысле ты в своих тестах linq/etc тестируешь? Зачем? У linq свои тесты есть.
P>>Это вы собираетесь базу тестировать
·>Опять врёшь. И цитаты, ясен пень, не будет.
Вы топите за косвенные проверки запросов, тестируя связку репозиторий+бд. Вот этого оно и есть.
P>> и топите за поведение. А мне надо всего лишь построение запроса к орм проверить. Если орм не позволяет такого — надо смотреть его sql выхлоп, если и такого нету, значит придется делать так, как вы предлагаете.
·>"Надо" кому? Мы вроде говорим о тестировании ожиданий бизнеса. Ты правда считаешь, что пользователи ожидают определённый выхлоп sql?!
Пользователи ожидают, что на их данных, которых в ваших тестах нет, будет нужный результат. Вашими тестами эта задача не решается
P>>И всё это нужно покрывать тестами, иначе любой дядел поломает цивилизацию
·>Ещё раз. У тебя в коде теста, в ожиданиях находится "in: [fn1]" и т.п. Как это соответствует "возвращать именно те данные, что прописаны в бл"? Где в бл вообще будет фигурировать in, fn1 и прочий select?
Не нужно сводить всё к бл напряму. У вас технологического кода в приложении примерно 99%. Он к бл относится крайне опосредовано — через связывание. А тестировать эти 99% тоже нужно.
P>>Смотрите выше — вы фильтры предлагаете тестировать однострочными вырожденными кейсами. Как вы будете ловить те самые комбинации, не ясно.
·>Ну я не знаю твой код. Я разрешаю тебе писать кейсы двухстрочные и даже трёхстрочные!
Вы что, недогоняете, что нет возможности покрыть неизвестные данные и комбинации какими либо тестами?
P>>Ни то, ни другое проблему с фильтрами не найдут.
·>Почему?
Потому, что у вас нет всего множества данных, которые могут приходить от юзеров.
Более того — количество данных дает вам такую особенность, как слишком долгое время выполнения запроса.
Задача с фильтрами у меня выявила такую вещь — если данных слишком много, то запрос с секунд резко уезжает в минуты.
Вот оптимизацию такой вещи закрыть тестами, что бы первый же фикс не сломал всю цивилизацию
P>>т.е. тесты контролера зависят от мока
·>Т.е. в тестах контроллера мы прописали ожидание, что время используется серверное, в соответствии с бизнес-требованиям.
В интеграционном тесте можно сделать то же самое.
P>>Вторая — вызываем бл при чтении из очереди, при этом время должно быть клиенское.
·>Т.е. это значит, что _изменились_ бизнес-требования и теперь мы должны брать не серверное время, а клиентское. Ясен пень, это фукнциональное изменение и соответствующие тесты надо менять. Если у тебя ни один тест не упадёт, у меня для тебя плохие новости.
Это как раз нефункциональное требование. Например, у нас запрос выполнялся слишком долго, и мы процессинг вытолкнули в очередь с отложеным процессингом.
P>>А это значит, что ваши тесты контролера идут в топку, как и моки которые вы под это дело прикрутили.
·>В контроллере надо будет заменить использование systemClock() на eventTimestampClock() и поправить тесты, чтобы зафиксировать функциональное изменение.
Функционых изменений здесь ровно 0. С т.з. пользователя никаких функций не добавляется.
P>>зачем?
·>Чтобы понять какое отношение имеет метапрограммирование к тестированию ожиданий бизнес-требований.
Метапрограммирование дает возможность зафиксировать свойства запроса, которые вы с вашими тестами сделать не сможете, никоим образом.
P>>Очевидно, посыплются не все. Ну нет такой ситуации что бы билдер на каждый запрос лепил UNION. Отвалится скорее всего несколько кейсов, ради которых это все и затевается.
·>Ну этот кусочек может быть в большой группе запросов, выбирающих разные данные по-разному, но часть логики — общая.
Может. Это дополнительные гарантии, сверх тех, что вы можете получить прогоном на тестовых данных. Потому издержки обоснованы.
P>>Кто даст гарантию, что в проде вы правильно прописали зависимости для CalendarLogic? Интерфейс то один и то же
·>Гарантию, конечно, никто не даст, но покрыть тестами с моками тоже можно. Для этого моки и придумали.
Вот вот — прибиваете контроллер к тестам на моках.
P>>Буквально curl тест все что может, это сравнить выхлоп, и всё. Если там время не фигурирует, то никак.
·>Ну, допустим, фигурирует. И чем это тебе поможет?
Тогда можно сравнить выхлоп, тот или не тот.
P>>Если же вы имеете ввиду интеграционные, то будет все ровно как и у вас.
·>С моками? Ну дык об чём спор-то тогда? Ведь именно моком я могу протащить тестовые значения now в нужные места. Там, где ты предлагаешь Time.Now синглтон, в который ты ничего протащить не сможешь.
Этим протаскиванием вы бетонируете весь дизайн. Смена дизайна = переписывание тестов.
P>>Бинго! Оптимизация, нефункциональные требования, технические особенности — все это имеет чудовищное влияние, намного больше, чем сама бизнес-логика.
P>>Если вы потеряете оптимизацию — приплызд. и тест нужен зафиксировать эту особенность что бы сохранить при изменениях.
·>Это уже будет перф-тест и к обсуждаемой теме не относится.
Покрыть перформанс тестами все вырожденные кейсы вам время жизни вселенной не позволит. Некоторые детали реализации нужно фиксировать обычыными тестами.
Это нововведение в тестах — кроме функциональности тесты могут фиксировать конкретную особенность.
P>>См выше — все нефунциональные вещи нужно проверять и фиксировать тестами.
·>Это не оправдывает твой подход, что функциональные тесты должны тестировать метапрограммирование.
Метапрограммирование тестируется там, где оно применяется в силу естественных причин. Если у вас запросы тривиальные, вам такое не нужно.
P>>Покажите, как вы все эти кейсы обнаруживать будете.
·>Возвращаемся к старому спору. Я уже отвечал на это неоднократно: системы мониторинга, health probes и прочие операционные инструменты. Это не тестирование.
Неэффективно. Для многих приложений час невалидной работы может стоит любых денег. Дешевле запустить тесты на проде — час-два и у вас информация по всем функциям.
P>>Полагаю ответ будет "у нас багов на проде нету" ?
·>Вот таких багов "протух-обновился сертификат" — и не должно быть. Такие вещи именно мониторингом отслеживаются заранее, чтобы пофиксить можно было до возникновения этой ситуации. А у вас серты протухают ВНЕЗАПНО, и обнаруживаются только тестами постфактум, что у вас в проде что-то где-то отвалилось... ну могу только пособолезновать вашим клиентам.
Вы снова включили телепатию. Сертификат может обновиться не у вашего приложения, а у какого нибудь из внешних сервисов. Мониторинг вам скажет, когда N юзеров напорются на это и у вас начнет расти соответсвующая метрика.
P>>насколько я понимаю, это я все еще жду вашего примера про фильтры, который решает ту самую data complexity
·>Какую ту самую? Я никакой "data complexity" не видел и телепатией не обладаю, чтобы выдать какое-то решение для задачи о которой я не слышал.
У вас есть иллюзия, что ваши тесты гарантируют отсутствие лишнего выхлопа на данных, которые у вас отсутсвуют.
P>> Вход на фильтры полным перебором подавать или магией воздействовать?
·>Да так же, как у тебя. Только ассертить не буквальный текст запроса, а результат его выполнения
P>>Вы когда нибудь видели дашборды где фильтры можно выбирать хрензнаетсколькилионом кнопочек и полей ввода?
·>Насколько мне доводилось видеть, они обычно через & соединяются. Т.е. список независимых фильтров. Сложность возникает когда фильтры между собой взаимосвязаны, но обычно такое избегают, т.к. юзерам такая compexity тоже не под силу, и использовать нормально всё равно не получается.
Ну да, вы сами чего то додумали, и воюеете за это
Я вижу, что в большинстве интернет магазинов в т.ч. крупных фильры работают некорректно.
P>>Не просто манифест — а любое описание, абы ваши пиры могли его понять и цикл разработки был быстрым.
·>Что значит за любое описание? Манифест это и есть описание api.
Любой формат, любое расширение файла
У ваших пиров должен быть инструмент для использования такого описания.
Например, вы решили выдать swagger, но описали в ём такое апи, которое ни один генератор кода не понимает. Это значит, что вы АПИ вообще не предоставили — мало кто в своём уме будет вручную имплементать нюансы которые к релизу поменяются 100500 раз
P>>Появятся после того, как вы заделивирите АПИ, например спустя год, когда у вас уже версия 1.x. И вот здесь вы решите перейти на rust, то в этот момент крайне странно топить за design-first — у нас уже готовое приложение версии 1.x, и нужно сделать так, что бы манифест отрендерился — с учетом тенантов и прочих вещей, включая not implemented. Или вы собираетесь бегать по отделам и рассказывать "а чо вы это делаете — мы же на джаве год назад это решили не делать" ?
·>У вас уже есть имплементация и мы вот наконец спустя год выкатываем манифест api, но ты всё ещё это называешь design-first?! Нет, это типичный code-first — когда прыгать надо, думать некогда.
Вы похоже не читаете. Какой смысл вам что либо рассказывать?
1. когда стартуем проект, где центральная часть это АПИ. Идем через design-first, формат файла и расширение — любое, см. выше
2. готовый проект — уже поздно топить за design-first, просто генерим артефакты
3. переписываем проект на rust — снова, нам design-first не нужен, это вариация предыдущего кейса
Вот если у вас переписывание случается каждый день, или даже чаще, да на новый язык, то АПИ нужно держать в каком то универсальном формате.
Правда, в этом случае я более чем уверен, вы дольше всего будете подбирать генераторы кода под это АПИ, или выписывать все загогулины руками с воплями "я уже заимплементал херли вы перенесли параметр из квери в хидеры!!!!!!!11111йййкукуку"
P>>>>Вы вместо уточнения пытаетесь в телепатию играть. Если у нас готовое приложение, то конечно же генерируется — и странно было бы иначе делать.
P>>·>Угу. ЧТД. "готовое приложение" == "code-first".
P>>Я вам говорю откуда возьмется апи в готовом, если вы решите уже готовое переписать на другом языке.
·>Причём тут design-first?
У вас точно меняется контингент, или в голове, или за компом:
Если вы захотите переписать свой сервис на другой ЯП, ну там с js на rust — что вы будете делать со своими аннотациями?
Переписывание предполагает, что у нас уже есть и АПИ, и реализация этого АПИ. Отсюда ясно, что проще сгенерировать артефакт, чем изначально держаться за конкретный формат и топить за design-first