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

Сообщение Re[21]: Что такое Dependency Rejection от 23.12.2023 19:46

Изменено 25.12.2023 8:08 Pauel

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

Б>Ну, у тебя (будем на ты?) то же самое написано. У тебя тяжелые зависимости вынесены в функции, а у меня вынесены в отдельные классы, закрыты интерфейсами (они в дальнейшем и мокаются), передаются как параметры.


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

Б>Моки позволяют отрезать эти тяжелые вычисления и заменить их на простое поведение для теста.


Вопрос в том, сколько вам кода для этих моков надо. Если у вас, скажем, три раза подряд идет вызов repo.get вам придется заниматься глупостями —
писать чтото навроде OnCall(3).Returnо(that) или писать кастомную вещь.

Собственно я вам привел пример, как вроде бы те же моки можно свести к простым тестам. Кода для моков около нуля, в отличие от вашего варианта.

Б>Стабы/моки — вопрос терминологии. Я называю моками все разновидности моков, а стабами те, которые работают как "функция" — получили результат, обработали, вернули нужны.


Стабы подкидывают значения. Моки — это тесты вида "проверить, что вызвали то и это"

Б>Моки, для которых проверяется факт вызова "с параметров а-б-в", тоже изредка могут применяться в основном для проверки отправки сообщений, где ответ не важен (в кафку, аудит).


Б>Про дизайн. Подход с моками как раз не навязывает дизайн (не вынуждают выделять функции).


Наоборон — моками вы бетонируете конкретный дизайн. Там где вы тестами на моках проверяете что repo.get вызвался с тем или иным параметром, изменить дизайн на другой вы можете только через поломку всех таких тестов. Даже если просто укажете, что "функция использует репозиторий", у вас будут поломки на ровном месте из за смены дизайна.
То еть — моки ограничивают вас в выборе

Б>Где уместно, я создам чистую функцию и тесты (обычно для алгоритмов бизнес-логики). Где не уместно — буду использовать моки (обычно для логики приложения).

Б>Т.е. любую сложную логику при желании могу протестить юнит-тестами

И тесты и дизайн кода влияют друг на друга. Если вы избавитесь от моков, внезапно окажется, что можно менять дизайн, а не бояться, что надо перебулшитить все тесты.

Б>Они у меня эти зависимости отделены. Выделены в отдельные классы (репозитории и т.п.), прокидываются как параметры.


P>>Теперь в тестах вы можете создать n троек значений, m результатов операций

P>>И покроется все одной таблицей тестов n x m

Б>Как тест будет написан? Как подготовлен, как будут передаваться эти n x m, как проверен?


Нам нужно описать все значения, табличкой. Часть значений протаскиваются через параметры, часть — через лямбды.
У нас их шесть штук, v1,v2,v3, repo1, svc, repo2,
Дальше у вас будет один параметризованый тест типа такого, не знаю как на вашем питоне это выглядит, напишу на жээсе

