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

Сообщение Re[20]: Что такое Dependency Rejection от 23.12.2023 14:26

Изменено 23.12.2023 14:30 Буравчик

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

Б>>Что значит дизайн такой? Это логика приложения такая, как ее по другому задизайнить?


P>То и значит — вы приняли решениче, что все будет в одной функции мутабельным вперемешку с тяжелыми зависимостями.это и есть ваш дизайн


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

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

P>Похоже, что моки про которые вы говорите, это фаулеровские стабы


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

Используются в основном стабы. Они просто заменяют в рамках теста тяжелое поведение на что-то легкое.

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

P>Варианты подходов к тестированию, я их вам показал. Ветвления влияют на количество тестов, но не на подход к тестированию

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

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

P>>>Проблема в самих зависимостях, их много, и в том, что ваш дизайн мутабельный

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

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

P>Потом преобразуете метод в класс, v1,v2,v3 уходят в члены класса


P>
P>class Logic(v1, v2, v3):  // параметры-значения в конструкторе
P>  def run(load, load2, commit): // эффекты-зависимости здесь, эдакий аналог импорта
P>     p = ctx.load(self.v1) 

P>     if что-то(p):  
P>       p2 = ctx.load2(self.v2) // load2 - абстракция так себе, должно быть хорошее имя
P>       p = update(p, p2)

P>     if что-то(p):   
P>       ctx.commit(self.v3)
      
P>     return p
P>


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

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

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

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


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

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

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

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

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


P>Если не хотите возиться с интерфейсом — можно вытащить repo.get и подобное в методы рядом с do_logic. Идея на самом деле так себе, этот подход стоит применять если у вас не сильно много зависимостей

P>
P>do_logic(v1, v2, v3):
P>  p = self.load(v1)

P>  if что-то(p):  
P>    p2 = self.load2(v2)
P>    p = update(p, p2)

P>  if что-то(p):   
P>    self.commit(v3)

P>  result = serialize(p) 
P>  return result
P>


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


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

Да, часть сложности перекладывается на тесты, из-за моков, но не критично.

P>А вот зависимости как параметры вы можете понасоздвать или нагенерить на все случаи жизни

P>1 ошибки всех сортов
P>2 пустой результат
P>3 куча вариантов ответа
P>4 куча сочетаний того и этого

Зависимости отделены также, как и у тебя. В твоем случае в тесты ты передаешь их как параметры-функции, а я подменяю поведение через стаб (по-сути ту же функцию). И в обоих случаях проверяются все варианты
Re[20]: Что такое Dependency Rejection
Здравствуйте, Pauel, Вы писали:

Б>>Что значит дизайн такой? Это логика приложения такая, как ее по другому задизайнить?


P>То и значит — вы приняли решениче, что все будет в одной функции мутабельным вперемешку с тяжелыми зависимостями.это и есть ваш дизайн


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

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

P>Похоже, что моки про которые вы говорите, это фаулеровские стабы


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

Используются в основном стабы. Они просто заменяют в рамках теста тяжелое поведение на что-то легкое.

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

P>Варианты подходов к тестированию, я их вам показал. Ветвления влияют на количество тестов, но не на подход к тестированию

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

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

P>>>Проблема в самих зависимостях, их много, и в том, что ваш дизайн мутабельный

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

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

P>Потом преобразуете метод в класс, v1,v2,v3 уходят в члены класса


P>
P>class Logic(v1, v2, v3):  // параметры-значения в конструкторе
P>  def run(load, load2, commit): // эффекты-зависимости здесь, эдакий аналог импорта
P>     p = ctx.load(self.v1) 

P>     if что-то(p):  
P>       p2 = ctx.load2(self.v2) // load2 - абстракция так себе, должно быть хорошее имя
P>       p = update(p, p2)

P>     if что-то(p):   
P>       ctx.commit(self.v3)
      
P>     return p
P>


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

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

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

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


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

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

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

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

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


P>Если не хотите возиться с интерфейсом — можно вытащить repo.get и подобное в методы рядом с do_logic. Идея на самом деле так себе, этот подход стоит применять если у вас не сильно много зависимостей

P>
P>do_logic(v1, v2, v3):
P>  p = self.load(v1)

P>  if что-то(p):  
P>    p2 = self.load2(v2)
P>    p = update(p, p2)

P>  if что-то(p):   
P>    self.commit(v3)

P>  result = serialize(p) 
P>  return result
P>


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


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

Да, часть сложности перекладывается на тесты, из-за моков, но не критично.

P>А вот зависимости как параметры вы можете понасоздвать или нагенерить на все случаи жизни

P>1 ошибки всех сортов
P>2 пустой результат
P>3 куча вариантов ответа
P>4 куча сочетаний того и этого

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