Информация об изменениях

Сообщение Re[15]: Что такое Dependency Rejection от 05.12.2023 19:18

Изменено 05.12.2023 19:30 Pauel

Re[15]: Что такое Dependency Rejection
Здравствуйте, ·, Вы писали:

P>>·>Лучше код покажи. Так что-то не очень ясно.

·>
P>>    expect(crypto.createHmac).toBeCalledWith('sha256', 'API_TOKEN');
·>

·>Мде... Или это "особенности" js?.. Но допустим. Как ты это предлагаешь переделать с "value"?

Например, нам нужно считать токен для стрима, нас интересует ид класса, ид сущность, версия сущность b секрет.

const stream = Stream.from({Sample.id, Sample.version, Sample.content});

expect(Token.from(stream, Sample.secret)).to.eq(Sample.sha256);


Теперь вы можете покрыть все кейсы — 1-3 успешных, и ведёрко фейлов типа ид, секрет, версия не установлены.
И теперь в приложении вам не надо мокать Token.from, т.к. вы знаете, что она соответствует нашим ожиданиям, и не надо проверять, что контроллер стрима вызывает Token.from если ему необходимо отрендерить ETag.
Все что вам необходимо — в интеграционны тестах вызвать тот самый контроллер и убедиться, что его значения совпадают с нашими.

P>>Ну да — проблемы нет а тесты стали красными. И так каждый раз.

·>Ничего не понял. Если использование уязвимой тормозной либы — не проблема, то зачем вообще код трогать?

ну вот представьте — либа слишком медленная. И вы находите ей замену. Идеальный результат — после замены тесты зеленые.
А вы непонятно с какой целью хотите что бы тесты сломались.
Что нового вы добавите в приложение починкой этих тестов?

P>>Иными словами я предлагаю использовать value тест где только можно, т.к. таких можно настрочить сколько угодно.

·>Ок, покажи код как правильно должно быть по-твоему.

Ниже будет пример подробный

·>Теперь мы используем её в коде:

·>
·>Result doSomething(Request r) {
·>  if(validateThing(r.part1)) {
·>    wroom(r.zcxv);
·>    if(validateThing(r.part2) {
·>      return NiceResult(r.a, r.b);
·>    } else {
·>      foBarBaz();
·>      return SoSoResult(meh(r));
·>    }
·>  } else {
·>    taramparam(r.aaa);
·>    return NoNoNo(zzzz(r));
·>  }
·>}
·>

·>Вот тут нам совершенно неважно что и как делает validateThing — тут нам надо всего три сценария рассмотреть:
·>1. validateThing(part1) => returns false;
·>2. validateThing(part1) => returns true; validateThing(part2) => returns false;
·>3. validateThing(part1) => returns true; validateThing(part2) => returns true;
·>вот это и будет замокано.

Что я вам и говорю — вы сделали дизайн под моки перемешивая изменения с вычислениями
Вместо вот такого:
     foBarBaz(); <- здесь у вас сайд эффекты
     return SoSoResult(meh(r));

у вас может быть следующее
     const toDo = calculate(); // покрыто юнит тестами, т.к. простые вычисления без сайд эффектов
     
     return SoSoChanges(todo);

а на вызывающей стороне вы вызываете какой нибудь result.applyTo(ctx)
Соответсвенно, все изменения у вас будут в конце, на самом верху, а до того — вычисления, которые легко тестируются.

·>Создаётся мок для класса Thing.

·>Дальше мокаешь методы и инжектишь в SUT.

Вот-вот, еще чтото надо куда то инжектить, вызывать, проверять, а вызвалось ли замоканое нужное количество раз, в нужной последовательности, итд итд итд
Итого — вы прибили тесты к реализации

P>>Вы там в своём уме? Чтобы замокать чего нибудь ради теста, вам нужно знание о реализации, следовательно, тесткейс автоматически становится вайтбоксом.

·>В общем случае: чтобы передать что-то методу для теста, тоже надо знание о реализации, иначе не узнаешь corner-case.

На мой взгляд это не нужно — самый минимум это ожидания вызывающей стороны. Всё остальное это если время некуда девать.

P>>Зачем?

·>Я понимаю код лучше, чем твои объяснения.

Я только-только поменял работу, к сожалению физически не могу показать типичный пример.

P>>В том то и проблема, что вы строите такой дизайн, что бы его потом моками тестировать. Потому и топите везде за моки.

·>Хочу посмотреть на твой дизайн.

Мой дизайн в основном в закрытых репозиториях. Пока что так. Опенсорсные проекты я только исследую или использую.

·>Важно то, что в этом tryAcceptComposition находится connectionString. Т.е. условно говоря твой "интеграционнный тест" полезет в реальную субд и будет безбожно тормозить.


Во первых — таких тестов у нас будет пару штук
Во вторых — моки не избавляют от необхидимости интеграционных тестов, в т.ч. запуска на реальной субд

>В коде с моками — был reservationsRepository и возможность отмежеваться от реальной субд чтобы тестировать что параметры поиска и создания резерваций верные


Вот я и говорю — вы сделали в бл зависимость от бд, а потом в тестах изолируетесь от нее. Можно ж и иначе пойти — изолироваться на этапе дизайна, тогда ничего мокать не надо

P>>Вы идете наоброт — выпихиваете всё в контроллер, тогда дешевыми юнит-тестами покрывать нечего, зато теперь надо контроллер обкладывать моками, чтобы хоть как то протестировать.

·>В некоторых случаях интеграционным тестом будет "код скомпилировался".

Если у вас много зависимостей — статическая типизация помогает слабовато.

P>>Если это не про моки, то вопросов еще больше. Каким образом иерархия контроллер <- сервис-бл <- репозиторий <- клиент-бд стала у вас вдруг плоской?

·>DI же, не?
·>
·>var db = new Db(connectionString);
·>var repo = new Repo(db);
·>var bl = new BlService(repo);
·>var ctl = new Controller(bl);
·>var server = new Server(port);
·>server.handle("/path", ctl);
·>server.start();
·>


Я вижу, что здесь вы создаёте ту самую глубокую иерархию — у вас сервис bl зависит от репозитория который зависит от bd.

Есть другой вариант — не вы передаете репозиторий в bl, а вызываете bl внутри т.н. use-case в терминологии clean architecture
тогда получается иерархия плоская — у контроллера кучка зависимостей, но репозиторий не зависит от бд, бл не зависит от репозитория

controller зависит от
* db
* repositotory
* bl

Вот наш юз кейс — интеграционный код, покрывается интеграционным тестом
const getUser = repository.getUser(id); < покрыто юнит тестом
const oldUser = db.execute(getUser);
const newUser = bl.modifyUser(oldUser); < покрыто юнит тестом
const updateUser = repository.updateUser(oldUser, newUser); < покрыто юнит тестом
const updatedUser = db.execute(updateUser); 

return updatedUser;


вот наш контроллер — интеграционный код, покрывается интеграционным тестом
handler(id, ctx, metadata) {
    const useCase = UpdateUser.from(ctx); < покрыто юнит тестом
    const serializer = Serializer.from(ctx, metadata) < покрыто юнит тестом
    const result = userCase.invoke(id); 

    return serializer.toResponse(result); < покрыто юнит тестом
}


P>>Я думал вы собираетесь замокать все зависимости контроллера, но вы похоже бьете все рекорды

·>Для юнит-теста Controller надо мокать только BlService.

вот это и проблема — у вас тест "как написано", т.к. далеко не факт, что ваш мок этого бл-сервиса соответствует реальному блсервису. Эта часть вообще никак не контролируется — и крайне трудно научить этому разработчикв.

Типичная проблема — моки крайне слабо напоминают тот самый класс.

P>>Совсем необязательно. Тесты это далеко не единственный инструмент в обеспечении качества. Потому покрывать код идея изначально утопичная. Покрывать нужно требования, ожидания вызывающей стороны, а не код.

·>Как? И как оценивать качество покрытия требований/ожиданий?

А как это QA делают? Строчка требований — список тестов, где она фигурирует.

P>>Для самого кода у нас много разных инструментов — от генерации по мат-модели, всяких инвариантов-пред-пост-условий и до код-ревью.

·>А метрики какие? Чтобы качество обеспечивать, его надо как-то измерять и контролировать.

Строчки это плохие метрики, т.к. цикломатическая сложность может зашкаливать как угодно

P>>Да хоть вот так, утрирую конечно

P>>
P>>curl -X POST -H "Content-Type: application/json" -d '{<резервирование>}' http://localhost:3000/api/v3/reservation
P>>

·>А где ассерт?

Это я так трохи пошутил — идея в том, что нам всего то нужен один тест верхнего уровня. он у нас тупой как доска — буквально как http реквест-респонс

P>>Постусловия к вам еще не завезли? Это блакбокс, только это совсем не тест.

·>А куда их завезли?

Много где завезли, в джава коде я такое много где видел. А еще есть сравнение логов — если у вас вдруг появится лишний аудит, он обязательно сломает выхлоп. Это тоже блекбокс

·>Это слишком тяжелый тест. Количество таких тестов должно быть минимально.


Разумеется — таких у нас по несколько штук на каждый роут

P>>Вы же моками обрезаете зависимости. Забыли? Каждая зависимость = причины для изменений.

·>Я не понял на какой вопрос этот ответ.

Вы спрашивали, почему короткая функция более устойчива по сравнению с maitred — потому, что у ней зависимостей меньше. Каждая зависимость, прямая или нет, это причина для изменения.
Например, ваш бл-сервис неявно зависит от бд-клиента, через репозиторий. А это значит, смена бд сначала сломает ваш репозиторий, а это сломает ваш бл сервис.
А раз вы его замокали для теста контроллера, то вам надо перебулшитить все тесты

P>>Вот с maitred все не просто — любое изменение дизайна затрагивает такой класс, тк у него куча зависимостей, каждая из которых может меняться по разными причнам.

·>Куча это сколько? Судя по коду в статье я там аж целых одну насчитал.

прямых — одна, а непрямых — все дочерние от того же репозитория

P>>И вам каждый раз надо будет подфикшивать тесты.

·>По-моему ты очень странно пишешь тесты и дизайнишь, от того и проблемы.

Ничего странного — вы как то прошли мимо clean architecture.

P>>Тогда не совсем понятен ваш вопрос.

·>https://en.wikipedia.org/wiki/Code_coverage

Я не знаю, в чем вы проблему видите. В жээс аннотация это обычный код, который инструментируется, и будет светиться или красным, или зеленым

P>>·>Самое что ни на есть вайтбокс тестирование. Что конкретный метод содержит конкретную аннотацию.

P>>Мы тестируем, что есть те или иные метаданные. А вот чем вы их установите — вообще дело десятое.
·>А как ещё на метод можно метаданные навесить? Или ты про js?

разными способами
1. аннотации метода
2. аннотации класса
3. аннотации контекста класса
4. скриптованием, например, на старте
5. конфигурация приложения
6. переменные окружения

P>>А вот метаданные это совсем не вайтбокс, это нефункциональные требования.

·>Зачем тогда их в эти тесты класть?..

затем и класть, что это часть требований. фиксируем капабилити

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

·>Тебе как-то рефлексивно придётся извлекать детали аудита из места вызова и параметров метода. На ЯП со статической типизацией будет полный швах.

Зачем? Есть трассировка и правило — результат аудита можно сравнивать
Re[15]: Что такое Dependency Rejection
Здравствуйте, ·, Вы писали:

P>>·>Лучше код покажи. Так что-то не очень ясно.

·>
P>>    expect(crypto.createHmac).toBeCalledWith('sha256', 'API_TOKEN');
·>

·>Мде... Или это "особенности" js?.. Но допустим. Как ты это предлагаешь переделать с "value"?

Например, нам нужно считать токен для стрима, нас интересует ид класса, ид сущность, версия сущность b секрет.

const stream = Stream.from({Sample.id, Sample.version, Sample.content});

expect(Token.from(stream, Sample.secret)).to.eq(Sample.sha256);


Теперь вы можете покрыть все кейсы — 1-3 успешных, и ведёрко фейлов типа ид, секрет, версия не установлены.
И теперь в приложении вам не надо мокать Token.from, т.к. вы знаете, что она соответствует нашим ожиданиям, и не надо проверять, что контроллер стрима вызывает Token.from если ему необходимо отрендерить ETag.
Все что вам необходимо — в интеграционны тестах вызвать тот самый контроллер и убедиться, что его значения совпадают с нашими.

P>>Ну да — проблемы нет а тесты стали красными. И так каждый раз.

·>Ничего не понял. Если использование уязвимой тормозной либы — не проблема, то зачем вообще код трогать?

ну вот представьте — либа слишком медленная. И вы находите ей замену. Идеальный результат — после замены тесты зеленые.
А вы непонятно с какой целью хотите что бы тесты сломались.
Что нового вы добавите в приложение починкой этих тестов?

P>>Иными словами я предлагаю использовать value тест где только можно, т.к. таких можно настрочить сколько угодно.

·>Ок, покажи код как правильно должно быть по-твоему.

Ниже будет пример подробный

·>Теперь мы используем её в коде:

·>
·>Result doSomething(Request r) {
·>  if(validateThing(r.part1)) {
·>    wroom(r.zcxv);
·>    if(validateThing(r.part2) {
·>      return NiceResult(r.a, r.b);
·>    } else {
·>      foBarBaz();
·>      return SoSoResult(meh(r));
·>    }
·>  } else {
·>    taramparam(r.aaa);
·>    return NoNoNo(zzzz(r));
·>  }
·>}
·>

·>Вот тут нам совершенно неважно что и как делает validateThing — тут нам надо всего три сценария рассмотреть:
·>1. validateThing(part1) => returns false;
·>2. validateThing(part1) => returns true; validateThing(part2) => returns false;
·>3. validateThing(part1) => returns true; validateThing(part2) => returns true;
·>вот это и будет замокано.

Что я вам и говорю — вы сделали дизайн под моки перемешивая изменения с вычислениями
Вместо вот такого:
     foBarBaz(); <- здесь у вас сайд эффекты
     return SoSoResult(meh(r));

у вас может быть следующее
     const toDo = calculate(); // покрыто юнит тестами, т.к. простые вычисления без сайд эффектов
     
     return SoSoChanges(todo);

а на вызывающей стороне вы вызываете какой нибудь result.applyTo(ctx)
Соответсвенно, все изменения у вас будут в конце, на самом верху, а до того — вычисления, которые легко тестируются.

·>Создаётся мок для класса Thing.

·>Дальше мокаешь методы и инжектишь в SUT.

Вот-вот, еще чтото надо куда то инжектить, вызывать, проверять, а вызвалось ли замоканое нужное количество раз, в нужной последовательности, итд итд итд
Итого — вы прибили тесты к реализации

P>>Вы там в своём уме? Чтобы замокать чего нибудь ради теста, вам нужно знание о реализации, следовательно, тесткейс автоматически становится вайтбоксом.

·>В общем случае: чтобы передать что-то методу для теста, тоже надо знание о реализации, иначе не узнаешь corner-case.

На мой взгляд это не нужно — самый минимум это ожидания вызывающей стороны. Всё остальное это если время некуда девать.

P>>Зачем?

·>Я понимаю код лучше, чем твои объяснения.

Я только-только поменял работу, к сожалению физически не могу показать типичный пример.

P>>В том то и проблема, что вы строите такой дизайн, что бы его потом моками тестировать. Потому и топите везде за моки.

·>Хочу посмотреть на твой дизайн.

Мой дизайн в основном в закрытых репозиториях. Пока что так. Опенсорсные проекты я только исследую или использую.

·>Важно то, что в этом tryAcceptComposition находится connectionString. Т.е. условно говоря твой "интеграционнный тест" полезет в реальную субд и будет безбожно тормозить.


Во первых — таких тестов у нас будет пару штук
Во вторых — моки не избавляют от необхидимости интеграционных тестов, в т.ч. запуска на реальной субд

>В коде с моками — был reservationsRepository и возможность отмежеваться от реальной субд чтобы тестировать что параметры поиска и создания резерваций верные


Вот я и говорю — вы сделали в бл зависимость от бд, а потом в тестах изолируетесь от нее. Можно ж и иначе пойти — изолироваться на этапе дизайна, тогда ничего мокать не надо

P>>Вы идете наоброт — выпихиваете всё в контроллер, тогда дешевыми юнит-тестами покрывать нечего, зато теперь надо контроллер обкладывать моками, чтобы хоть как то протестировать.

·>В некоторых случаях интеграционным тестом будет "код скомпилировался".

Если у вас много зависимостей — статическая типизация помогает слабовато.

P>>Если это не про моки, то вопросов еще больше. Каким образом иерархия контроллер <- сервис-бл <- репозиторий <- клиент-бд стала у вас вдруг плоской?

·>DI же, не?
·>
·>var db = new Db(connectionString);
·>var repo = new Repo(db);
·>var bl = new BlService(repo);
·>var ctl = new Controller(bl);
·>var server = new Server(port);
·>server.handle("/path", ctl);
·>server.start();
·>


Я вижу, что здесь вы создаёте ту самую глубокую иерархию — у вас сервис bl зависит от репозитория который зависит от bd.

Есть другой вариант — не вы передаете репозиторий в bl, а вызываете bl внутри т.н. use-case в терминологии clean architecture
тогда получается иерархия плоская — у контроллера кучка зависимостей, но репозиторий не зависит от бд, бл не зависит от репозитория

controller зависит от
* db
* repositotory
* bl

Вот наш юз кейс — интеграционный код, покрывается интеграционным тестом
const getUser = repository.getUser(id); // покрыто юнит тестом
const oldUser = db.execute(getUser);
const newUser = bl.modifyUser(oldUser); // покрыто юнит тестом
const updateUser = repository.updateUser(oldUser, newUser); // покрыто юнит тестом
const updatedUser = db.execute(updateUser); 

return updatedUser;


вот наш контроллер — интеграционный код, покрывается интеграционным тестом
handler(id, ctx, metadata) {
    const useCase = UpdateUser.from(ctx); // покрыто юнит тестом
    const serializer = Serializer.from(ctx, metadata) // покрыто юнит тестом
    const result = userCase.invoke(id); 

    return serializer.toResponse(result); // покрыто юнит тестом
}


P>>Я думал вы собираетесь замокать все зависимости контроллера, но вы похоже бьете все рекорды

·>Для юнит-теста Controller надо мокать только BlService.

вот это и проблема — у вас тест "как написано", т.к. далеко не факт, что ваш мок этого бл-сервиса соответствует реальному блсервису. Эта часть вообще никак не контролируется — и крайне трудно научить этому разработчикв.

Типичная проблема — моки крайне слабо напоминают тот самый класс.

P>>Совсем необязательно. Тесты это далеко не единственный инструмент в обеспечении качества. Потому покрывать код идея изначально утопичная. Покрывать нужно требования, ожидания вызывающей стороны, а не код.

·>Как? И как оценивать качество покрытия требований/ожиданий?

А как это QA делают? Строчка требований — список тестов, где она фигурирует.

P>>Для самого кода у нас много разных инструментов — от генерации по мат-модели, всяких инвариантов-пред-пост-условий и до код-ревью.

·>А метрики какие? Чтобы качество обеспечивать, его надо как-то измерять и контролировать.

Строчки это плохие метрики, т.к. цикломатическая сложность может зашкаливать как угодно

P>>Да хоть вот так, утрирую конечно

P>>
P>>curl -X POST -H "Content-Type: application/json" -d '{<резервирование>}' http://localhost:3000/api/v3/reservation
P>>

·>А где ассерт?

Это я так трохи пошутил — идея в том, что нам всего то нужен один тест верхнего уровня. он у нас тупой как доска — буквально как http реквест-респонс

P>>Постусловия к вам еще не завезли? Это блакбокс, только это совсем не тест.

·>А куда их завезли?

Много где завезли, в джава коде я такое много где видел. А еще есть сравнение логов — если у вас вдруг появится лишний аудит, он обязательно сломает выхлоп. Это тоже блекбокс

·>Это слишком тяжелый тест. Количество таких тестов должно быть минимально.


Разумеется — таких у нас по несколько штук на каждый роут

P>>Вы же моками обрезаете зависимости. Забыли? Каждая зависимость = причины для изменений.

·>Я не понял на какой вопрос этот ответ.

Вы спрашивали, почему короткая функция более устойчива по сравнению с maitred — потому, что у ней зависимостей меньше. Каждая зависимость, прямая или нет, это причина для изменения.
Например, ваш бл-сервис неявно зависит от бд-клиента, через репозиторий. А это значит, смена бд сначала сломает ваш репозиторий, а это сломает ваш бл сервис.
А раз вы его замокали для теста контроллера, то вам надо перебулшитить все тесты

P>>Вот с maitred все не просто — любое изменение дизайна затрагивает такой класс, тк у него куча зависимостей, каждая из которых может меняться по разными причнам.

·>Куча это сколько? Судя по коду в статье я там аж целых одну насчитал.

прямых — одна, а непрямых — все дочерние от того же репозитория

P>>И вам каждый раз надо будет подфикшивать тесты.

·>По-моему ты очень странно пишешь тесты и дизайнишь, от того и проблемы.

Ничего странного — вы как то прошли мимо clean architecture.

P>>Тогда не совсем понятен ваш вопрос.

·>https://en.wikipedia.org/wiki/Code_coverage

Я не знаю, в чем вы проблему видите. В жээс аннотация это обычный код, который инструментируется, и будет светиться или красным, или зеленым

P>>·>Самое что ни на есть вайтбокс тестирование. Что конкретный метод содержит конкретную аннотацию.

P>>Мы тестируем, что есть те или иные метаданные. А вот чем вы их установите — вообще дело десятое.
·>А как ещё на метод можно метаданные навесить? Или ты про js?

разными способами
1. аннотации метода
2. аннотации класса
3. аннотации контекста класса
4. скриптованием, например, на старте
5. конфигурация приложения
6. переменные окружения

P>>А вот метаданные это совсем не вайтбокс, это нефункциональные требования.

·>Зачем тогда их в эти тесты класть?..

затем и класть, что это часть требований. фиксируем капабилити

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

·>Тебе как-то рефлексивно придётся извлекать детали аудита из места вызова и параметров метода. На ЯП со статической типизацией будет полный швах.

Зачем? Есть трассировка и правило — результат аудита можно сравнивать