Форум
Архитектура программного обеспечения
Тема
Как правильно задавать вопросы
B
I
abc
U
X
3
X
3
H1
H2
H3
H4
H5
H6
Asm
C/C++
C#
Erlang
Haskell
IDL
Java
Lisp
MSIL
Nemerle
ObjC
OCaml
Pascal
Perl
PHP
Prolog
Python
Ruby
Rust
SQL
VB
Здравствуйте, ·, Вы писали: ·>Здравствуйте, Pauel, Вы писали: P>>>>Например, нам нужно считать токен для стрима, нас интересует ид класса, ид сущность, версия сущность b секрет. P>>·>Не очень ясно в чём идея. У тебя есть библотека которая умеет несколько алгоритмов, апдейт по частям с приведением типов, формирование дайджеста в разных форматах. И ты это оборачиваешь в свою обёртку, которая умеет только одно - конкретный алгоритм упакованный в отдельный метод Token.from, который содержит конкретные параметры и т.п. И что в этом такого особенного? Называется частичное применение функции. И причём тут моки? P>>Это я вам пример привел, для чего вообще люди мокают, когда всё прекрасно тестируется и без моков. ·>Т.е. проблема не в моках как таковых, а в том, что некоторые люди используют некоторые подходы и пишут корявый код? Это проблема ликбеза, а не моков. Ровно та же проблема есть в любых подходах тестирования. P>>·>Проблема такого подхода в том, что это работает только в тривиальных случаях. Когда в твоём приложении понадобятся много разных комбинаций, то у тебя количество частично применённых функций пойдёт по экспоненте. У тебя получатся ApiTokenAsHex.from, ApiTokenAsBytes.from, ApiTokenAsB64.from, HashedPassword.from, HashedPasswordAsBytes.from и пошло поехало. P>>А зачем вам эти AsHex, AsBytes? Это же просто преставление. У вас есть токен - это значение. Его можно создать парой-тройкой способов, и сохранить так же. ·>Именно. 3 способа создания x 3 способа представления - уже девять комбинаций. У тебя будет либо девять функций, либо своя обёртка над сторонним api, изоморфная по сложности тому же api. P>> Отсюда ясно, что нет нужды плодить те функции, что вы предложили, а следовательно и тестировать их тоже не нужно. ·>Нужда идёт из кода, из требований. Если у тебя достаточно сложная система, у тебя будет там все 9 комбинаций, а то и больше. P>>Всех дел - отделить данные от представления, и количество тестов уменьшается. ·>Количество может быть уменьшено, только если ты какое-то требование перестал тестировать. P>>>>Теперь вы можете покрыть все кейсы - 1-3 успешных, и ведёрко фейлов типа ид, секрет, версия не установлены. P>>·>Т.е. вынесли кусочек кода в метод и его протестировали отдельно. И? Моки тут причём? P>>Насколько я понимаю, я вам показываю, как разные люди понимают моки. Вы же всё приписываете лично мне. ·>Что лично тебе я приписал? P>>·>Но если у тебя оно нужно конкретно в одном месте для etag, то это уже оверкилл. P>>Вас всё тянет любое высказывание к абсурду свести. Естетсвенно, что городить вагон абстракций, ради того, что бы вычислить етаг не нужно. ·>А ты таки предложил новую абстракцию ввести в виде ApiToken.from. P>>При этом и моки тоже не нужны для этого случая. P>>У нас будет функция [tt]getEtag(content)[/tt] которую можно проверить разными значениями. ·>Именно. Зачем нам тогда нужен ApiToken.from? P>>Тем не менее, пример на моках что я вам показал, он именно отсюда взят. ·>Пример на то и пример, чтобы показать как использовать моки. А в твоём примере было показано как _не надо_ писать код. Я показал как надо писать код при использовании моков. И мой код был вполне вменяемым. Оправданы ли они в данном случае моки - вопрос другой. P>>>>ну вот представьте - либа слишком медленная. И вы находите ей замену. Идеальный результат - после замены тесты зеленые. P>>·>Оборачивать каждую стороннюю либу - т.к. её _возможно_ нужно будет заменить - это оверинжиниринг в большинстве случаев. P>>При чем здесь оборачивание? У вас есть функционал - верификация jwt токена. P>>Объясните внятно, почему замена jsonwebtoken на jose должна сломать тесты? P>>А вот если вы замокали jsonwebtoken, то ваши тесты сломаются. И причина только в том, что вы замокали там где этого не надо делать. ·>В примере никакого jwt токена не было. Я не знаю ты о чём, у тебя где-то в голове какая-то более широкая картина, но к сожалению, я не очень хороший телепат. P>>>>а на вызывающей стороне вы вызываете какой нибудь [tt]result.applyTo(ctx)[/tt] P>>·>А какая разница? Ведь эту конструкцию надо будет тоже каким-то тестом покрыть. P>>Для этого есть интеграционный тест ·>Жуть. Т.е. ты не можешь что-то протестировать быстрыми тестами, приходится плодить медленные интеграционные и тривиальные ошибки обнаруживать только после запуска всего приложения. Спасибо, не хочу. P>>>>Соответсвенно, все изменения у вас будут в конце, на самом верху, а до того - вычисления, которые легко тестируются. P>>·>То что ты логику в коде куда-то переносишь, не означает, что её вдруг можно не тестировать. P>>Там где логика - там юнит-тесты, без моков, а там где интеграция - там интеграционные тесты, тоже без моков. ·>Слишком тяжелый подход. Только на мелких приложениях работает. P>>>>Вот-вот, еще чтото надо куда то инжектить, вызывать, проверять, а вызвалось ли замоканое нужное количество раз, в нужной последовательности, итд итд итд P>>·>Это надо не для моков как таковых (если validateThing - тривиальна, то зачем её вообще мочить? Всегда можно запихать реальную имплементацию), а для разделения компонент и независимого их тестирования. P>>А задизайнить компоненты, что бы они не зависели друг от друга? ·>Это как? То что зависимости неявные - это не означает, что их нет. P>>·>Я тебе уже написал когда это _надо_, но ты как-то тихо проигнорировал: [i]Более того, когда мы будем дорабатывать поведение фукнции validateThing потом, эти три наших теста не пострадают, что они вдруг не очень правильно сформированные part1/part2 подают. Вдобавок, функция хоть пусть и валидная, но она может быть медленной — лазить в сеть или банально жечь cpu вычислениями.[/i] P>>Это ваш выбор дизайна - на самом низу стека вы вдруг вызываете чтение из сети-базы-итд, или начинаете жечь cpu. P>>А если этого не делать, то и моки не нужны. ·>Ага-ага. Но тогда нужны ещё более медленные и неуклюжие интеграционные тесты, много. P>>>>Итого - вы прибили тесты к реализации P>>·>В js принято валить всё в Global и сплошные синглтоны? DI не слышали? P>>Зачем global? DI можно по разному использовать - речь же об этом. Посмотрите framework as a detail или clean architecture, сразу станет понятнее. ·>require это нифига не DI, это SL. P>>Вместо выстраивания глубокой иерархии, где на самом низу стека идет чтение из бд, делаем иначе - вычисляем, что читать из бд, следующим шагом выполняем чтение. P>>Итого - самое важное тестируется плотно юнит-тестами, а остальное - интеграционными P>>С вашими моками покрытие будет хуже, кода больше, а интеграционные всё равно нужны ·>Интеграционные тесты тестируют интеграцию крупных, тяжелых компонент. А у тебя они тестируют всю мелкую функциональность. P>>>>На мой взгляд это не нужно - самый минимум это ожидания вызывающей стороны. Всё остальное это если время некуда девать. P>>·>Так пиши ожидания когда они известны, если ты их знаешь. Анализ вайтбокс, покрытие кода - именно поиск неизвестных ожиданий. P>>·>"Мы тут написали все ожидания по спеке, но вот в тот else почему-то ни разу не попали ни разу. Разбираемся почему." P>>С моками чаще получается так - моками некто заставил попасть в этот else, а на проде этот else срабатывает совсем в другом контексте, и все валится именно там где покрыто тестом. ·>Очень вряд-ли, но допустим даже что-то вальнулось в проде. Изучив логи прода, ты с моками сможешь быстро воспроизвести сценарий и получить быстрый красный тест, который можно спокойно отлаживать и фиксить, без всякой интеграции. P>>·>Это только для tryAcceptComposition. А таких врапперов придётся написать под каждый метод контроллера. И на каждый пару тестов. P>>Именно! И никакие моки этого не отменяют! ·>Они отменяют необходимость написания тучи этих самых xxxComposition, нет кода - нечего тестировать. P>>>>Во вторых - моки не избавляют от необхидимости интеграционных тестов, в т.ч. запуска на реальной субд P>>·>Такой тест может быть один на всю систему. Запустилось, соединения установлены, версии совпадают, пинги идут. Готово. P>>Если у вас система из одного единственного роута и без зависимостей входящих-исходящих, то так и будет. А если роутов много, и есть зависимости, то надо интеграционными тестами покрыть весь входящий и исходящий трафик. ·>Не весь, а только покрывающий интеграцию компонент. P>>>>Вот я и говорю - вы сделали в бл зависимость от бд, а потом в тестах изолируетесь от нее. Можно ж и иначе пойти - изолироваться на этапе дизайна, тогда ничего мокать не надо P>>·>В бл зависимость от репы. P>>А следовательно и от бд. Например, вечная проблема с репозиториями, как туда фильтры передавать и всякие параметры, что бы не плодить сотни методов. Сменили эту механику - и весь код где прокидываются фильтры, издох - начинай переписывать тесты. ·>Это какая-то специфичная проблема в твоих проектах, я не в курсе. P>>·>Гораздо больше, чем динамическая. P>>Этого недостаточно. Нужен внятный интеграционный тест, его задача - следить что подсистема с правильными зависимостями соответствует ожиданиям ·>Задача инеграционного теста - тестировать места взаимодействия компонент, а не сценарии поведения. P>>>>тогда получается иерархия плоская - у контроллера кучка зависимостей, но репозиторий не зависит от бд, бл не зависит от репозитория P>>·>А какие типы у объектов getUser/updateUser? И примерная реализация всех методов? P>>getUser - чистая функция, выдает объект-реквест, например [tt]select * from users where id=?[/tt] + параметры + трансформации. UpdateUser - чистая функция, вычисляет все что нужно для операции. execute - принимает объект-реквест, возвращает трансформированый результат. ·>Как ассертить такую функцию в юнит-тесте? Как проверить, что текст запроса хотя бы синтаксически корректен? И как ассертить что параметры куда надо положены и транформация верна? ·>И далее, ну допустим ты как-то написал кучу тестов что getUser выглядит правильно. А дальше? Как убедиться, что настоящая субд будет работать с getUser именно так, как ты описал в ожиданиях теста репы? >>>> const updateUser = repository.updateUser(oldUser, newUser); P>>·>А если я случайно напишу P>>·>const updateUser = repository.updateUser(newUser, oldUser); P>>·>где что когда упадёт? P>>Упадет интеграционный тест, тест вида "выполним операцию и проверим, что состояние юзера соответствует ожиданиям". ·>Плохо, это функциональная бизнес-логика, а не интеграция. Это должно ловиться юнит-тестом, а не интеграционным. P>>·>А как контролиоруется что newUser возвращённый из бл обработается так как мы задумали в репе? P>>Метод репозитория тоже тестируется юнитами, без моков - его задача сгенерировать правильный реквест. P>>Проблему может вызвать ветвление в контроллере - он должен быть линейным. ·>Т.е. нужно писать интеграционный тест как минимум для каждого метода контроллера. Это слишком дохрена. P>>·>У тебя какое-то странное понимание моков. Цель мока не чтобы максимально точно описать поведение мокируемого, а чтобы описать различные возможные сценарии поведения мокируемого, на которые мы реагируем различным способом в sut. P>>Я вам пишу про разные кейсы из того что вижу, а вы все пытаетесь причину во мне найти :facepalm: ·>Т.е. ты пытаешься своё "Я так вижу" за какой-то объективный факт. P>>·>Т.е. при тестировании контроллера совершенно неважно, что именно делает реальный modifyUser. Важно какие варианты результата он может вернуть и что на них реагируем в sut в соответствии с ожиданиями. P>>А моки вам зачем для этого? ·>Чтобы описать варианты результата что может вернуть modifyUser. P>>>>А как это QA делают? Строчка требований - список тестов, где она фигурирует. P>>·>А как узнать, что требования покрывают как можно больше возможных сценариев, а не только те, о которых не забыли подумать? P>>Если мы очем то забыли, то никакой кавередж не спасет. Например, если вы забыли, что вам могут приходить данные разной природы, то всё, приплыли. ·>Это наверное для js актуально, где всё есть Object и где угодно может быть что угодно. При статической типизации вариантов разной природы не так уж много. Если modifyUser возвращает boolean, то тут только два варианта данных. А вот в js оно внезапно может выдать "FileNotFound" - и приехали. P>>>>Это я так трохи пошутил - идея в том, что нам всего то нужен один тест верхнего уровня. он у нас тупой как доска - буквально как http реквест-респонс P>>·>Ага. Но он, в лучшем случае, покроет только один xxxComposition. А их будет на каждый метод контроллера. P>>Это в любом случае так - e2e тесты должны покрыть весь входящий и исходящий трафик. ·>Вот тут тебе и аукнется цикломатическая сложность. Чтобы покрыть весть трафик - надо будет выполнить все возможные пути выполнения. Что просто дофига и очень долго. P>>>>Много где завезли, в джава коде я такое много где видел. А еще есть сравнение логов - если у вас вдруг появится лишний аудит, он обязательно сломает выхлоп. Это тоже блекбокс P>>·>Это означает, что небольшое изменение в формате аудита ломает все тесты с образцами логов. Вот и представь, добавление небольшой штучки в аудит поломает все твои "несколько штук" тестов на каждый роут. P>>Похоже, вы незнакомы с acceptance driven тестированием. В данном случае нам нужно просмотреть логи, и сделать коммит, если всё хорошо. ·>Это для игрушечных проектов ещё может только заработать. В более менее реальном проекте этих логов будут десятки тысяч. И просмотреть всё просто нереально. После тысячной строки диффа ты задолбаешься смотреть дальеш и тупо всё закоммитишь, незаметив особый случай в тысячепервой строке. P>>·>Именно. А должно быть несколько штук на весь сервис. Роутов может быть сотни. P>>Тесты верхнего уровня в любом случае должны покрывать весь апи. Вы же своими моками пытаетесь искусственно уровень тестирования понизить. ·>Нет. Интеграционные тесты должны протестировать интеграцию, а не весь апи. P>>>>Например, ваш бл-сервис неявно зависит от бд-клиента, через репозиторий. А это значит, смена бд сначала сломает ваш репозиторий, а это сломает ваш бл сервис. P>>·>Не понял, что значит "сломает"? Что за такая "смена бд"? P>>Как ваш репозиторий фильтры принимает ? ·>Не очень знаком с этой терминологией. P>>·>Зачем? В тесте контрллера используется бл, поэтому моки только для него. И что творится в репе с т.з. контроллера - совершенно неважно. P>>Если у вас полная изоляция, то так и будет. А если неполная, что чаще всего, то будет всё сломано. P>>Собственно - что бы добиться этой самой хорошей изоляции в коде девелопера надо тренировать очень долго. ·>Если изоляция нужна, можно использовать интерфейсы. P>>>>:-) прямых - одна, а непрямых - все дочерние от того же репозитория P>>·>Неясно как "непрямые" зависимости влияют на _дизайн_ класса. Он их вообще никак не видит. Никак. P>>Я вот регулярно вижу такое - контроллер вызывает сервис, передает тому объекты http request response. А вот сервис, чья задача это бл, вычитывает всё из req, res, лезет в базу пополам с репозиторием, и еще собственно бл считает, плюс кеширование итд. P>>То есть, типичный разработчик не умеет пользоваться абстракциями и изоляцией, а пишет прямолинейный низкоуровневый код. И у него нет ни единого шанса протестировать такое чудовище кроме как моками. ·>Ну ликбез же. P>>>>Ничего странного - вы как то прошли мимо clean architecture. P>>·>Я как-то мимо rest и ui прошел в принципе... если есть какая ссылка "clean architecture for morons", давай. P>>https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html ·>Похоже это просто N-ая итерация очередной вариации самой правильной организации трёх слоёв типичного веб-приложения. MVC, MVP, MVI, MVVM, MVVM-C, VIPER, и ещё куча... ·>Причём тут моки - неясно. ·>Я вообще в последние N лет редко занимаюсь UI/web. У меня всякая поточка, очереди, сообщения, low/zero gc и прочее. Как там этот getUser сделать с zero gc - тот ещё вопрос. ·>Моки же общий инструмент, от деталей архитектуры зависит мало. P>>>>Я не знаю, в чем вы проблему видите. В жээс аннотация это обычный код, который инструментируется, и будет светиться или красным, или зеленым P>>·>В js вроде вообще нет аннотаций. Не знаю что именно ты зовёшь аннотацией тогда. P>>[code] P>> @Operation({method: 'PATCH', route:'/users', summary: 'modify user', responseType: User, }) P>> modify(@Req() req: RawBodyRequest<User>): Promise<User> { P>> ... P>> } P>>[/code] ·>А что за версия яп? Ну не важно. Это ещё можно простить, это просто маппинг урлов на объекты при отсутствии схемы REST. Как туда запихать аудит? Как аудит-аннотация будет описывать что, куда, когда и как аудитить? P>>·>И это всё юнит-тестится как ты обещал?! [tt]expext(Metadata.from(ControllerX, 'tryAccept')).metadata.to.match({...})[/tt]? P>>Смотря что - валидатор, это юнит-тест. А вот чтото нужна или нет авторизация, это можно переопределить конфигом или переменными окружения. P>>>>Зачем? Есть трассировка и правило - результат аудита можно сравнивать P>>·>Не понял к чему ты это говоришь. На вход метода подаются какие-то парамы. У тебя на нём висит аннотация. Откуда аннотация узнает что/как извлечь из каких парамов чтобы записать что-то в аудит? P>>Кое что надо и в самом методе вызывать, без этого работать не будет. ·>Именно! И накой тогда аннотация нужна для этого? P>> Видите нужные строчки - аудит работает, выхлоп совпадает с предыдущим забегом - точно все отлично. Не совпадает - точно есть проблемы. ·>Строчки где видим? Аудит может сообщения куда-нибудь в сокет слать или в бд отдельную писать. ·>И где валидировать, что для данных сценариев записываются нужная инфа в аудит? ·>Суть в том, что детали аудита каждой бизнес-операции можно покрывать туевой хучей отдельных юнит-тестов с моками на каждое возможное ожидание. И тут аннотации будут только мешаться. Интеграционный тест же можно написать один - что хоть какая-то бизнес-операция записала хоть какой-то аудит.
Теги:
Введите теги разделенные пробелами. Обрамляйте в кавычки словосочетания с пробелами внутри, например:
"Visual Studio" .NET
Имя, пароль:
Загрузить
Нравится наш сайт?
Помогите его развитию!
Отключить смайлики
Получать ответы по e-mail
Проверить правописание
Параметры проверки …