All streams
Search
Write a publication
Pull to refresh
@Druuread⁠-⁠only

User

Send message
> Нет, это indirect input.

Тогда я о ней не могу знать.

> Значит, вы тестируете не свой метод.

Нет, просто я не хочу привязывать тесты к реализации. Тесты проверяют спецификацию, спецификация не зависит от реализации.
> Все равно там будет столько объектов, сколько у меня тестовых сценариев. И выгоды по сравнению с «объяви объект прямо в тесте» нет.

Конечно же, есть. Вы объявляете все это только один раз (а не в каждом тесте).

> Просто считайте, что я тестирую не «читающее апи», а «маппящий метод».

Мапящий метод в бд не лезет, он о ней не в курсе. Он принимает какие-то данные и возвращает смапленные данные. Пишется он одинаково, что с моками, что без моков, просто потому что зависимостей не имеет (по крайней мере от бд, остальные мы на данный момент не обсуждаем).
> А вот и нет. В тесткейсе описана доменная сущность (метаданные Х), а не состояние БД. С

Если это доменная сущность, то она тогда в аргументах. В промежуточном слое ее быть не может, т.к. я просто не знаю ничего об этом промежуточном слое. Я знаю, что мой метод принимает определенные аргументы, лезет в бд, и что-то там возвращает. Я ничего не знаю о существовании (и особенностях работы) каких-либо промежуточных слоев.
То есть если я знаю о том, что метод работает с доменной сущностью — то значит этот метод в базу не лезет, в базу за него полез (и построил потом доменную сущность) кто-то другой и в аргументах передал.

> Пример кода.

Вы можете его увидеть в описании любого фреймворка для моков.
> Где-то объект плоский, где-то многоуровневый, где-то ссылки, где-то включения. Для всего этого в БД надо построить исходные данные.

Для всего этого достаточно одного набора тестовых данных. В котором будет и простой объект и многоуровневый, и что вам там надо еще, это-во-первых. Во-вторых — мапингом ваше «читающее апи» вообще заниматься не должно, это ответственность, как минимум, другого метода, как максимум — другого модуля, у которого данные для маппинга будут просто в аргументах. Вот в нем-то и будут тесты на логику маппинга.
> У меня тестовая проверка зависит от поведения зависимостей

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

> (самый тривиальный пример — API, читающий объект. Очевидно, он зависит от того, что в хранилище).

Результат зависит, а поведение? Нам же требуются разные тесткейзы с разными данными в хранилище. Почему вы не можете написать все тесты с теми же данными?
> Он выполняет задачу «подать на вход тестируемому объекту ровно те данные, которые описаны в тесткейсе».

Это аргументы метода и внешнее состояние (сервисов, бд, етц.). То, что возвращают те или иные зависимости, данными, конечно, не является, так как мы даже не знаем (и не должны) о том, какие зависимости данный метод тянет (черный ящик же).

> В следующем предложении было написано. Ну и у Мезароса тоже.

То, что там написано — по-просту неверно.

> Да, о качественном. И вот у качественного кода в моей практике мало зависимостей

Тогда либо у вас нарушается SRP, либо вам везет.

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

Я не понимаю что конкретно вы хотите. Чем этот код, по-вашему, должен отличаться от построения сетапа с нуля, кроме того, что часть сетапа оказывается опущена?
> И на его поддержку тоже нужны усилия.

конечно же нужны. Но благодаря тому, что нет многократного дублирования, их на порядок меньше.

> Значит, ваш сетап должен (заранее) содержать кейсы под все тесты. И чем это лучше «давайте зададим свой кейс в каждом тесте»?

Да нет, конечно. Я понял проблему, у вас, видимо, поведение зависимых объектов существенно зависит от поведения зависимостей? Тогда, конечно, надо делать много сетапов. Если же это не так, вам одного сетапа хватит на много-много тестов.
> А почему лучше?

Потому что нет кода, который не выполняет никакой задачи (мок сервиса в данном случае).

> Чтобы протестировать описанные кейсы.

Ну так тестируйте с моком БД.

> Антипаттерн shared fixture.