[code
@test()
@params(
1,2,3,4,5,6 // и так на каждую строчку в табличке
)
@params(
8,4,2,1,3,5 // точно так же и исключения прокидывают
)
... сколько строчек, столько и тестов фактически выполнится
"a cool name test name" (v1, v2, v3, repo1, svc, repo2, result) {
let logic = Logic(v1,v2,v3);

let r = logic.run({
load: () => repo1,
load2: () => svc,
... и так прокидываем все что надо
})

expect(r).to.deep.eq(result)
}
[/code]


P>>Любители моков правда очень обрадуются, умножат(sic!) Количество тестов за счет проверок "commit вызывается после двух load"


Б>Это твое (неправильное) понимание


Это типичная картина, к сожалению. Еще могут оставить только такие тесты.

P>>Теперь ваш мок repo.get должен считать вызовы — что его вызвали первый раз с v1, второй раз с v2, и это важно, т.к на втором разе вы должны вернуть кое что другое

P>>И это именно то, чего делать не стоит — в сложной логике ваш мок репозитория будет чудовищем, которое мало будет походить на реальную часть системы
P>>Чем более унивесальная зависимость, тем труднее её мокать, и тем выше шансы на ошибку

Б>Для этого теста (который будет проверять эту ветку логики) для операции repo.get будет написан такой стаб:


Б>
Б>if v1:
Б>  return value1
Б>else if v2:
Б>  return value2
Б>else:
Б>  raise
Б>


а нам всего то надо подкинуть два значения
() => value1
() => value2


Б>Да, выглядит компактно и понятно. И никто не заставляет наворачивать дизайн как у тебя — с дополнительными функциями, от которых код становится сложнее.


А я вас и не заставляю Большей частью у меня получается избавиться от моков, заменить на проверку значения или состояния. Они самые дешовые, их можно за короткое время настрочить десятки и сотни.

Б>Зависимости отделены также, как и у тебя. В твоем случае в тесты ты передаешь их как параметры-функции, а я подменяю поведение через стаб (по-сути ту же функцию). И в обоих случаях проверяются все варианты


Ну так посмотрите — у меня две строчки, у вас шесть.
Re[21]: Что такое Dependency Rejection
Здравствуйте, Буравчик, Вы писали:

Б>Ну, у тебя (будем на ты?) то же самое написано. У тебя тяжелые зависимости вынесены в функции, а у меня вынесены в отдельные классы, закрыты интерфейсами (они в дальнейшем и мокаются), передаются как параметры.


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

Б>Моки позволяют отрезать эти тяжелые вычисления и заменить их на простое поведение для теста.


Вопрос в том, сколько вам кода для этих моков надо. Если у вас, скажем, три раза подряд идет вызов repo.get вам придется заниматься глупостями —
писать чтото навроде OnCall(3).Returnо(that) или писать кастомную вещь.

Собственно я вам привел пример, как вроде бы те же моки можно свести к простым тестам. Кода для моков около нуля, в отличие от вашего варианта.

Б>Стабы/моки — вопрос терминологии. Я называю моками все разновидности моков, а стабами те, которые работают как "функция" — получили результат, обработали, вернули нужны.


Стабы подкидывают значения. Моки — это тесты вида "проверить, что вызвали то и это"

Б>Моки, для которых проверяется факт вызова "с параметров а-б-в", тоже изредка могут применяться в основном для проверки отправки сообщений, где ответ не важен (в кафку, аудит).


Б>Про дизайн. Подход с моками как раз не навязывает дизайн (не вынуждают выделять функции).


Наоборон — моками вы бетонируете конкретный дизайн. Там где вы тестами на моках проверяете что repo.get вызвался с тем или иным параметром, изменить дизайн на другой вы можете только через поломку всех таких тестов. Даже если просто укажете, что "функция использует репозиторий", у вас будут поломки на ровном месте из за смены дизайна.
То еть — моки ограничивают вас в выборе

Б>Где уместно, я создам чистую функцию и тесты (обычно для алгоритмов бизнес-логики). Где не уместно — буду использовать моки (обычно для логики приложения).

Б>Т.е. любую сложную логику при желании могу протестить юнит-тестами

И тесты и дизайн кода влияют друг на друга. Если вы избавитесь от моков, внезапно окажется, что можно менять дизайн, а не бояться, что надо перебулшитить все тесты.

Б>Они у меня эти зависимости отделены. Выделены в отдельные классы (репозитории и т.п.), прокидываются как параметры.


P>>Теперь в тестах вы можете создать n троек значений, m результатов операций

P>>И покроется все одной таблицей тестов n x m

Б>Как тест будет написан? Как подготовлен, как будут передаваться эти n x m, как проверен?


Нам нужно описать все значения, табличкой. Часть значений протаскиваются через параметры, часть — через лямбды.
У нас их шесть штук, v1,v2,v3, repo1, svc, repo2,
Дальше у вас будет один параметризованый тест типа такого, не знаю как на вашем питоне это выглядит, напишу на жээсе

@test()
@params(
    1,2,3,4,5,6  // и так на каждую строчку в табличке
)
@params(
    8,4,2,1,3,5 // точно так же и исключения прокидывают
)
...   сколько строчек, столько и тестов фактически выполнится
"a cool name test name" (v1, v2, v3, repo1, svc, repo2, result) {
   let logic = Logic(v1,v2,v3);

   let r = logic.run({
      load: () => repo1,
      load2: () => svc,
      ... и так прокидываем все что надо
   }) 

   expect(r).to.deep.eq(result)
}



P>>Любители моков правда очень обрадуются, умножат(sic!) Количество тестов за счет проверок "commit вызывается после двух load"


Б>Это твое (неправильное) понимание


Это типичная картина, к сожалению. Еще могут оставить только такие тесты.

P>>Теперь ваш мок repo.get должен считать вызовы — что его вызвали первый раз с v1, второй раз с v2, и это важно, т.к на втором разе вы должны вернуть кое что другое

P>>И это именно то, чего делать не стоит — в сложной логике ваш мок репозитория будет чудовищем, которое мало будет походить на реальную часть системы
P>>Чем более унивесальная зависимость, тем труднее её мокать, и тем выше шансы на ошибку

Б>Для этого теста (который будет проверять эту ветку логики) для операции repo.get будет написан такой стаб:


Б>
Б>if v1:
Б>  return value1
Б>else if v2:
Б>  return value2
Б>else:
Б>  raise
Б>


а нам всего то надо подкинуть два значения
() => value1
() => value2


Б>Да, выглядит компактно и понятно. И никто не заставляет наворачивать дизайн как у тебя — с дополнительными функциями, от которых код становится сложнее.


А я вас и не заставляю Большей частью у меня получается избавиться от моков, заменить на проверку значения или состояния. Они самые дешовые, их можно за короткое время настрочить десятки и сотни.

Б>Зависимости отделены также, как и у тебя. В твоем случае в тесты ты передаешь их как параметры-функции, а я подменяю поведение через стаб (по-сути ту же функцию). И в обоих случаях проверяются все варианты


Ну так посмотрите — у меня две строчки, у вас шесть.