Почему это антипаттерн?

> Или нет. У меня на практике мало зависимостей (кроме тех классов, где я знаю, что нарушен SRP, и которые стоят в очереди на рефакторинг).

Если у вас весь весь функционал в куче, то, конечно, зависимостей мало. Но мы же о качественном коде говорим?

> Покажите пример, пожалуйста.

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

Еще раз, _сетап для теста_. Глобальный сетап возникает не сам, он пишется. Иногда он правится под тест-сьюиты — и очень редко под конкретные тесты.
> Так почему же не сервис?

Потому что лучше мокать только БД, чем и сервис и БД (вы же сам сервис тоже тестировать будете, это ваш код? или я неверно понял вопрос?)

> То есть тоже нужен мок?

Зачем?

> Глобальный сетап — зло.

В чем? Я вижу только добро — экономию при прочих равных.

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

Зачем для этого какая-то особая инфраструктура? Подмена куска сетапа не сложнее, чем его определение.

> … если зависимостей мало, то и моков мало. А если моков мало, то их сетап — это ровно то же самое, что подмена в глобальном. Так что никакого «в десятки меньше кода».

Но на практике их много, либо у вас у god-object'ы вместо классов.
> Это, простите, как? У класса, который не делает ничего, осмысленных зависимостей быть не может.

Если он сам не делает ничего, то он делегирует кому-то всю полезную работу.

> Таким, что в каждом тесте сетапится только то, что нужно для его выполнения.

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

Я ответил. Мокать БД. Просто сделал на всякий случай пояснение — если по каким-то исключительным причинам вы можете легко поднять и без проблем использовать полноценную тестовую БД — то мокать и вовсе ничего не надо (но это чисто умозрительная ситуация, понятно, что в реальности такое представить сложно).

> Мне надо проверить, что SUT корректно себя ведет, если его зависимость (внутренняя!) ведет себя некорректно. Как это сделать без мока?

Никак. А зачем решать бесполезные задачи?

> То есть вы предлагаете писать тесты на А, Б, Ц и Д?

Те же самые тесты, я же указал.

> Тогда у вас получается больше кода, а не меньше, потому что тесты те же, только еще и для А и Б надо засетапить все, что нужно для Ц и Д.

Сетапить вообще ничего не надо кроме глобального сетапа.

> Так в разных тестах разное поведение нужно, вообще-то. Какие тут глобальные моки?

Если надо подменить какую-то конкретную часть сетапа — ну не проблема, подменяйте. В любом случае, это обычно требует в десятки меньше кода, чем на полноценный сетап всех моков.
> Ну так моки упрощают поддержку тестов.

Каким образом? Надо тратить лишнее время на поддержку моков и их синхронизацию с реальным поведением зависимостей. Чем это проще?

> А много зависимостей (обычно) возникает тогда, когда много ответственностей. О чем и речь.

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

И, наоборот, чем больше ответственности — тем меньше зависимостей, с god-object в пределе, который делает все и у которого практически нет зависимостей.
> Допустим, ваш тест показал, что сумма контракта считается неверно. За ее расчет отвечает около сотни классов. Тривиально?

А юнит-тесты вам как помогут? Если какой-то из юнит-тестов падает на сломанной зависимости, то он и в интеграционном виде на ней упадет и вы увидите ошибку там же. Если же не падает — ну тогда в случае интеграционных тестов вы эту ошибку увидите, а в случае юнит-тестов — вообще нет, она будет пропущена.

> А еще лучше — обнаружить две хорошо локализованные ошибки, что наличие юнит-тестов и позволяет сделать.

Конечно, лучше. И еще лучше, если бы не надо было совершать кучу лишних телодвижений для моков. Но такую методологию тестирования еще не придумали. Либо мы пишем простые и качественные тесты, которые, однако, чуть менее точно локализуют ошибку, либо сложные и некачественные, которые локализуют ошибку лучше.
> Как вы делите код на «лишний» и «нелишний»?

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

> Окажет. Проблема-то возникает только тогда, когда мы вынуждены для теста сетапить те зависимости, которые к тесту отношения не имеют.

Проблема возникает, когда много зависимостей. Единственный способ ощутимо снизить количество зависимостей — это просто перебором построить такой граф, чтобы их было минимум. Но перетасовывать функционал модулей, нарушая их семантику, ради снижения количества зависимостей, чтобы было проще потестировать, хотя можно это не делать — весьма странное решение.
> И каким образом у вас вариант с моками оказался больше?

С моками вам надо настраивать моки, без моков — соответственно, не надо.

> С моками досточно имитировать непосредственные зависимости, без моков — надо построить все зависимости в графе.

Надо мокировать только что, что надо мокировать в итоге (какие-то внешние зависимости), а не все в графе. И делается это один раз. Под какие-то тесть-сьюиты могут вноситься, конечно, какие-то необходимые изменения, но в самих тестах уже ничего дополнительно делать практически никогда не надо. В итоге лишнего кода в разы меньше.
> Мне мокировать БД или этот сервис? И почему?

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

> И еще — локальные зависимости ошибаться не могут?

Могут, конечно же, но вероятность того, что два модуля согласованно ошибутся так, чтобы съесть ошибку, весьма мала.

> А вот теперь смотрите: у вас есть модули А и Б, оба зависят от Ц и Д. Мы решили, по вашей методике, сэкономить тесты, и написали только интеграционные тесты на А и Б (тем самым Ц и Д тестируются имплицитно).

Конечно же, мы не пишем тесты только на А и Б, мы пишем все те же самые тесты, что писали бы и в случае использования моков. Просто мы «искаробки» получаем некоторое дополнительное, как вы выразились, «имплицитное», тестирование зависимых модулей, что позволит поймать больше багов в этих модулях. То есть не «меньше тестов при том же результате», а «выше результат при тех же тестах».

> Значит, в вашем тесте на Х есть три мока (З1, З2, З3) вместо одного (У).

Так это глобальные моки. Они настраиваются раз и для всех тестов.
Если вы разобьете класс и снизите число зависимостей, то это никакого влияния на результат не окажет, т.к. эти зависимости просто будут устанавливаться _в других_ тестах. Вы перенесли код из одного места в другое. Да, это может в итоге привести к некоему упрощению (хоть и не всегда), но проблемы не решает — как куча лишнего кода была, так и осталась.

Ну и, да, нет ничего хуже, чем портить архитектуру приложения ради того, чтобы оно было «тестируемей». Сам факт того, что это приходится делать, уже говорит о том, что что-=то пошло не так.
> вход — конверсия по метаданным из БД

Давайте сразу определимся — внешнее окружение (бд, удаленные сервисы и т.д.), естественно следует мокировать, это попадает под «использовать только в тех случаях, когда без них не обойтись».

> А почему поймано больше багов?..

Простой пример, у вас есть один и тот же тест на некоторый модуль, вы его запускаете в двух форматах — либо заменив зависимости моками, либо не заменив. В первом случае выполняется только код непосредственно тестируемого модуля, а во втором случае — и код всех используемых зависимостей, то есть один интеграционный тест заменяет 1+количество_используемых_зависимостей юнит-тестов. Кроме того, вы просто можете забыть обновить моки, в итоге с реальными зависимостями тест падает (т.к. их поведение изменилось), а с моками — проходит. Естественно, может быть обратная ситуация — когда несколько багов, взаимонакладываясь друг на друга, в итоге дают правильное поведение — но это очень большая редкость по сравнению с предыдущими двумя пунктами
> Например, у вас есть простой однострочный тест, который ловит абсолютно все ошибки (идеал по вашим же критериям). Вот только искать, где именно ошибка, вам придется самому и тест, несмотря на высочайшую «оценку качества», не сможет здесь помочь.

Тест укажет, в чем состоит ошибка (какое именно требование нарушено), а если известно, в чем ошибка, то ее локализация (в тех рамках, в которых она может быть выполнена за счет изоляции тестов) — тривиальная задача, которая даже в самых сложных случаях решается за время порядка единиц минут, обычно же — секунды.

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

Information

Rating
Does not participate
Registered
Activity