На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS

Поздравляем всех студентов и Татьян с Татьяниным днём!




msm.ru
! Правила раздела:
1. Название темы - краткое описание кто/что против кого/чего
2. В первом сообщении - список параметров, по которым идет сравнение.
3. Старайтесь аргументировать свои высказывания. Фразы типа "Венда/Слюникс - ацтой" считаются флудом.
4. Давайте жить дружно и не доводить обсуждение до маразма и личных оскорблений.
Модераторы: Модераторы, Комодераторы
  
> TDD vs не TDD
    Всем привет.

    Вот есть техника разработки ПО, именуемая TDD.

    Интересно стало много вообще кто ее использует? Очень часто встречаю ее в вакансиях, но везде где я был, про эту технику спрашивали либо очень слабо(какие то общие вопросы, типа что это вообще такое), либо не спрашивали вовсе, порой складывается впечатление, что им это нужно для галочки онли. Я как то пробовал писать по этой технике, но либо был не опытен конкретно в этой технике, либо не было времени, одни сплошные дедлайны, поэтому как то не пошло в свое время. Сейчас снова вернулся к этому, начал тут почитывать одну книжку. Наверное снова буду пробовать.

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

    Добавлено
    Ну и плюс еще статья с хабра в догонку(перевод - Автор оригинала: Eric Elliott), кому не лень почитать - https://habr.com/ru/company/ruvds/blog/450316/
    Из этой статьи для меня оказалось в новинку что наличие Мок-объектов в UT говорит о том, что с кодом что то не так, и что DI - это признак жесткой связи между объектами :blink:
    В прочем автор поясняет почему, в принципе с такой точкой зрения - я даже согласен. Но для меня это стало открытием на самом деле.
    Сообщение отредактировано: Wound -
      Цитата Wound @
      Или вас просто заставляют на работе писать по этой технике, но вы считаете ее оцтоем?

      Это. Но это на прошлой работе. На текущей — нет TDD и так во многих случаях удобней.

      Цитата Wound @
      Стало меньше времени уходить на разработку?

      ХЗ, бывает, что изменение требует значительно меньше кода, чем тестов, и приходится писать кучу однообразной мути, чтобы типа было покрыто тестами и т.п. Причём, ладно юнит-тесты, но функциональные были просто адом.

      Как по мне, значимость автоматического тестирования несколько преувеличена, а иногда оно приносит больше вреда, чем пользы, например, провоцирует писать quick&dirty код (тесты ж всё равно отловят ошибки, а на их написание и так уходит куча времени, чтоб ещё его тратить на качественный код/архитектуру, а синюю фазу рефакторинга на практике часто игнорируют).

      Конечно, это сильно зависит от сферы применения (всякое «mission-critical» ПО всё же лучше хорошо тестировать) и от «уровня» сервиса (например, фронтенд менее критичен к ошибкам, чем какие-нибудь РСУБД/ОС и прочая инфраструктура, которую использует сервис).

      Цитата Wound @
      Из этой статьи для меня оказалось в новинку что наличие Мок-объектов в UT говорит о том, что с кодом что то не так

      Я после работы в TDD-ориентированной компании пришёл к выводу, что моки вообще больше зло, чем добро, и не нужны. Стабы лучше. )

      Добавлено
      Ещё раздражает мантра «тесты — это спецификация». Возникает вопрос: а почему бы не писать код декларативно, чтобы он выглядел как спецификация и не требовал тестов? Не, опять же, бывают случаи, когда какой-нибудь числодробильный код не декларативен ради оптимизации времени выполнения и/или памяти, такие функции разумно покрыть тестами, да, тем более, что обычно это достаточно независимые функции и мокать/стабать/вообще-создавать-сколько-нибудь-сложное-окружение там не нужно.
        Цитата Wound @
        Кто по этой методике работает - хотелось бы услышать как изменилась ваша жизнь, после использования этой техники?

        Хотелось бы работать, но полноценно нигде, где я работал, это внедрено не было.

        Цитата
        Ну и вообще у кого какие мысли, высказывайте. Было бы интересно обсудить эту технику.

        Пока мысли такие, что это хорошая техника, но нужно с ней обязательно "пожить" какое-то продолжительное время, чтобы полноценно освоить.
        Когда я таки начну полноценно делать свои проектики, тогда точно буду юзать TDD. Дело за малым :D

        Цитата
        Из этой статьи для меня оказалось в новинку что наличие Мок-объектов в UT говорит о том, что с кодом что то не так

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

        Добавлено
        Цитата korvin @
        ХЗ, бывает, что изменение требует значительно меньше кода, чем тестов, и приходится писать кучу однообразной мути, чтобы типа было покрыто тестами и т.п.

        "типа покрыто" уже звучит как-то странно... Я так понимаю, что когда говорят об уменьшении времени, имеется в виду не врямя на разработку конкретной фичи, а скорее время на поддержку кода и пр. Сколько времени отладки и поиска ошибок экономит TDD. Ну и часто можно услышать, что он еще приводит к существенному сокращению критичных багов на проде.

        Добавлено
        Цитата korvin @
        Ещё раздражает мантра «тесты — это спецификация».Вс

        Все относительно же. В том смысл, что такое отношение к тестам лучше, чем "обычное", когда "напишем потом, сейчас функциональность важнее".

        Цитата
        Возникает вопрос: а почему бы не писать код декларативно, чтобы он выглядел как спецификация и не требовал тестов?

        Даже если сам код декларативный, это не означает, что он правильный. Тесты нужны в любом случае для проверки.
        Скажем, поменяли что-то в коде и "случайно" нарушили контракт, какая разница декларативно код написан или нет?
        Ну или я тебя не так понял.
          Цитата D_KEY @
          Сколько времени отладки и поиска ошибок экономит TDD.

          Практически нисколько.

          Цитата D_KEY @
          Ну и часто можно услышать, что он еще приводит к существенному сокращению критичных багов на проде.

          Каждый кулик… Оно отлавливает лишь самые простые, которых бы не было, если бы использовались нормальные языки (а не JS) и писался бы нормальный код. А нетривиальные баги всё равно проскакивают.

          Цитата D_KEY @
          В том смысл, что такое отношение к тестам лучше, чем "обычное", когда "напишем потом, сейчас функциональность важнее".

          Та ничем не лучше.

          Цитата D_KEY @
          Даже если сам код декларативный, это не означает, что он правильный. Тесты нужны в любом случае для проверки.

          Ну ахренеть теперь. А если тест есть — это не значит, что он (тест) правильный. А если в тесте нет какого-то кейса?

          Цитата D_KEY @
          Скажем, поменяли что-то в коде и "случайно" нарушили контракт, какая разница декларативно код написан или нет?

          Поменяли что-то в тесте и "случайно" нарушили требования, какая разница, есть тест или его нет?

          Цитата D_KEY @
          Ну или я тебя не так понял.

          Не удивлюсь, если так. =)
          Сообщение отредактировано: korvin -
            Цитата korvin @
            Цитата D_KEY @
            Ну или я тебя не так понял.

            Не удивлюсь, если так. =)

            А ты можешь кодом показать? :)

            Добавлено
            Цитата korvin @
            Оно отлавливает лишь самые простые, которых бы не было, если бы использовались нормальные языки (а не JS) и писался бы нормальный код.

            :lol:
            Ну в идеальном мире всяко было бы лучше, да. Начать писать юниты те же - достаточно легко. Примеить TDD - чуть сложнее. Перейти на "нормальные языки" - крайне трудно. Писать нормальный код - невозможно :D

            Добавлено
            Цитата korvin @
            Практически нисколько.
            ...
            Каждый кулик…
            ...
            Та ничем не лучше.

            Грустно, если так.

            Добавлено
            Цитата korvin @
            А нетривиальные баги всё равно проскакивают

            Ну это понятно. Вопрос еще в том, насколько проще это поймать, воспроизвести, зафиксировать в новом тесте и вылить на прод. Обычно адепты говорят, что быстрее.

            Цитата
            Ну ахренеть теперь. А если тест есть — это не значит, что он (тест) правильный. А если в тесте нет какого-то кейса?

            Значит нужно его добавить как только обнаружили что-то.

            Цитата
            Поменяли что-то в тесте и "случайно" нарушили требования, какая разница, есть тест или его нет?

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

            Добавлено
            Цитата korvin @
            Как по мне, значимость автоматического тестирования несколько преувеличена

            Автоматическое тестирование уменьшает ручное. С современными требованиями по скорости поставки иначе никак.
              Цитата D_KEY @
              А ты можешь кодом показать?

              Что показать?

              Цитата D_KEY @
              Ну в идеальном мире всяко было бы лучше, да. Начать писать юниты те же - достаточно легко. Примеить TDD - чуть сложнее. Перейти на "нормальные языки" - крайне трудно. Писать нормальный код - невозможно

              В идеальном мире меритократии?
              Оно достаточно легко и чуть сложнее пока кодовая база небольшая, а потом объём тестов и говнокода становится настолько большим, что усилия на сопровождение этого всего начинают перевешивать пользу от него.

              Цитата D_KEY @
              Обычно адепты говорят, что быстрее.

              Я ж говорю, каждый кулик…

              Цитата D_KEY @
              Значит нужно его добавить как только обнаружили что-то.

              И вправду, как я об этом не подумал. А почему бы сразу в коде не поправить?

              Цитата D_KEY @
              Вероятность-то разная

              Не особо.

              А «flaky tests» — слышал такое? А поломки совершенно, на первый взгляд, не связанных тестов из-за изменений в других тестах не встречал?

              Цитата D_KEY @
              В общем, если покажешь код, который не нуждается в тестирвании, будет неплохо.

              ExpandedWrap disabled
                filter odd |> map (* 2) |> reduce (+)


              Цитата D_KEY @
              Автоматическое тестирование уменьшает ручное. С современными требованиями по скорости поставки иначе никак.

              Я ничего не говорил про ручное. Не, я ж не против наличия автотестов, но пусть их и пишут тестировщики/бизнес-аналитики/постановщики-задач, но TDD-то не про это.
                Цитата korvin @
                Цитата D_KEY @
                А ты можешь кодом показать?

                Что показать?

                Декларативный код, которому не нужны тесты.

                Цитата
                В идеальном мире меритократии?

                Ага, главное не забывать, что он недостежим :D
                Поэтому важны тенденции, а не абсолютные показатели.

                Цитата
                Оно достаточно легко и чуть сложнее пока кодовая база небольшая, а потом объём тестов и говнокода становится настолько большим, что усилия на сопровождение этого всего начинают перевешивать пользу от него.

                Не свидетельствует ли это о хреновой декомпозиции?

                Цитата
                Цитата D_KEY @
                Значит нужно его добавить как только обнаружили что-то.

                И вправду, как я об этом не подумал. А почему бы сразу в коде не поправить?

                Так ты правишь и там и там, просто наличие теста позволит тебе в следующий раз, когда сломаешь, заметить это на более ранней стадии.

                Цитата
                А поломки совершенно, на первый взгляд, не связанных тестов из-за изменений в других тестах не встречал?

                Встречал. И это всегда были или хреновые тесты или хреновая подсистема или хреновая декомпозиция и кривые связи.
                То есть опять же некий признак, что явно идет не так, как хотелось бы :)

                Цитата
                Цитата D_KEY @
                В общем, если покажешь код, который не нуждается в тестирвании, будет неплохо.

                ExpandedWrap disabled
                  filter odd |> map (* 2) |> reduce (+)


                Ну и почему на это не стоит написать тест? Ты ведь можешь в случае правок учесть не все места, где он используется. А тест проверит, что все контракты соблюдены.

                Цитата
                Не, я ж не против наличия автотестов, но пусть их и пишут тестировщики/бизнес-аналитики/постановщики-задач, но TDD-то не про это.

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

                Добавлено
                Цитата D_KEY @
                Так ты правишь и там и там, просто наличие теста позволит тебе в следующий раз, когда сломаешь, заметить это на более ранней стадии.

                И это не сферический пример в вакууме. У меня сейчас автотесты "опаздывают" чуть по разработке продукта (догоняем, но пока не догнали). Так это уже приводило несколько раз к тому, что одни и те же вещи уже несколько раз отламывали в продукте :D
                  Цитата D_KEY @
                  Не свидетельствует ли это о хреновой декомпозиции?

                  Нет, с чего бы?

                  Цитата D_KEY @
                  Так ты правишь и там и там, просто наличие теста позволит тебе в следующий раз, когда сломаешь, заметить это на более ранней стадии.

                  На какой более ранней?

                  Цитата D_KEY @
                  Встречал. И это всегда были или хреновые тесты или хреновая подсистема или хреновая декомпозиция и кривые связи.

                  Ага, т.е. к хреновому коду ещё и добавляем хреновые тесты. Больше хрени богу хрени.

                  Цитата D_KEY @
                  То есть опять же некий признак, что явно идет не так, как хотелось бы

                  Ага:
                  — У нас что-то не так
                  — Давайте использовать TDD
                  — Теперь у нас что-то не так x10

                  Напоминает картинку отсюда.

                  Цитата D_KEY @
                  Ну и почему на это не стоит написать тест?

                  Потому что этот код полностью корректен.

                  Цитата D_KEY @
                  Ты ведь можешь в случае правок учесть не все места, где он используется.

                  А какое мне дело до того, где он используется? Это независимый юнит, если по каким-то требованиям у него меняется определение, то либо новые требования / новое определение корректны для всех, либо кому-то придётся использовать старую версию или не использовать расшаренный юнит, а писать свою реализацию. Как тут поможет юнит-тест этого кода?

                  Цитата D_KEY @
                  На моем опыте всегда было лучше, когда разрабы продукта писали сами unit-тесты и активно участвовали в разработке автотестов на других уровнях.

                  На моём опыте — это дичайшая дичь.

                  Цитата D_KEY @
                  У меня сейчас автотесты "опаздывают" чуть по разработке продукта

                  Так это не TDD.

                  Добавлено
                  Цитата D_KEY @
                  Так это уже приводило несколько раз к тому, что одни и те же вещи уже несколько раз отламывали в продукте

                  Так это потому что «хреновая подсистема или хреновая декомпозиция и кривые связи».
                    Цитата korvin @
                    Цитата D_KEY @
                    Не свидетельствует ли это о хреновой декомпозиции?

                    Нет, с чего бы?

                    Ну потому что при нормальной декомпозиции размер системы не должен настолько сильно сказываться.

                    Цитата
                    Цитата D_KEY @
                    Так ты правишь и там и там, просто наличие теста позволит тебе в следующий раз, когда сломаешь, заметить это на более ранней стадии.

                    На какой более ранней?

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

                    Цитата
                    Цитата D_KEY @
                    Встречал. И это всегда были или хреновые тесты или хреновая подсистема или хреновая декомпозиция и кривые связи.

                    Ага, т.е. к хреновому коду ещё и добавляем хреновые тесты. Больше хрени богу хрени.

                    Я к тому, что на первый взгляд мне кажется, что TDD показывает проблемы системы. И это хорошо.
                    А если его применять сначала, то кажется, что многих проблем можно будет избежать.
                    Но пока не поработаю плотно с этим хотя бы полгодика, не стану ничего утверждать.
                    Цитата

                    Цитата D_KEY @
                    То есть опять же некий признак, что явно идет не так, как хотелось бы

                    Ага:
                    — У нас что-то не так
                    — Давайте использовать TDD
                    — Теперь у нас что-то не так x10

                    Не совсем так. Начали использовать TDD, что помогло нам заметить проблемы. Устранили проблемы, стало лучше со многим.

                    Цитата
                    Цитата D_KEY @
                    Ну и почему на это не стоит написать тест?

                    Потому что этот код полностью корректен.

                    Корректность кода определяется не самим кодом, а тем, удовлетворяет ли он контрактам, которые от него хотят извне.

                    Цитата
                    Цитата D_KEY @
                    Ты ведь можешь в случае правок учесть не все места, где он используется.

                    А какое мне дело до того, где он используется? Это независимый юнит, если по каким-то требованиям у него меняется определение, то либо новые требования / новое определение корректны для всех, либо кому-то придётся использовать старую версию или не использовать расшаренный юнит, а писать свою реализацию. Как тут поможет юнит-тест этого кода?

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

                    Цитата
                    Цитата D_KEY @
                    На моем опыте всегда было лучше, когда разрабы продукта писали сами unit-тесты и активно участвовали в разработке автотестов на других уровнях.

                    На моём опыте — это дичайшая дичь.

                    Интересно.

                    Цитата
                    Цитата D_KEY @
                    У меня сейчас автотесты "опаздывают" чуть по разработке продукта

                    Так это не TDD.

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

                    Цитата
                    Так это потому что «хреновая подсистема или хреновая декомпозиция и кривые связи».

                    Да! :D
                    И я думаю, что многих из этих проблем не было бы при использовании TDD со старта.
                    Сообщение отредактировано: D_KEY -
                      Цитата D_KEY @
                      Ну потому что при нормальной декомпозиции размер системы не должен настолько сильно сказываться.

                      Как это не должен, когда должен: к юнит тестам добавляются интеграционные тесты, потом функциональные и системные.

                      Цитата D_KEY @
                      На самой ранней из возможных.

                      Самая ранняя из возможных — это хинты IDE от компилятора и анализатора.

                      Цитата D_KEY @
                      Я к тому, что на первый взгляд мне кажется, что TDD показывает проблемы системы.

                      Нет, не показывает. Что он показывает, так проблемы анализа и постановки задач и вместо нормального решения затыкает костылём (собой).

                      Цитата D_KEY @
                      А если его применять сначала, то…

                      …никогда не дойдёшь до реализации. Либо вместо тебя дойдут конкуренты по методу херак-херак и в продакшн, но это уже другая история.

                      Цитата D_KEY @
                      Начали использовать TDD, что помогло нам заметить проблемы.

                      Не помогло. Добавило новых.

                      Цитата D_KEY @
                      Корректность кода определяется не самим кодом, а тем, удовлетворяет ли он контрактам, которые от него хотят извне.

                      Контракт этого кода: сумма удвоенных нечётных целых чисел из списка. Именно это в нём и написано. Дальше что?

                      Цитата D_KEY @
                      Соответственно, поправили код

                      Зачем вы поправили код?

                      Цитата D_KEY @
                      найти и исправить все места, где этот код используется в соответствии с новым контрактом.

                      Это ещё с какого хера? Если у этих мест появились новые требования (новый контракт), пусть сами его использование и чинят. Моя задача, как реализатора контракта — реализовать его.

                      Цитата D_KEY @
                      И я думаю, что многих из этих проблем не было бы при использовании TDD со старта.

                      Что ж, удачи тебе. )
                        Я как то при написании личного говнокода пробовал сначала написать тесты. Было неудобно то, что я до написания плохо представлял себе, какой конкретно интерфейс я хочу получить в проектируемом классе, и в результате, когда он менялся, приходилось переписывать тесты, потом нужно было красить их в зелёный, потом опять смена интерфейса с переписыванием тестов и так несколько раз. Зато потом, когда переводил поделие на линукс, тесты помогли, и прога заработала сразу как только были пройдены тесты основной либы.
                        На работе у нас тесты есть, но на них всегда не хватает времени, так что ни о каком TDD не идёт и речи. На одной из прошлых работ в своей зоне ответственности организовал тестирование - написал какие-то тесты, а потом, когда находились баги, первым делом вносил их в тест. Даже баги многопоточки получалось тестами покрывать, правильно расставляя в коде sleep-ы. TDD, вероятно, там бы сработало, но было лень ему следовать :)
                          Цитата D_KEY @
                          Статью я не читал еще. Но думаю, что ты усилил утверждение. Обычно говорят о том, что моки могут свидетельствовать о том, что с кодом что-то не так. Соовтетственно, обилие моков - повод задуматься.

                          Вот тут об этом пишут: https://medium.com/javascript-scene/mocking...ll-944a70c90a6a
                          Основная идея в том, что если у тебя есть Моки, значит у тебя тесная связь, и твоя функция/система зависит от чего то извне, без чего не может быть протестирована, а это типа херово. Надо писать код в функциональной парадигме, чтоб этого избегать, чистые функции(без сайд эффектов), всякие там монады(then().then().then...) и т.п. Типа моки нужно применять в интеграционных тестах, и весь функционал, которые не покрывают UT - подразумевается что он должен работать с IO, соккеты/файлы/чета там еще, поэтому это тистируется интеграционными тестами. Ну это как я понял из статьи выше.

                          Добавлено
                          Вот например вот тут он описывает что представляет из себя тесную связь, а что слабую:
                          Цитата

                          Tight coupling:
                          Class inheritance (coupling is multiplied by each layer of inheritance and each descendant class)
                          Global variables
                          Other mutable global state (browser DOM, shared storage, network, etc…)
                          Module imports with side-effects
                          Implicit dependencies from compositions, e.g., const enhancedWidgetFactory = compose(eventEmitter, widgetFactory, enhancements); where widgetFactory depends on eventEmitter
                          Dependency injection containers
                          Dependency injection parameters
                          Control parameters (an outside unit is controlling the subject unit by telling it what to do)
                          Mutable parameters

                          Loose coupling:
                          Module imports without side-effects (in black box testing, not all imports need isolating)
                          Message passing/pubsub
                          Immutable parameters (can still cause shared dependencies on state shape)

                          DI - тесная связь. Вот для меня это открытием стало, потому что до этого я думал что это слабая связь, да и везде об этом пишут. :-?
                          Сообщение отредактировано: Wound -
                            Цитата Wound @
                            Кто по этой методике работает - хотелось бы услышать как изменилась ваша жизнь, после использования этой техники? Стало меньше времени уходить на разработку?

                            Я стараюсь сначала писать тест, а потом фиксить баг. Это не всегда получается в силу разных причин, но я стараюсь :) Требований писать тесты у нас нет, более того, шеф не доверяет юнит-тестам и поэтому на каждый таск делается ручной тест (им он доверяет) :D
                            Времени уходит больше, т.к. на написание теста тоже нужно время. А из-за того, что у нас код не всегда можно протестировать, приходится иногда что-то менять в коде, чтобы сделать его тестируемым.

                            В принципе мне нравится как сама идея TDD, так и результат. При этом я допускаю, отход от паттерна "сперва тест".


                            Цитата Wound @
                            Из этой статьи для меня оказалось в новинку что наличие Мок-объектов в UT говорит о том, что с кодом что то не так, и что DI - это признак жесткой связи между объектами :blink:
                            В прочем автор поясняет почему, в принципе с такой точкой зрения - я даже согласен.

                            Не знаю, наверное я не понял всей мысли, но эта мысль кажется мне странной :) Я уж не говорю о том, что Мок-объекты заменяют итерфейсы, а не объекты (D из SOLID).
                              Цитата Fester @
                              Не знаю, наверное я не понял всей мысли, но эта мысль кажется мне странной Я уж не говорю о том, что Мок-объекты заменяют итерфейсы, а не объекты (D из SOLID).

                              Ну вот автор и приводит пример.
                              Типа можно ли протестировать модуль/функцию без имитации зависимости? Если нельзя - то это эта функция/модуль - тесно связана с имитируемыми зависимостями. Соотвественно получается что Моки и DI - являются тесной зависимостью. Чем больше зависимостей - тем больше вероятность получить ошибку.
                              Вот тут он это описывает:
                              Цитата

                              Ironically, most of the sources of coupling are mechanisms originally designed to reduce coupling. That makes sense, because in order to recompose our smaller problem solutions into a complete application, they need to integrate and communicate somehow. There are good ways, and bad ways. The sources that cause tight coupling should be avoided whenever it’s practical to do so. The loose coupling options are generally desirable in a healthy application.
                              You might be confused that I classified dependency injection containers and dependency injection parameters in the “tight coupling” group, when so many books and blog post categorize them as “loose coupling”. Coupling is not binary. It’s a gradient scale. That means that any grouping is going to be somewhat subjective and arbitrary.
                              I draw the line with a simple, objective litmus test:
                              Can the unit be tested without mocking dependencies? If it can’t, it’s tightly coupled to the mocked dependencies.
                              The more dependencies your unit has, the more likely it is that there may be problematic coupling.
                              Now that we understand how coupling happens, what can we do about it?
                              Сообщение отредактировано: Wound -
                                Мне кажется, что автор этого текста сам себя перемудрил :)

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

                                А список "слабых связей" жесть:
                                >> Module imports without side-effects (in black box testing, not all imports need isolating)
                                Не совсем понимаю, что это такое

                                >> Message passing/pubsub
                                А потом тестировать switch, который будет разгребать эти сообщения.

                                >> Immutable parameters (can still cause shared dependencies on state shape)
                                Ага и по 100 параметров в каждой функции. Ну нахрен.
                                  Цитата korvin @
                                  Цитата D_KEY @
                                  Ну потому что при нормальной декомпозиции размер системы не должен настолько сильно сказываться.

                                  Как это не должен, когда должен: к юнит тестам добавляются интеграционные тесты, потом функциональные и системные.

                                  Ну так они полезны. И потому это стоит того. Альтернатива - ручное тестирование с регрессом на несколько человеко-месяцев :D

                                  Цитата
                                  Самая ранняя из возможных — это хинты IDE от компилятора и анализатора.

                                  Эм. Согласен. Но я имел в виду самая ранняя из возможных для конкретного кейса.

                                  Цитата
                                  Что он показывает, так проблемы анализа и постановки задач и вместо нормального решения затыкает костылём (собой).

                                  Каким боком тут анализ и постановка задач?

                                  Цитата
                                  Цитата D_KEY @
                                  А если его применять сначала, то…

                                  …никогда не дойдёшь до реализации. Либо вместо тебя дойдут конкуренты по методу херак-херак и в продакшн, но это уже другая история.

                                  Ну вот хотелось бы понять, на чем это основано. Как раз обычно все эти быстрые стартапы часто активно используют эти "новые" хорошие инженерные практики, т.к. без них херак-херак превращается в ад.

                                  Цитата
                                  Цитата D_KEY @
                                  Начали использовать TDD, что помогло нам заметить проблемы.

                                  Не помогло. Добавило новых.

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

                                  Цитата
                                  Контракт этого кода: сумма удвоенных нечётных целых чисел из списка. Именно это в нём и написано. Дальше что?

                                  Простой пример. Начал рефакторить или оптимизировать, ошибся в чем-то. Сломал.

                                  Цитата
                                  Зачем вы поправили код?

                                  Программисты такие, знаешь ли. Код правят. Делали фичу, рефакторили, оптимизировали - выбирай на свой вкус.

                                  Цитата
                                  Цитата D_KEY @
                                  найти и исправить все места, где этот код используется в соответствии с новым контрактом.

                                  Это ещё с какого хера? Если у этих мест появились новые требования (новый контракт), пусть сами его использование и чинят. Моя задача, как реализатора контракта — реализовать его.

                                  Да. А ты при реализации или случайно или специально его нарушил. Как без тестов это понять?

                                  Цитата
                                  Цитата D_KEY @
                                  И я думаю, что многих из этих проблем не было бы при использовании TDD со старта.

                                  Что ж, удачи тебе. )

                                  Ну я не скоро еще буду что-либо стартовать :D

                                  Добавлено
                                  Цитата Wound @
                                  Основная идея в том, что если у тебя есть Моки, значит у тебя тесная связь, и твоя функция/система зависит от чего то извне, без чего не может быть протестирована, а это типа херово. ... Типа моки нужно применять в интеграционных тестах, и весь функционал, которые не покрывают UT - подразумевается что он должен работать с IO, соккеты/файлы/чета там еще, поэтому это тистируется интеграционными тестами. Ну это как я понял из статьи выше.

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

                                  А вот с этим я скорее согласен:
                                  Цитата
                                  Надо писать код в функциональной парадигме, чтоб этого избегать, чистые функции(без сайд эффектов), всякие там монады(then().then().then...) и т.п.
                                    Цитата Fester @
                                    Мне кажется, что автор этого текста сам себя перемудрил

                                    Ну почему же. Просто он немного с другой стороны смотрит на это.

                                    Добавлено
                                    Цитата D_KEY @
                                    Ну так моки и помогают протестить в юнитах то, что без них можно будет тестить только в интеграционных тестах. Конечно, их не должно быть много. Но в абсолют я бы это не возводил.

                                    Вот, а предполагается, что по хорошему, Моки в ЮТ быть не должны, они должны быть в интеграционных тестах, типа именно там их место. А если они в ЮТ - то у тебя слабо декомпозирована система, и есть жесткие связи, без которых ты не можешь протестить функционал, что является кладезью ошибок.
                                    Сообщение отредактировано: Wound -
                                      Цитата Wound @
                                      Вот, а предполагается, что по хорошему, Моки в ЮТ быть не должны, они должны быть в интеграционных тестах, типа именно там их место.

                                      Да. Но на практике выгоднее, когда у тебя отваливается все как можно раньше. ЮТ у нас, например, гоняются локально. Возможно, стоит так же часто гонять интеграционные, но это неудобно.

                                      Цитата
                                      А если они в ЮТ - то у тебя слабо декомпозирована система, и есть жесткие связи, без которых ты не можешь протестить функционал, что является кладезью ошибок.

                                      Еще раз. Так может быть. И поэтому действительно к мокам нужно осторожно относиться. Но вот представь, что мы все пишем так, как рекомендуется в статье, а потом, дополнительно, еще пишем ЮТ, где мокируем всякий ввод/вывод, работу с базой, сеть и пр. То есть делаем дополнительные проверки перед интеграционнымм, чтобы раньше узнать о проблемах. Я не понимаю, почему это плохо.
                                        Цитата Wound @
                                        Моки в ЮТ быть не должны, они должны быть в интеграционных тестах

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

                                        Цитата Wound @
                                        А если они в ЮТ - то у тебя слабо декомпозирована система, и есть жесткие связи, без которых ты не можешь протестить функционал, что является кладезью ошибок.

                                        Ну вот представим, что у тебя есть 2 компоненты: калькулятор и логгер. Все действия калькулятора должны быть запротоколированы. Как должен выглядить идеальный юнит-тест суммы? :)
                                          Да, давайте больше практики добавим в холивар :) Не политика жеж
                                            Цитата D_KEY @
                                            Да. Но на практике выгоднее, когда у тебя отваливается все как можно раньше. ЮТ у нас, например, гоняются локально. Возможно, стоит так же часто гонять интеграционные, но это неудобно.

                                            Так в статье речь про TDD, а не про гоняния UT. Соответственно при разработке на основе TDD - ты тестируешь непосредственный функционал того, что ты реализовываешь, и ловишь ошибки сразу, ну либо после изменений.

                                            Цитата D_KEY @
                                            Еще раз. Так может быть. И поэтому действительно к мокам нужно осторожно относиться. Но вот представь, что мы все пишем так, как рекомендуется в статье, а потом, дополнительно, еще пишем ЮТ, где мокируем всякий ввод/вывод, работу с базой, сеть и пр. То есть делаем дополнительные проверки перед интеграционнымм, чтобы раньше узнать о проблемах. Я не понимаю, почему это плохо.

                                            Ээээ. Если мы пишем так как в статье, значит нам уже не надо писать дополнительно ЮТ. Мы с них начинаем писать. Пишем тест, потом функционал. А уж потом дополнительно пишем интеграционные тесты, где с помощью моков эмулируем различные сценарии.
                                            Ну и автор не говорит что нельзя моки юзать в ЮТ и все тут, он как бы намекает, что если в ЮТ используются Моки то это уже говорит о том, что модули сильно взаимосвязаны, и можно переписать куда лучше, чтоб избавится от моков.

                                            Цитата Fester @
                                            Интеррационный тест для того и нужен, чтобы проверить взаимодействие двух реальных объектов. Т.е. суть интергационного теста - как раз отсутсвие мока.

                                            С помощью моков можно и нужно моделировать различное поведение системы в тех или иных случаях. Вот и автор пишет по этому поводу:
                                            Цитата

                                            When you use generic composition utilities, each element of the composition can be unit tested in isolation without mocking the others.
                                            The compositions themselves will be declarative, so they’ll contain zero unit-testable logic (presumably the composition utility is a third party library with its own unit tests).
                                            Under those circumstances, there’s nothing meaningful to unit test. You need integration tests, instead.

                                            Смысл в том, что если ты юзаешь какие то стандартные/универстальные утилиты композиции, то их нет смысла тестировать, и значит UT лишено смысла, по сути ты Моки тестируешь, у тебя код становится сильно связанным, тесты становятся взаимозависимыми. У тебя должны тестироваться отдельные части системы, а не все в куче. Все в куче - это уже интеграционный тест.

                                            Цитата Fester @
                                            Ну вот представим, что у тебя есть 2 компоненты: калькулятор и логгер. Все действия калькулятора должны быть запротоколированы. Как должен выглядить идеальный юнит-тест суммы?

                                            Ты для начала пишешь ЮТ, потом пишешь программу.
                                            Вот ты и пишешь например:

                                            ExpandedWrap disabled
                                              void SumTest()
                                              {
                                                 ICalculator calc = new Calculator();
                                               
                                                 assertEqual(7, calc.Sum(3,4));
                                                 ...
                                              }


                                            Потом ты реализуешь сам калькулятор, применяя опыт написанный выше, что функции должны быть без сайд эффектов и чистые, у тебя получится что то типа:
                                            ExpandedWrap disabled
                                              interface ICalculator
                                              {
                                                  int Sum(int a, int b);
                                              }
                                               
                                              public class Calculator : ICalculator
                                              {
                                               
                                                 public int Sum(int a, int b)
                                                 {
                                                    return a + b;
                                                 }
                                              }

                                            Логировать ты можешь в другом месте, например на уровне выше.

                                            Добавлено
                                            Цитата Wound @
                                            Логировать ты можешь в другом месте, например на уровне выше.

                                            Либо создай класс LoggerCalculator, в котором например ты можешь логировать и считать сумму.
                                            Например на основе нашего кода выше:
                                            ExpandedWrap disabled
                                              public class CalculatorWithLogger
                                              {
                                                 private ICalculator _calculator;
                                                 private readonly ILogger _logger;
                                                 public CalculatorWithLogger(ICalculator calc, ILogger logger)
                                                 {
                                                     _calculator = calc;
                                                     _logger = logger;
                                                 }
                                               
                                                 public int Sum(int a, int b)
                                                 {
                                                    _logger.WriteLine($"Call method Sum({a},{b})");
                                                    return _calculator.Sum(a, b);
                                                 }
                                              }


                                            Добавлено
                                            Так вот этот класс CalculatorWithLogger - уже нет смысла тестировать с помощью UT. Потому что ты по сути будешь тестировать логгер(который уже тестируется в своей библиотеке) и Calculator, для которого уже и так написаны UT.
                                            А вот в интеграционном тесте - его есть смысл тестировать, подкидывая различные логгеры например.

                                            Добавлено
                                            Вот автор об этом и пишет дальше:
                                            Цитата

                                            That means that the code you use to set up network requests and request handlers won’t need unit tests. Use integration tests for those, instead.

                                            That bears repeating:
                                            Don’t unit test I/O.
                                            I/O is for integrations. Use integration tests, instead.


                                            It’s perfectly OK to mock and fake for integration tests.

                                            Статья конечно довольно большая. И если рассказывать в отрыве не понятно что к чему, но когда начинаешь читать, начинаешь понимать о чем он пишет.
                                            Сообщение отредактировано: Wound -
                                              Цитата Wound @
                                              Потом ты реализуешь сам калькулятор, применяя опыт написанный выше, что функции должны быть без сайд эффектов и чистые, у тебя получится что то типа

                                              Такой калькулятор не удовлетворяет требованию, что все действия должны быть запротоколированы ;)


                                              Цитата Wound @
                                              Логировать ты можешь в другом месте, например на уровне выше.

                                              Отличное решение!
                                              Пусть нас надо написать функцию, которая конвертирует температуру по Кельвину в температуру по Цельсию. Протестированный калькулятор у нас уже есть. Давай использовать его ;) Не забываем, что каждый вызов калькулятора должен быть запротоколирован логгером. Как должна выглядеть такая функция и тест к ней?
                                                Цитата Fester @
                                                Такой калькулятор не удовлетворяет требованию, что все действия должны быть запротоколированы

                                                Почему не удовлетворяет? Удовлетворяет. Все действия запротоколированны. Посмотри код выше. Юзать то ты будешь CalculatorWithLogger.

                                                Цитата Fester @
                                                Отличное решение!
                                                Пусть нас надо написать функцию, которая конвертирует температуру по Кельвину в температуру по Цельсию. Протестированный калькулятор у нас уже есть. Давай использовать его Не забываем, что каждый вызов калькулятора должен быть запротоколирован логгером. Как должна выглядеть такая функция и тест к ней?

                                                В чем проблема написать такой тест? Ну возьми функции выше, измени названия классов.
                                                  Цитата Wound @
                                                  Либо создай класс LoggerCalculator, в котором например ты можешь логировать и считать сумму.
                                                  Например на основе нашего кода выше:

                                                  Замечетельно! Вот только у тебя в системе один логгер и Х классов, которые должны что-то протоколировать. Таким образом твое решение приводит к удвоению количества классов ;)

                                                  А если пойти еще дальше и к логгеру добавить... ну скажем запись в БД, при этом и запись в БД и логгер опциональны, то у тебя получается такой вот зоопарк: Calculator, Logger, Database, CalculatorWithLogger, CalculatorWithDatabase, CalculatorWithLoggarAndDatabase.

                                                  Согласись, решение так себе...

                                                  Добавлено
                                                  Цитата Wound @
                                                  Либо создай класс LoggerCalculator, в котором например ты можешь логировать и считать сумму.
                                                  Например на основе нашего кода выше:

                                                  Кстати, даже в этом коде есть логика, которую можно тестировать ;)
                                                  Либо ты должен проверять параметры конструктора и кидать исключение в случае если logger или calculator имеют нулевые значения. Либо ты должен проверять на ноль непосредственно при сложении. Т.е. тут цикломатическая сложность гарантированно больше 1, а значит надо делать тест. Можно конечно назвать этот тест интеграционным, но мне кажется, что это уже дрочерство на терминологию ;)

                                                  Цитата Wound @
                                                  Почему не удовлетворяет? Удовлетворяет. Все действия запротоколированны. Посмотри код выше. Юзать то ты будешь CalculatorWithLogger.

                                                  Да, вот только функция уровнем выше (которая будет использовать CalculatorWithLogger) ничего не должна знать про существование CalculatorWithLogger, ей известен только ICalculator... Т.е. либо тебе придется вводить еще одну абстракцию, либо ты рискуешь совершить ошибку передав в CalculatorWithLogger в качестве ICalculator другой CalculatorWithLogger.
                                                    Цитата Fester @
                                                    Замечетельно! Вот только у тебя в системе один логгер и Х классов, которые должны что-то протоколировать. Таким образом твое решение приводит к удвоению количества классов

                                                    Конечно, и что в этом плохого? Есть такой принцип: Принцип единственной ответственности буква S, из абрревиатуры SOLID, вот мы как раз ему и последовали. В чем косяк то? :-?

                                                    Цитата Fester @
                                                    А если пойти еще дальше и к логгеру добавить... ну скажем запись в БД, при этом и запись в БД и логгер опциональны, то у тебя получается такой вот зоопарк: Calculator, Logger, Database, CalculatorWithLogger, CalculatorWithDatabase, CalculatorWithLoggarAndDatabase.

                                                    Согласись, решение так себе...

                                                    Никакого зоопарка не будет. Будет один интерфейс, от которого будут наследоваться все эти CalculatorWithLogger, CalculatorWithDatabase, CalculatorWithLoggarAndDatabase. Это вроде обычный подход, разве нет? :huh:
                                                    А Calculator, Logger, Database - это вообще разные классы, реализующие разный функционал. Городить все это в один класс - это будет неверным.
                                                    Сообщение отредактировано: Wound -
                                                      Цитата Wound @
                                                      В чем проблема написать такой тест? Ну возьми функции выше, измени названия классов.

                                                      Проблема в том, что эта функция должна либо сама создать объект CalculatorWithLogger (для этого ей надо будет как-то получить ILogger и ICalculator), либо функция должна будет получить CalculatorWithLogger в качестве параметра (это насколько я понимаю, противоречит требованию к юнит-тесту)
                                                        Цитата Fester @
                                                        Кстати, даже в этом коде есть логика, которую можно тестировать
                                                        Либо ты должен проверять параметры конструктора и кидать исключение в случае если logger или calculator имеют нулевые значения. Либо ты должен проверять на ноль непосредственно при сложении. Т.е. тут цикломатическая сложность гарантированно больше 1, а значит надо делать тест. Можно конечно назвать этот тест интеграционным, но мне кажется, что это уже дрочерство на терминологию

                                                        Так я выше написал, что этот класс будет тестироваться с помощью интеграционных тестов. Ну добавь туда проверку на null, если тебе так нужно, я же пример привел, а не конечное приложение.

                                                        Цитата Fester @
                                                        Да, вот только функция уровнем выше (которая будет использовать CalculatorWithLogger) ничего не должна знать про существование CalculatorWithLogger, ей известен только ICalculator... Т.е. либо тебе придется вводить еще одну абстракцию, либо ты рискуешь совершить ошибку передав в CalculatorWithLogger в качестве ICalculator другой CalculatorWithLogger.

                                                        Если у тебя будет как ты выше написал CalculatorWithLogger, CalculatorWithDatabase, CalculatorWithLoggarAndDatabase, то тут в любом случае придется вводить интерфейс. Ты уже докапываешься до незначительных деталей ИМХО.

                                                        Добавлено
                                                        Цитата Fester @
                                                        Проблема в том, что эта функция должна либо сама создать объект CalculatorWithLogger (для этого ей надо будет как-то получить ILogger и ICalculator), либо функция должна будет получить CalculatorWithLogger в качестве параметра (это насколько я понимаю, противоречит требованию к юнит-тесту)

                                                        Какая эта функция? Я тебя не понимаю. Обработчик ГУИ формы? CalculatorWithLogger - это внешнее API, которые ты юзаешь в своем коде, а Calculator - это внутреняя бизнес логика, которая тестируется с помощью ЮТ.
                                                        Сообщение отредактировано: Wound -
                                                          Цитата Wound @
                                                          Конечно, и что в этом плохого? Есть такой принцип: Принцип единственной ответственности буква S, из абрревиатуры SOLID, вот мы как раз ему и последовали. В чем косяк то?

                                                          Принцип единственной ответственности не запрещает DI и не запрещает общаться с другими компонентами.

                                                          Цитата Wound @
                                                          Так я выше написал, что этот класс будет тестироваться с помощью интеграционных тестов.

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


                                                          Цитата Wound @
                                                          Если у тебя будет как ты выше написал CalculatorWithLogger, CalculatorWithDatabase, CalculatorWithLoggarAndDatabase, то тут в любом случае придется вводить интерфейс. Ты уже докапываешься до незначительных деталей ИМХО.

                                                          И не просто так придется вводить интерфейс. Т.к. у тебя будет
                                                          Calculator : ICalculator
                                                          CalculatorWithLogger : ICalculator
                                                          CalculatorWithDatabase : ICalculator
                                                          CalculatorWithLoggarAndDatabase : ICalculator
                                                          что, как ты понимаешь, легко может привести к ошибке.

                                                          Для исключения ошибки тебе придется вводить еще один интерфейс:
                                                          Calculator : ICalculatorUnit
                                                          CalculatorWithLogger : ICalculator
                                                          CalculatorWithDatabase : ICalculator
                                                          CalculatorWithLoggarAndDatabase : ICalculator


                                                          Цитата Wound @
                                                          Какая эта функция?

                                                          которая конвертирует температуру по Кельвину в температуру по Цельсию
                                                            Цитата Fester @
                                                            Принцип единственной ответственности не запрещает DI и не запрещает общаться с другими компонентами.

                                                            Конечно. Но у тебя в ТЗ оговорено что класс должен и логировать и вычислять сумму. Соответственно первый класс для вычисления суммы, второй для логирования, а третий агрегирует в себе эти два.
                                                            В чем конкретно проблема то? Как ты напишешь эту задачу? И в чем будет профит от твоего решения, я не пойму?
                                                            Ты хочешь ввести в свой метод внешние зависимости и потом тестировать это с помощью UT?
                                                            В итоге ты получаешь более зависимый тест, который может грохнуться из за того же логера, получаешь дополнительную кладезь ошибок, тебе придется передавать этот логер в ЮТ. Т.е. у тебя функция вычисляет сумму двух чисел, и помимо этого делает еще что то, что делать не должна.
                                                            Вот автор и описывает - что такой подход ведет к запутанности, сильной зависимости, и соответственно к подводным камням и ошибкам.

                                                            Цитата Fester @
                                                            Ну т.е. все сводится к тому какой тест можно назвать юнит-тестом, а какой уже интеграционный. Вопрос не в сути, а в терминологии. При этом ради сохранения чистоты терминологии автор готов пойти на усложнение кода и, соответственно, добавление ошибок.

                                                            Нет, ты все не так понял. Я тебе советую перечитать статью, на всякий случай продублирую ее еще раз: https://medium.com/javascript-scene/mocking...ll-944a70c90a6a

                                                            Цитата Fester @
                                                            И не просто так придется вводить интерфейс. Т.к. у тебя будет
                                                            Calculator : ICalculator
                                                            CalculatorWithLogger : ICalculator
                                                            CalculatorWithDatabase : ICalculator
                                                            CalculatorWithLoggarAndDatabase : ICalculator
                                                            что, как ты понимаешь, легко может привести к ошибке.

                                                            Для исключения ошибки тебе придется вводить еще один интерфейс:
                                                            Calculator : ICalculatorUnit
                                                            CalculatorWithLogger : ICalculator
                                                            CalculatorWithDatabase : ICalculator
                                                            CalculatorWithLoggarAndDatabase : ICalculator

                                                            Ты не понял о каком интерфейсе я говорил.

                                                            ICalculator - это интерфейс для реализации калькуляторов, зачем его вообще трогать?

                                                            У тебя будет интерфейс, который призван объединить вычисление с IO, какой нить:
                                                            ICalculatorWithIO или что то подобное. Не суть важно.
                                                            В принципе, вместо интерфейса ICalculator, можно взять абстрактный класс например и наследовать свои калькуляторы от него, если тебя смущает именно интерфейс, но можно и интерфейс, просто этот интерфейс(ICalculator) будет внутренним, если можно так выразиться. А ICalculatorWithIO - внешним, если исходить из того, что по ТЗ наружу должен торчать интерфейс который считает и логирует.


                                                            Цитата Fester @
                                                            которая конвертирует температуру по Кельвину в температуру по Цельсию

                                                            Так я не понимаю, какие проблемы? По аналогии напиши, в чем прикол то? Сложность в переименовании интерфейсов и классов возникла?
                                                            Сообщение отредактировано: Wound -
                                                              Цитата Wound @
                                                              Если мы пишем так как в статье, значит нам уже не надо писать дополнительно ЮТ. Мы с них начинаем писать. Пишем тест, потом функционал. А уж потом дополнительно пишем интеграционные тесты, где с помощью моков эмулируем различные сценарии.

                                                              Я может плаваю в теории, но в интеграционных тестах вместо моков уже должны быть реальные объекты, разве нет?
                                                              А так все верно, да. Просто я бы предложил перед интеграционными тестами, делать еще ЮТ с моками, чтобы некоторые ошибки отлавливать раньше запуска интеграционных тестов.

                                                              Цитата
                                                              Ну и автор не говорит что нельзя моки юзать в ЮТ и все тут, он как бы намекает, что если в ЮТ используются Моки то это уже говорит о том, что модули сильно взаимосвязаны, и можно переписать куда лучше, чтоб избавится от моков.

                                                              Вот если вместо "говорит о том, что модули сильно взаимосвязаны" написать "может говорить о том, что модули сильно взаимосвязаны", то я соглашусь :)
                                                                Цитата D_KEY @
                                                                Я может плаваю в теории, но в интеграционных тестах вместо моков уже должны быть реальные объекты, разве нет?

                                                                Ну вот автор пишет что нет. Плюс есть мнение, и это не только у этого автора я читал, что в интеграционных тестах - моками имитируются все ситуации, которые могут быть. Реальный объект, не реальный объект, серверная атака, имитация ошибки, имитация с отключенным соединением и т.д. и т.п.

                                                                Цитата D_KEY @
                                                                А так все верно, да. Просто я бы предложил перед интеграционными тестами, делать еще ЮТ с моками, чтобы некоторые ошибки отлавливать раньше запуска интеграционных тестов.

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

                                                                Цитата D_KEY @
                                                                Вот если вместо "говорит о том, что модули сильно взаимосвязаны" написать "может говорить о том, что модули сильно взаимосвязаны", то я соглашусь

                                                                Выдрал специально для тебя:
                                                                Цитата

                                                                What is a code smell?
                                                                “A code smell is a surface indication that usually corresponds to a deeper problem in the system.” ~ Martin Fowler
                                                                A code smell does not mean that something is definitely wrong, or that something must be fixed right away. It is a rule of thumb that should alert you to a possible opportunity to improve something.
                                                                This text and its title in no way imply that all mocking is bad, or that you should never mock anything.
                                                                Additionally, different types of code need different levels (and different kinds) of mocks. Some code exists primarily to facilitate I/O, in which case, there is little to do other than test I/O, and reducing mocks might mean your unit test coverage would be close to 0.
                                                                If there is no logic in your code (just pipes and pure compositions), 0% unit test coverage might be acceptable, assuming your integration or functional test coverage is close to 100%. However, if there is logic (conditional expressions, assignments to variables, explicit function calls to units, etc…), you probably do need unit test coverage, and there may be opportunities to simplify your code and reduce mocking requirements.
                                                                ---
                                                                What is tight coupling?
                                                                The need to mock in order to achieve unit isolation for the purpose of unit tests is caused by coupling between units. Tight coupling makes code more rigid and brittle: more likely to break when changes are required. In general, less coupling is desirable for its own sake because it makes code easier to extend and maintain. The fact that it also makes testing easier by eliminating the need for mocks is just icing on the cake.
                                                                From this we can deduce that if we’re mocking something, there may be an opportunity to make our code more flexible by reducing the coupling between units. Once that’s done, you won’t need the mocks anymore.
                                                                Coupling is the degree to which a unit of code (module, function, class, etc…) depends upon other units of code. Tight coupling, or a high degree of coupling, refers to how likely a unit is to break when changes are made to its dependencies. In other words, the tighter the coupling, the harder it is to maintain or extend the application. Loose coupling reduces the complexity of fixing bugs and adapting the application to new use-cases.
                                                                ---
                                                                What does composition have to do with mocking?
                                                                Everything. The essence of all software development is the process of breaking a large problem down into smaller, independent pieces (decomposition) and composing the solutions together to form an application that solves the large problem (composition).
                                                                Mocking is required when our decomposition strategy has failed.
                                                                Mocking is required when the units used to break the large problem down into smaller parts depend on each other. Put another way, mocking is required when our supposed atomic units of composition are not really atomic, and our decomposition strategy has failed to decompose the larger problem into smaller, independent problems.
                                                                When decomposition succeeds, it’s possible to use a generic composition utility to compose the pieces back together. Examples:
                                                                ---
                                                                When you use generic composition utilities, each element of the composition can be unit tested in isolation without mocking the others.
                                                                The compositions themselves will be declarative, so they’ll contain zero unit-testable logic (presumably the composition utility is a third party library with its own unit tests).
                                                                Under those circumstances, there’s nothing meaningful to unit test. You need integration tests, instead.
                                                                  Цитата D_KEY @
                                                                  Простой пример. Начал рефакторить или оптимизировать, ошибся в чем-то. Сломал.

                                                                  Зачем ты начал рефакторить то, что и так ясно отражает суть?
                                                                  Про оптимизацию я уже писал, но ты всё пропустил.

                                                                  Цитата D_KEY @
                                                                  Как раз обычно все эти быстрые стартапы часто активно используют эти "новые" хорошие инженерные практики

                                                                  Видел я как такой стартап использует эти практики. Ничем от херак-херак не отличается.

                                                                  Цитата D_KEY @
                                                                  Программисты такие, знаешь ли. Код правят. Делали фичу, рефакторили, оптимизировали - выбирай на свой вкус.

                                                                  Э нет, делать фичу — это новые требования и новая реализация. Рефакторят говнокод, а этот код чист, декларативен и понятен, идеален, его не нужно рефакторить =)
                                                                  Про оптимизации я уже писал.
                                                                  Кроме того, во многих сервисных проектах (особенно в этих ваших стартапах) проблемы производительности возникают обычно не в коде и не с первых годов жизни проекта.
                                                                    Цитата korvin @
                                                                    Зачем ты начал рефакторить то, что и так ясно отражает суть?
                                                                    Про оптимизацию я уже писал, но ты всё пропустил.

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

                                                                    Цитата
                                                                    Видел я как такой стартап использует эти практики. Ничем от херак-херак не отличается.

                                                                    А хорошо получалось или было все нестабильно?

                                                                    Цитата
                                                                    Э нет, делать фичу — это новые требования и новая реализация.

                                                                    А, то есть мы полностью переписываем код каждый раз? :D

                                                                    Цитата
                                                                    Про оптимизации я уже писал.

                                                                    То, что если он недекларативен ради оптимизации, то тогда тесты имеет смысл писать?
                                                                    А что, декларативный код не оптимизируют?
                                                                      Цитата Wound @
                                                                      Но у тебя в ТЗ оговорено что класс должен и логировать и вычислять сумму.

                                                                      Логгинг в этом плане идеальный пример, т.к. логгером можно отслеживать процесс. Согласись, далеко не все функии умещаются в одну строку. И логгинг бывает разного уровня (debug, info, warn, error, fatal итд).

                                                                      Цитата Wound @
                                                                      Соответственно первый класс для вычисления суммы, второй для логирования, а третий агрегирует в себе эти два.

                                                                      Прикол в том, что 3-й класс делает систему неоправданно сложной.

                                                                      Цитата Wound @
                                                                      Вот автор и описывает - что такой подход ведет к запутанности, сильной зависимости, и соответственно к подводным камням и ошибкам.

                                                                      Весь прикол заключает в том, что если пользоваться терминологией автора этой статьи (т.е. UT не имеет мок-объектов, а то, что имеет мок-объекты - это интергационные тесты), то не существует систем, которые тестируются юнит-тестами :) Юнит-тестами можно протестировать только базовые компоненты, а продуктивный (aka полезный) код тестируется исключительно интеграционными тестами.
                                                                      Например, у нас есть модуль, который контролирует передачу RFID чипов. Каждый евент от RFID-антенны сохраняется в БД и передается от 0 до n раз на 5 других подсистем. И вся информация о правильном поведении идет из вне :) Ну и каких "юнит-тестах" (в понимании автора статьи) тут можно говорить?
                                                                        Цитата Fester @
                                                                        Логгинг в этом плане идеальный пример, т.к. логгером можно отслеживать процесс. Согласись, далеко не все функии умещаются в одну строку. И логгинг бывает разного уровня (debug, info, warn, error, fatal итд).

                                                                        Так и что с ним не так то? :-?

                                                                        Цитата Fester @
                                                                        Прикол в том, что 3-й класс делает систему неоправданно сложной.

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

                                                                        Цитата Fester @
                                                                        Весь прикол заключает в том, что если пользоваться терминологией автора этой статьи (т.е. UT не имеет мок-объектов, а то, что имеет мок-объекты - это интергационные тесты), то не существует систем, которые тестируются юнит-тестами

                                                                        Ни автор, ни я не писали того, что выделенно жирным в твоей цитате.

                                                                        Цитата Fester @
                                                                        Юнит-тестами можно протестировать только базовые компоненты, а продуктивный (aka полезный) код тестируется исключительно интеграционными тестами.

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

                                                                        Цитата Fester @
                                                                        Например, у нас есть модуль, который контролирует передачу RFID чипов. Каждый евент от RFID-антенны сохраняется в БД и передается от 0 до n раз на 5 других подсистем. И вся информация о правильном поведении идет из вне Ну и каких "юнит-тестах" (в понимании автора статьи) тут можно говорить?

                                                                        Юнит тестами тестируют конкретные функции/методы, ты же привел какой то юзер кейс, который должен тестироваться интеграционными тестами.
                                                                        Тестируемая функция в ЮТ не должна не от чего зависеть, она должна быть автономной. Т.е. если у тебя в каком то 1 методе ошибка. То в юнит тестах - должен быть сфейлен ровно 1 UT из 1000 запущенных, даже если в программе у тебя куча мест, которые используют эту функцию/метод.
                                                                        Если же у тебя сфейлилось 50 юнит тестов, из за того, что в ровно 1 методе у тебя ошибка, значит с твоими юнит тестами что то не то, и их ценность стремится к нулю.
                                                                        Сообщение отредактировано: Wound -
                                                                          Цитата D_KEY @
                                                                          а в реальном декларативном коде не вознкает потребности в рефакторинге?

                                                                          Так я ж бомблю, что его почти не пишут, этот самый декларативный код, предпочитая обмазаться тестами и говнякать =)

                                                                          Добавлено
                                                                          Цитата D_KEY @
                                                                          А хорошо получалось или было все нестабильно?

                                                                          Всё было плохо.

                                                                          Цитата D_KEY @
                                                                          А, то есть мы полностью переписываем код каждый раз?

                                                                          Зачем каждый раз? Если ты про то, чтобы переиспользовать эту функцию в новой фиче, то если она подходит, можно и переиспользовать, но тогда две фичи будут от неё зависеть и менять её нельзя. Да и зачем? Если у тебя новые требование (новая формула, скажем, умножать нужно не на два, а на три), то естественно нужно написать новую функцию и использовать там, где нужно, а не трогать старую. Ты что, open-close принцип забыл?

                                                                          Цитата D_KEY @
                                                                          То, что если он недекларативен ради оптимизации, то тогда тесты имеет смысл писать?

                                                                          Ну да, если это нечитаемое байтоковыряние, конечно, надо.

                                                                          Цитата D_KEY @
                                                                          А что, декларативный код не оптимизируют?

                                                                          А что такое «оптимизация декларативного кода»?

                                                                          Добавлено
                                                                          К слову о моках и в чём с ними проблема.

                                                                          Допустим, есть у нас такой сервис:

                                                                          ExpandedWrap disabled
                                                                            public final class Transfer {
                                                                             
                                                                                public static final class Request {
                                                                             
                                                                                    public final Account.ID source;
                                                                                    public final Account.ID destination;
                                                                                    public final Money amount;
                                                                             
                                                                                    public Request(Account.ID source, Account.ID destination, Money amount) {
                                                                                        this.source = source;
                                                                                        this.destination = destination;
                                                                                        this.amount = amount;
                                                                                    }
                                                                                }
                                                                             
                                                                                public static final class InsufficientFunds extends RuntimeException {}
                                                                             
                                                                                private final Accounts accounts;
                                                                                private final Compliance compliance;
                                                                                private final Transactions transactions;
                                                                                private final Notifier notifier;
                                                                             
                                                                                public Transfer(Accounts accounts, Compliance compliance, Transactions transactions, Notifier notifier) {
                                                                                    this.accounts = accounts;
                                                                                    this.compliance = compliance;
                                                                                    this.transactions = transactions;
                                                                                    this.notifier = notifier;
                                                                                }
                                                                             
                                                                                public void submit(Request r) {
                                                                                    final var source = accounts.load(r.source);
                                                                                    final var destination = accounts.load(r.destination);
                                                                             
                                                                                    validate(source, destination, r.amount);
                                                                                    final var transaction = Transaction.fresh(r.source, r.destination, r.amount);
                                                                                    transactions.store(transaction);
                                                                                    notifyUsers(source, destination, transaction.id);
                                                                                }
                                                                             
                                                                                private void validate(Account source, Account destination, Money amount) {
                                                                                    checkCompliance(source, destination, amount);
                                                                                    checkBalance(source, amount);
                                                                                }
                                                                             
                                                                                private void checkCompliance(Account source, Account destination, Money amount) {
                                                                                    if (source.hasSameOwnerAs(destination)) {
                                                                                        return;
                                                                                    }
                                                                                    compliance.validateTransfer(source.owner, destination.owner, amount);
                                                                                }
                                                                             
                                                                                private void checkBalance(Account source, Money amount) {
                                                                                    if (source.canWithdraw(amount)) {
                                                                                        return;
                                                                                    }
                                                                                    throw new InsufficientFunds();
                                                                                }
                                                                             
                                                                                private void notifyUsers(Account source, Account destination, Transaction.ID transaction) {
                                                                                    notifier.notify(source.owner, transaction, "withdraw");
                                                                                    notifier.notify(destination.owner, transaction, "top up");
                                                                                }
                                                                            }


                                                                          у него четыре внешние зависимости, вот одна из них:
                                                                          ExpandedWrap disabled
                                                                            public interface Accounts {
                                                                             
                                                                                Account load(Account.ID id);
                                                                             
                                                                                // more methods
                                                                                // ...
                                                                            }

                                                                          три другие примерно такие же: интерфейсы с разными методами, я описал только те, что используются в сервисе Transfer.

                                                                          теперь для юнит-тестов мы мокаем эти интерфейсы, как обычно это делают, что-то вроде
                                                                          ExpandedWrap disabled
                                                                            final var accounts = Mockito.mock(Accounts);
                                                                            ...
                                                                            final var sourceAccount = // new account
                                                                            ...
                                                                            when(accounts.load(sourceAccount.id)).thenReturn(sourceAccount);
                                                                            ...
                                                                            subject.submit(new Transfer.Request(sourceAccount.id, ...));
                                                                            ...
                                                                            // assertions on mocked Transactions DB and Notifier?

                                                                          и тут возникают вопросы:
                                                                          – откуда мы знаем, что submit вообще вызовет accounts.load?
                                                                          – откуда мы знаем, что submit вызовет accounts.load с параметром sourceAccount.id?
                                                                          – откуда мы знаем, что submit не вызовет других методов accounts?
                                                                          Этого нет в публичном интерфейсе (здесь я имею ввиду публичные методы и конструкторы) класса Transfer, нет в его «контракте». Фактически это — деталь реализации.

                                                                          Т.е. наличие параметра Accounts в публичном конструкторе — это часть контракта, публичная внешняя зависимость, а как именно используется экземпляр Accounts — это деталь реализации и «знание» этой детали в тесте — прямое нарушение инкапсуляции.

                                                                          Теперь рассмотрим альтернативу:
                                                                          ExpandedWrap disabled
                                                                            public final class Transfer {
                                                                             
                                                                                public static final class Request {
                                                                             
                                                                                    public final Money amount;
                                                                                    public final Lazy<Account> source;
                                                                                    public final Lazy<Account> destination;
                                                                                    public final Lazy<Void> checkCompliance;
                                                                             
                                                                                    public Request(Money amount, Lazy<Account> source, Lazy<Account> destination, Lazy<Void> checkCompliance) {
                                                                                        this.amount = amount;
                                                                                        this.source = source;
                                                                                        this.destination = destination;
                                                                                        this.checkCompliance = checkCompliance;
                                                                                    }
                                                                                }
                                                                             
                                                                                public static final class Response {
                                                                             
                                                                                    public final Transaction transaction;
                                                                                    public final Notification[] notifications;
                                                                             
                                                                                    private Response(Transaction transaction, Notification... notifications) {
                                                                                        this.transaction = transaction;
                                                                                        this.notifications = notifications;
                                                                                    }
                                                                             
                                                                                    public static final class Notification {
                                                                                        public final User.ID user;
                                                                                        public final Transaction.ID transaction;
                                                                                        public final String message;
                                                                                        public Notification(User.ID user, Transaction.ID transaction, String message) {
                                                                                            this.user = user;
                                                                                            this.transaction = transaction;
                                                                                            this.message = message;
                                                                                        }
                                                                                    }
                                                                                }
                                                                             
                                                                                public static final class InsufficientFunds extends RuntimeException {}
                                                                             
                                                                                public Response submit(Request r) {
                                                                                    validate(r);
                                                                                    return transaction(r);
                                                                                }
                                                                             
                                                                                private void validate(Request r) {
                                                                                    checkCompliance(r);
                                                                                    checkBalance(r);
                                                                                }
                                                                             
                                                                                private void checkCompliance(Request r) {
                                                                                    if (r.source.val().hasSameOwnerAs(r.destination.val())) {
                                                                                        return;
                                                                                    }
                                                                                    r.checkCompliance.val();
                                                                                }
                                                                             
                                                                                private void checkBalance(Request r) {
                                                                                    if (r.source.val().canWithdraw(r.amount)) {
                                                                                        return;
                                                                                    }
                                                                                    throw new InsufficientFunds();
                                                                                }
                                                                             
                                                                                private Response transaction(Request r) {
                                                                                    final var transaction = Transaction.fresh(r.source.val().id, r.destination.val().id, r.amount);
                                                                                    return new Response(
                                                                                            transaction,
                                                                                            new Response.Notification(r.source.val().owner, transaction.id, "withdraw"),
                                                                                            new Response.Notification(r.destination.val().owner, transaction.id, "top up")
                                                                                    );
                                                                                }
                                                                            }

                                                                          Ни одной внешней зависимости с полным сохранением функциональности.

                                                                          Примерный тест:
                                                                          ExpandedWrap disabled
                                                                            final var sourceAccount = // new account
                                                                            ...
                                                                            final var response = subject.submit(new Transfer.Request(
                                                                                money,
                                                                                () -> sourceAccount,
                                                                                () -> destinationAccount,
                                                                                () -> { /* e.g. do nothing in this particular test case */ }
                                                                            ));
                                                                             
                                                                            // assertions on response

                                                                          И никаких моков нафиг не надо.
                                                                            Цитата korvin @
                                                                            Так я ж бомблю, что его почти не пишут, этот самый декларативный код, предпочитая обмазаться тестами и говнякать =)

                                                                            Так и не начнут же :)
                                                                            По крайней мере не раньше, чем меритократические тенденции возобладают в РФ :lol:

                                                                            Цитата
                                                                            Цитата D_KEY @
                                                                            А хорошо получалось или было все нестабильно?

                                                                            Всё было плохо.

                                                                            Ну значит или те, кто нахваливают TDD врут, или у тех, кого ты видил, было что-то не так сделано. Ну и вообще основная проблема-то может быть вообще в кадрах :D

                                                                            Цитата
                                                                            Если у тебя новые требование (новая формула, скажем, умножать нужно не на два, а на три), то естественно нужно написать новую функцию и использовать там, где нужно, а не трогать старую. Ты что, open-close принцип забыл?

                                                                            Я просто не могу смапить твой синтетический пример на реальный код :)

                                                                            Цитата

                                                                            А что такое «оптимизация декларативного кода»?

                                                                            Ну, скажем, у тебя из-за (слишком) ленивых вычислений в программе на haskell появляются моменты, когда память то почти свободна, то лавинообразно забивается и все начинает тупить.
                                                                            Соответственно, можно чуть иначе попытаться написать код и расход память станет стабильнее.
                                                                              По мне так декларативный код - утопия.
                                                                              Сообщение отредактировано: applegame -
                                                                                Цитата Wound @
                                                                                Так и что с ним не так то?

                                                                                То, что логгинг интересен по ходу исполнения кода, а не "на уровень выше".

                                                                                Цитата Wound @
                                                                                С чего вдруг сложной? В чем заключается сложность? Я не понимаю.

                                                                                Вводится дополнительный класс с дополнительной логикой.
                                                                                Сравни:
                                                                                ExpandedWrap disabled
                                                                                      interface ILogger
                                                                                      {
                                                                                        void Info (string message);
                                                                                      }
                                                                                   
                                                                                      interface ICalculator
                                                                                      {
                                                                                          int Sum(int a, int b);
                                                                                      }
                                                                                      
                                                                                      public class Calculator : ICalculator
                                                                                      {
                                                                                         ILogger _logger = null;
                                                                                         public Calculator (ILogger logger)
                                                                                         {
                                                                                            _logger = logger;
                                                                                         }
                                                                                      
                                                                                         public int Sum(int a, int b)
                                                                                         {
                                                                                            _logger?.Info ($"Sum({a},{b})");
                                                                                            return a + b;
                                                                                         }
                                                                                      }


                                                                                и

                                                                                ExpandedWrap disabled
                                                                                      interface ILogger
                                                                                      {
                                                                                        void Info (string message);
                                                                                      }
                                                                                   
                                                                                      interface ICalculatorWithIO
                                                                                      {
                                                                                          int Sum(int a, int b);
                                                                                      }
                                                                                   
                                                                                      interface ICalculator : ICalculatorWithIO
                                                                                      {
                                                                                      }
                                                                                   
                                                                                      public class Calculator : ICalculator
                                                                                      {
                                                                                         public int Sum(int a, int b)
                                                                                         {
                                                                                            return a + b;
                                                                                         }
                                                                                      }
                                                                                   
                                                                                      public class CalculatorWithLogger : ICalculatorWithIO
                                                                                      {
                                                                                         private ICalculator _calculator;
                                                                                         private readonly ILogger _logger;
                                                                                         public CalculatorWithLogger(ICalculator calc, ILogger logger)
                                                                                         {
                                                                                             if (calc == null)
                                                                                                throw new ArgumentNullException ("Calculator must be initialized.");
                                                                                   
                                                                                             _calculator = calc;
                                                                                             _logger = logger;
                                                                                         }
                                                                                      
                                                                                         public int Sum(int a, int b)
                                                                                         {
                                                                                            _logger?.Info ($"Call method Sum({a},{b})");
                                                                                            return _calculator.Sum(a, b);
                                                                                         }
                                                                                      }


                                                                                И если в варианте с моком тестировать надо только одну функцию, то в предлагаемом тобой подходе тестировать надо 3 функции.


                                                                                Цитата Wound @
                                                                                Если у тебя функция и жнец и жрец и на дуде игрец, тогда это явно говорит о плохом дизайне в твоем приложении и о жестких зависимостях, от которых нужно избавляться.

                                                                                Просто в реальном мире данные надо брать из многих разных источноков, сопоставлать эти данные и принимать какое-либо решение.

                                                                                Цитата Wound @
                                                                                Тестируемая функция в ЮТ не должна не от чего зависеть, она должна быть автономной. Т.е. если у тебя в каком то 1 методе ошибка. То в юнит тестах - должен быть сфейлен ровно 1 UT из 1000 запущенных, даже если в программе у тебя куча мест, которые используют эту функцию/метод.

                                                                                Именно для этого и нужны моки :)
                                                                                В примере выше тест для суммы выглядел бы так:
                                                                                ExpandedWrap disabled
                                                                                  public void Sum_2plus2_4 ()
                                                                                  {
                                                                                     // Arrange
                                                                                     ILogger logger = Substitute.For<ILogger>();
                                                                                     ICalculator calc = new Calculator (logger);
                                                                                   
                                                                                     // Act
                                                                                     int res = calc.Sum (2, 2);
                                                                                   
                                                                                     // Assert
                                                                                     Assert::AreEqual (4, res);
                                                                                   
                                                                                     // тут же можно проверить, вызвался ли логгер с правильными данными
                                                                                     logger.Received(1).Info("Sum(2,2)");
                                                                                  }

                                                                                как видишь, тут совершенно наплевать сколько там ошибок в логгере. Этот тест от кода логгера не зависит.
                                                                                  В жопу этот TDD, один хер реальное тестирование все равно происходит вручную в QA отделах. Есть конечно исключения, но в подавляющем большинстве не критических для жизни людей проектов все именно так.
                                                                                  Лично я пишу иногда тесты, но они как правило одноразовые. То есть потестировал, ошибки нашел, исправил, и все, валяются эти тесты годами и никогда не дают сбоев.
                                                                                  Сообщение отредактировано: applegame -
                                                                                    Цитата D_KEY @
                                                                                    По крайней мере не раньше, чем меритократические тенденции возобладают в РФ

                                                                                    Ну ладно, возвращаю тебе избирательное право =)

                                                                                    Цитата applegame @
                                                                                    По мне так декларативный код - утопия.

                                                                                    «Важны тенденции, а не абсолютные показатели» — Отто Фон БисмаркD_KEY


                                                                                    Цитата D_KEY @
                                                                                    Ну, скажем, у тебя из-за (слишком) ленивых вычислений в программе на haskell появляются моменты, когда память то почти свободна, то лавинообразно забивается и все начинает тупить.
                                                                                    Соответственно, можно чуть иначе попытаться написать код и расход память станет стабильнее.

                                                                                    Для этого нужно минимум изменений, которые никак не меняют логику.
                                                                                      Цитата Fester @
                                                                                      То, что логгинг интересен по ходу исполнения кода, а не "на уровень выше".

                                                                                      Логгинг интересен тогда, когда у тебя непонятный код, не покрытый UT, за который ты не ручаешься. Если у тебя все очевидно как работает - логирование нахрен и не нужно. Поэтому логгировать ты вполне себе можешь именно в том месте, где этому место, а не везде. Вот скажи, ты очевидные в реализации чистые функции, без сайд эффектов часто логируешь? И главное с какой целью?

                                                                                      Цитата Fester @
                                                                                      Вводится дополнительный класс с дополнительной логикой.

                                                                                      Нет там класса с дополнительной логикой, там есть обертка инкапсулирующая в себе 2 класса.

                                                                                      Цитата Fester @
                                                                                      Сравни:

                                                                                      И чем это лучше, расскажи пожалуйста? Плюс ко всему ты написал функцию с сайд эффектом.

                                                                                      Цитата Fester @
                                                                                      И если в варианте с моком тестировать надо только одну функцию, то в предлагаемом тобой подходе тестировать надо 3 функции.

                                                                                      Во втором варианте тестировать нужно тоже ровно 1 метод, без всяких моков. Неужели ты этого не понимаешь? Какой смысл тестировать например HttpClient/File/Logger/etc ?

                                                                                      Цитата Fester @
                                                                                      Просто в реальном мире данные надо брать из многих разных источноков, сопоставлать эти данные и принимать какое-либо решение.

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

                                                                                      Цитата Fester @
                                                                                      Именно для этого и нужны моки
                                                                                      В примере выше тест для суммы выглядел бы так:

                                                                                      Ага, только вот тебе автор как раз и пишет - что эти самые моки являются жесткой зависимостью, ты не можешь протестировать эту функцию без мока, в итоге мы приходим к тому, что тесты зачастую сложнее, чем само приложение, в итоге польза от таких UT стремится к нулю, а все тестирование сводится к тестированию моков. Ты статью то почитай, а :D
                                                                                        Цитата korvin @
                                                                                        «Важны тенденции, а не абсолютные показатели» — Отто Фон БисмаркD_KEY
                                                                                        Декларативное программирование красивое словечко не более того. Как коммунизм. Пишите декларативно:
                                                                                        ExpandedWrap disabled
                                                                                          <beautiful_life planet="earth"/>

                                                                                        Отличная программа я щетаю. Никаких рефакторингов, тестирования и прочего хлама. :lol: Осталось только написать The Browser, который сможет выполнить эту программу.
                                                                                        Сообщение отредактировано: applegame -
                                                                                          Цитата applegame @
                                                                                          и все, валяются эти тесты годами и никогда не дают сбоев.

                                                                                          Вот тоже с таким сталкивался.

                                                                                          D_KEY: во всяких этих стартапах зачастую рефакторинг и оптимизации проводят крайне редко, в основном, либо добавляют новые фичи, либо изменяют существующие, а это означает, что либо тесты правятся с каждым таким изменением, либо они валяются без реальной пользы, просто занимая лишнее время на билд проекта.
                                                                                            Цитата Fester @
                                                                                            // тут же можно проверить, вызвался ли логгер с правильными данными
                                                                                               logger.Received(1).Info("Sum(2,2)");

                                                                                            Зачем это делать? Проверить правильно ли разработчики логгера его написали? :huh:
                                                                                              Цитата applegame @
                                                                                              Декларативное программирование красивое словечко не более того. Как коммунизм.

                                                                                              ФП достаточно декларативно. Да даже в рамках традиционного ИП/ООП можно писать достаточно декларативно.
                                                                                                Цитата korvin @
                                                                                                – откуда мы знаем, что submit вообще вызовет accounts.load?
                                                                                                – откуда мы знаем, что submit вызовет accounts.load с параметром sourceAccount.id?
                                                                                                – откуда мы знаем, что submit не вызовет других методов accounts?

                                                                                                Не знаю как в Java, а NSubstitute умеет проверять какие методы, в каком порядке, сколько раз и с какими аргументами были вызваны. Вообще не вопрос.

                                                                                                Цитата korvin @
                                                                                                Т.е. наличие параметра Accounts в публичном конструкторе — это часть контракта, публичная внешняя зависимость, а как именно используется экземпляр Accounts — это деталь реализации и «знание» этой детали в тесте — прямое нарушение инкапсуляции.

                                                                                                Никто не мешает тебе изменить контракт на
                                                                                                ExpandedWrap disabled
                                                                                                      public void submit(Accounts accounts, Request r) {
                                                                                                          final var source = accounts.load(r.source);
                                                                                                          final var destination = accounts.load(r.destination);
                                                                                                   
                                                                                                          validate(source, destination, r.amount);
                                                                                                          final var transaction = Transaction.fresh(r.source, r.destination, r.amount);
                                                                                                          transactions.store(transaction);
                                                                                                          notifyUsers(source, destination, transaction.id);
                                                                                                      }

                                                                                                :) Просто разработчику было лень пихать accounts в каждый вызов :)


                                                                                                Цитата korvin @
                                                                                                Ни одной внешней зависимости с полным сохранением функциональности.

                                                                                                Да брось! Теже яйца, только в профиль :) Теперь тебе надо мокать Request и точно также мокать 2 аккаунта ;)
                                                                                                  Цитата Wound @
                                                                                                  Зачем это делать?

                                                                                                  Интеграционный тест: правильно ли твой калькулятор пользуется логгером. =)
                                                                                                    Цитата korvin @
                                                                                                    Интеграционный тест: правильно ли твой калькулятор пользуется логгером. =)

                                                                                                    Это если речь идет о интеграционном тесте, он же пишет юнит тест.
                                                                                                      Цитата korvin @
                                                                                                      ФП достаточно декларативно.
                                                                                                      Да ни фига, просто маскируют императивность монадами да хвостовыми рекурсиями. И в конечном счете все те же рефакторинги, тесты и косяки. Так уж сложилось, что наш физический мир императивен, поэтому копни любую декларативность и под ней будет архитолстенный слой махровой императивщины.
                                                                                                      Сообщение отредактировано: applegame -
                                                                                                        Цитата Wound @
                                                                                                        Зачем это делать? Проверить правильно ли разработчики логгера его написали?

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

                                                                                                        PS: у нас есть список строк по которому идет поиск ошибок в логах. Если изменяется выводимая в лог строка, то надо и этот список проапдейтить. Проверка контента в данном случае работает на ура. Правда должен признать, что это скорее прихоть шефа :D Он хочет когда-нибудь достичь дзена и вручить этот список клиентам, чтобы они сами научились локализовывать проблемы.
                                                                                                          Цитата Fester @
                                                                                                          Не знаю как в Java, а NSubstitute умеет проверять какие методы, в каком порядке, сколько раз и с какими аргументами были вызваны. Вообще не вопрос.

                                                                                                          Да вопрос не в этом, как «технически» узнать. Mockito тоже всё это умеет. Вопрос в том, какого чёрта мы это как бы знаем, это деталь реализации, которую юнит-тест знать не должен.

                                                                                                          Цитата Fester @
                                                                                                          Никто не мешает тебе изменить контракт на

                                                                                                          Это никак не решает проблему: в интерфейсе Accounts десяток методов, метод submit использует только один из них. А может два, а может только при каких-то условиях. Об этом ничего не сказано в сигнатуре метода (входных параметрах), поэтому и юнит-тест этих деталей знать не должен. А значит, мокать нужно не отдельные вызовы метода Accounts.load, а все методы Accounts, т.е. фактически делать не мок, а стаб. Либо в качестве зависимости нужно не Accounts указывать, а интерфейс, определённый в модуле Transfer и имеющий точно нужную сигнатуру, а ля
                                                                                                          ExpandedWrap disabled
                                                                                                            interface AccountLoader {
                                                                                                                Account load(Account.ID id);
                                                                                                            }

                                                                                                          и его использовать в сигнатуре метода.

                                                                                                          Цитата Fester @
                                                                                                          Теперь тебе надо мокать Request и точно также мокать 2 аккаунта

                                                                                                          Не надо мне мокать Request, я же написал пример теста, где там мок?

                                                                                                          Добавлено
                                                                                                          Цитата Wound @
                                                                                                          Это если речь идет о интеграционном тесте, он же пишет юнит тест.

                                                                                                          А это ещё одна проблема с моками: их любители часто думают, что пишут юнит-тест, но фактически получают интеграционный =)

                                                                                                          Добавлено
                                                                                                          Цитата applegame @
                                                                                                          Да ни фига, просто маскируют императивность монадами да хвостовыми рекурсиями.

                                                                                                          Никто ничего не маскирует, все пишут декларативную функциональную композицию. =)

                                                                                                          Цитата applegame @
                                                                                                          Так уж сложилось, что наш физический мир императивен

                                                                                                          Нет, наши физические компьютеры имеют императивную модель, а мир чисто конкурентно-событийный.
                                                                                                          Некоторые императивщики, конечно, пытаются переписать историю, но физически она не меняется.
                                                                                                            Цитата Fester @
                                                                                                            В данном случае это просто пример В реальном мире это может быть не логгер, а, скажем, нужно совершить 3 попытки коннекта и потом кинуть исключение. Или убедиться, что какая-то функция была вызвана с опреденными параметрами.

                                                                                                            Зачем три попытки коннекта тестировать юнит тестами? :blink:

                                                                                                            Добавлено
                                                                                                            Цитата korvin @
                                                                                                            А это ещё одна проблема с моками: их любители часто думают, что пишут юнит-тест, но фактически получают интеграционный =)

                                                                                                            Ну вот как в примере выше Fester и пишет UT с моками, тестирует коннекты и логеры и называет это UT.
                                                                                                              Цитата korvin @
                                                                                                              Вопрос в том, какого чёрта мы это как бы знаем, это деталь реализации, которую юнит-тест знать не должен.

                                                                                                              Так и во втором варианте надо знать детали :) Ну или можно не знать детали и мокать все, но тогда это будет, как ты сказал стаб :)


                                                                                                              Цитата korvin @
                                                                                                              Не надо мне мокать Request, я же написал пример теста, где там мок?

                                                                                                              Я не силен в Java, так что заранее прошу прощения, но:
                                                                                                              ExpandedWrap disabled
                                                                                                                    public Response submit(Request r) {
                                                                                                                        validate(r);
                                                                                                                        return transaction(r);
                                                                                                                    }
                                                                                                                 
                                                                                                                    private void validate(Request r) {
                                                                                                                        checkCompliance(r);
                                                                                                                        checkBalance(r);
                                                                                                                    }
                                                                                                                 
                                                                                                                    private void checkCompliance(Request r) {
                                                                                                                // исходя из твоей логики, ты ничего не должен знать о реализации, а значит тебе придется мокать все проперти и методы как у source, так и у destination.
                                                                                                                        if (r.source.val().hasSameOwnerAs(r.destination.val())) {
                                                                                                                 
                                                                                                                            return;
                                                                                                                        }
                                                                                                                // тут ты вызываешь заклушку, т.е. это таки Mock-объект от Request'а
                                                                                                                        r.checkCompliance.val();
                                                                                                                    }
                                                                                                                 
                                                                                                                    private void checkBalance(Request r) {
                                                                                                                // canWithdraw - еще одна функция, которую ты забыл мокнуть в своем тесте :)
                                                                                                                        if (r.source.val().canWithdraw(r.amount)) {
                                                                                                                            return;
                                                                                                                        }
                                                                                                                // надеюсь предусмотрен тест сценарий при котором будет вызывать это исключение... ну да, придется как-то иначе мокать canWithdraw, но что поделать?
                                                                                                                        throw new InsufficientFunds();
                                                                                                                    }
                                                                                                                 
                                                                                                                    private Response transaction(Request r) {
                                                                                                                // ой, еще и Transaction надо мокнуть.
                                                                                                                        final var transaction = Transaction.fresh(r.source.val().id, r.destination.val().id, r.amount);
                                                                                                                // а у source и destination еще и owner надо мокнуть, нет, зная реализацию Response мы понимаем, что null там вполне прокатит, но мы же исходим из того, что реализация нам не известна ;)
                                                                                                                        return new Response(
                                                                                                                                transaction,
                                                                                                                                new Response.Notification(r.source.val().owner, transaction.id, "withdraw"),
                                                                                                                                new Response.Notification(r.destination.val().owner, transaction.id, "top up")
                                                                                                                        );
                                                                                                                    }


                                                                                                              Как видишь Request придется тебе мокать целиком и полностью... ну или делать из него стаб. Тут я не хочу обсуждать как правильно это все называть. Как видим, ни от каких зависимостей ты не ушел, а реализацию тебе надо знать чтобы не мокать то, что не нужно в данном тесте.
                                                                                                                Цитата korvin @
                                                                                                                Никто ничего не маскирует, все пишут декларативную функциональную композицию. =)
                                                                                                                Ага, пользуясь тем, что аргументы функции вычисляются гарантированно раньше самой функции. Даже вон do-нотацию придумали, чтобы упростить маскировку.
                                                                                                                Цитата korvin @
                                                                                                                Нет, наши физические компьютеры имеют императивную модель, а мир чисто конкурентно-событийный.
                                                                                                                Наш мир, в первую очередь, причинно-следственный, и поэтому императивная модель куда ближе к этому миру, чем декларативная. Невозможно сварить борщ не составив последовательность действий. Даже если ты просто собираешь домик из готовых кубиков, ты не можешь начать строительство с крыши.
                                                                                                                Сообщение отредактировано: applegame -
                                                                                                                  Цитата Wound @
                                                                                                                  Зачем три попытки коннекта тестировать юнит тестами?

                                                                                                                  Ну есть скажем требование: "в случае неудачного соединения N-раз повторить повторить попытку, после чего сообщить пользователю об ощибке". Вполне себе нормальный сценарий, который тоже можно протестировать.
                                                                                                                    Цитата Fester @
                                                                                                                    Так и во втором варианте надо знать детали

                                                                                                                    Какие?

                                                                                                                    Цитата Fester @
                                                                                                                    Как видишь Request придется тебе мокать целиком и полностью...

                                                                                                                    Не вижу, зачем? Это pure data, там нечего мокать. Если ты про canWithdraw, то это простой чистый метод, зачем его мокать? Всякие БД и прочие внешние сервисы мокают из-за сайд-эффектов и из-за того, что поднимать БД/внешний сервис для каждого теста — крайне медленно.
                                                                                                                    Но можно canWithdraw вынести в Request как Lazy<Boolean>-поле, и снова мокать ничего не надо.

                                                                                                                    Цитата Fester @
                                                                                                                    Как видим, ни от каких зависимостей ты не ушел

                                                                                                                    Я их сделал явными и ограниченными.

                                                                                                                    Цитата Fester @
                                                                                                                    а реализацию тебе надо знать чтобы не мокать то, что не нужно в данном тесте.

                                                                                                                    Не надо, мне нужно знать только тип входных данных (Request) и тип выходных данных (Response), которые полностью описывают всё, что нужно.

                                                                                                                    Цитата applegame @
                                                                                                                    Ага, пользуясь тем, что аргументы функции вычисляются гарантированно раньше самой функции.

                                                                                                                    Что?

                                                                                                                    Цитата applegame @
                                                                                                                    Даже вон do-нотацию придумали, чтобы упростить маскировку.

                                                                                                                    Нет, не для этого.

                                                                                                                    Цитата applegame @
                                                                                                                    Наш мир, в первую очередь, причинно-следственный, и поэтому императивная модель

                                                                                                                    Не применима, т.к. позволяет менять уже существующие причинно-следственные события, когда в реальном мире они не меняются, только порождаются новые. Чисто. Функционально.

                                                                                                                    Цитата applegame @
                                                                                                                    Невозможно сварить борщ не составив последовательность действий. Даже если ты просто собираешь домик из готовых кубиков, ты не можешь начать строительство с крыши.

                                                                                                                    Эти аналогии вообще к чему? По-твоему функциональные программы пишут на лету, в рантайме? Программа и есть описание процесса, выполненное в той или иной нотации/модели.

                                                                                                                    Цитата Fester @
                                                                                                                    Ну есть скажем требование: "в случае неудачного соединения N-раз повторить повторить попытку, после чего сообщить пользователю об ощибке". Вполне себе нормальный сценарий, который тоже можно протестировать.

                                                                                                                    Ну вот смотри, D_KEY, вместо того, чтобы декомпозировать код и вынести логику retry в отдельный RetriableConnection с настраиваемой политикой, отдельно и независимо протестированную, Fester решил накостылять сомнительные тесты прям над бизнес-логикой, сохранив её сложность и зафиксировав эту сложность в спецификации (тесте). Чё-т не очень ему TDD помогает делать код/архитектуру лучше.
                                                                                                                      Цитата applegame @
                                                                                                                      В жопу этот TDD, один хер реальное тестирование все равно происходит вручную в QA отделах.

                                                                                                                      Да, давайте завалим QA отдел непротестированным говном, у них ведь мало работы на регрессе и проверке новой функциональности :lol:
                                                                                                                      Наличие дополнительного ручного тестирования никак не отменяет необходимость автоматических тестов на разных уровнях.
                                                                                                                      А TDD - это вообще про написание кода больше :)

                                                                                                                      Цитата
                                                                                                                      но в подавляющем большинстве не критических для жизни людей проектов все именно так.

                                                                                                                      Да, там еще, как правило, есть разные процедуры сертификации, а иногда и системы испытаний. Это никак не отменяет важность автоматического тестирования. Я бы даже сказал наоборот.
                                                                                                                      Более того, когда такое ручное тестирование находит ошибку, то по возможности нужно добавить автоматические тесты на это. Люди в следующий раз могут пропустить эту ошибку или она может не воспроизвестись, например.
                                                                                                                      Прогоны в CI позволят получить более надежную систему как раз :)
                                                                                                                        Цитата korvin @
                                                                                                                        Что?
                                                                                                                        Декларативность не подразумевает вообще никакой последовательности действий. Ты объявил какой результат желаешь получить и условия его получения и опа все волшебно само-собой сложилось. Красота.
                                                                                                                        Цитата korvin @
                                                                                                                        Не применима, т.к. позволяет менять уже существующие причинно-следственные события,
                                                                                                                        Каким образом императивная модель позволяет менять существующие события?
                                                                                                                        Цитата korvin @
                                                                                                                        когда в реальном мире они не меняются, только порождаются новые. Чисто. Функционально.
                                                                                                                        Я не знаю в каком реальном мире ты живешь, но явно не в этой Вселенной. :D Допустим у меня есть стул и я его покрасил, в реальном мире у меня есть только покрашенный стул, в мире ФП у меня остался и покрашенный и непокрашенный. Чудеса в решете :) ФП переносит объекты из прошлого в настоящее. Машина времени прямо. :)

                                                                                                                        Добавлено
                                                                                                                        Цитата D_KEY @
                                                                                                                        Наличие дополнительного ручного тестирования никак не отменяет необходимость автоматических тестов на разных уровнях.
                                                                                                                        А TDD - это вообще про написание кода больше
                                                                                                                        Ну да. Я не против автоматического тестирования вообще, я скорее разочарован в TDD, никак не помогает в конечном итоге, скорее мешает. Особенно если проект не очень крупный.
                                                                                                                        Сообщение отредактировано: applegame -
                                                                                                                          Цитата korvin @
                                                                                                                          Чё-т не очень ему TDD помогает делать код/архитектуру лучше.

                                                                                                                          Так не существует никаких волшебных техник, которые бы всегда приводили к тому, что любой человек начинает все правильно декомпозировать и архитектуру хорошую делать.
                                                                                                                          Вопрос в том, помогает ли TDD в этом при прочих равных. Твой тезис в том, что не помогает, есть масса сторонников TDD, которые утверждают, что им помогает. И там и там в качестве аргументов - опыт.
                                                                                                                          Со стороны судить сложно, так что я хочу попробовать это на своей практике и понять, насколько это помогает мне.

                                                                                                                          Кстати, обе стороны могут оказаться правыми в том смысле, что если человек не обладал навыками декомпозиции и пр., а начитавшись про TDD и начав его применять, стал об этом задумываться, то естественно он получить улучшения, а потом будет считать, что именно TDD ему помог. Хотя дело может быть исключительно в том, что он в принципе стал задумываться о тех вещах, о которых раньше не думал.
                                                                                                                            Цитата korvin @
                                                                                                                            Программа и есть описание процесса, выполненное в той или иной нотации/модели.
                                                                                                                            Нет. Декларативная программа - это не описание процесса, это описание конечного результата.
                                                                                                                              Цитата D_KEY @
                                                                                                                              Ну в твоем синтетическом примере может и не полез бы, а в реальном декларативном коде не вознкает потребности в рефакторинге?

                                                                                                                              Ну вот чуть менее синтетический:

                                                                                                                              ExpandedWrap disabled
                                                                                                                                import Data.Function ((&))
                                                                                                                                import Text.Printf
                                                                                                                                 
                                                                                                                                data SaleSum = SaleSum {
                                                                                                                                  totalCost     :: Double,
                                                                                                                                  totalQuantity :: Int
                                                                                                                                }
                                                                                                                                 
                                                                                                                                class SaleSummary s where
                                                                                                                                  summary :: s -> SaleSum
                                                                                                                                 
                                                                                                                                instance Semigroup SaleSum where
                                                                                                                                  (SaleSum c1 q1) <> (SaleSum c2 q2) = SaleSum (c1 + c2) (q1 + q2)
                                                                                                                                 
                                                                                                                                instance Monoid SaleSum where
                                                                                                                                  mempty = SaleSum 0.0 0
                                                                                                                                 
                                                                                                                                instance Show SaleSum where
                                                                                                                                  show (SaleSum cost quantity) =
                                                                                                                                    "Total cost = " ++ printf "%.2f" cost ++ ", quantity = " ++ show quantity
                                                                                                                                 
                                                                                                                                totalSummary :: SaleSummary s => [s] -> SaleSum
                                                                                                                                totalSummary xs = xs & map summary & mconcat
                                                                                                                                 
                                                                                                                                ----------------------------------------------------------------
                                                                                                                                 
                                                                                                                                data Sale = Sale {
                                                                                                                                  itemID   :: String,
                                                                                                                                  price    :: Double,
                                                                                                                                  quantity :: Int
                                                                                                                                  -- maybe some other fields
                                                                                                                                }
                                                                                                                                 
                                                                                                                                instance SaleSummary Sale where
                                                                                                                                  summary s = SaleSum (price s * fromIntegral (quantity s)) (quantity s)
                                                                                                                                 
                                                                                                                                testSales = [
                                                                                                                                  Sale "Foo" 0.20 4,
                                                                                                                                  Sale "Bar" 1.10 3
                                                                                                                                  ]
                                                                                                                                 
                                                                                                                                main = print $ totalSummary testSales

                                                                                                                              https://ideone.com/xcshAS

                                                                                                                              Добавлено
                                                                                                                              Цитата applegame @
                                                                                                                              в мире ФП у меня остался и покрашенный и непокрашенный

                                                                                                                              Чушь. В мире ФП у тебя осталась история, что стул был непокрашенный, так же, как и в реальном.
                                                                                                                              Сообщение отредактировано: korvin -
                                                                                                                                Цитата korvin @
                                                                                                                                Это pure data, там нечего мокать.

                                                                                                                                Ты это знаешь только из-за того, что знаешь как оно реализовано.

                                                                                                                                Цитата korvin @
                                                                                                                                Если ты про canWithdraw, то это простой чистый метод, зачем его мокать?

                                                                                                                                Затем, что ты этот метод вызываешь и действуешь исходя из возвращаемого этим методом значения.

                                                                                                                                Цитата korvin @
                                                                                                                                Но можно canWithdraw вынести в Request как Lazy<Boolean>-поле, и снова мокать ничего не надо.

                                                                                                                                Это поле надо будет как-то заполнить. Как ты собираешься это сделать?

                                                                                                                                Цитата korvin @
                                                                                                                                Я их сделал явными и ограниченными.

                                                                                                                                Не сделал. Ты сделал 3 проперти (которые на секундочку должны быть полноценными объектами, а еще точнее абстракциями) и добавил указатель на функцию/делегат, которую надо вызвать в submit. Если у тебя в классе Transfer будет 10 функций (submit, rollback, list, rejeckt, еще что-то) и все они будут что-то рассчитывать опираять на данные из source и/или destination, то тебе придется добавлять 10 функций твой Request. А самое смешное, что если выносить эти функции в Request, то поля source, destination и money становятся не нужны :D

                                                                                                                                Цитата korvin @
                                                                                                                                Не надо, мне нужно знать только тип входных данных (Request) и тип выходных данных (Response), которые полностью описывают всё, что нужно.

                                                                                                                                А еще Account и Money чтобы работа шла так, как было задумано, а не по умолчанию :)

                                                                                                                                Цитата korvin @
                                                                                                                                вместо того, чтобы декомпозировать код и вынести логику retry в отдельный RetriableConnection с настраиваемой политикой, отдельно и независимо протестированную, Fester решил накостылять сомнительные тесты прям над бизнес-логикой, сохранив её сложность и зафиксировав эту сложность в спецификации (тесте).

                                                                                                                                сорян конечно, но RetriableConnection тоже надо тестировать.
                                                                                                                                  Цитата Fester @
                                                                                                                                  Ну есть скажем требование: "в случае неудачного соединения N-раз повторить повторить попытку, после чего сообщить пользователю об ощибке". Вполне себе нормальный сценарий, который тоже можно протестировать.

                                                                                                                                  GUI ты тоже юнит тестами тестишь? Вот есть скажем требование что размер формы должен быть 100x200, вполне себе нормальный сценарий, который тоже можно протестировать. Или скажем "в случае неудачного соединения N-раз должно выскакивать окно - "Подключение невозможно, проверьте параметры сети", которое должно располагаться по центру экрана". Вполне себе нормальный сценарий, который тоже можно протестировать. Ага?
                                                                                                                                    Цитата korvin @
                                                                                                                                    Чушь. В мире ФП у тебя осталась история, что стул был непокрашенный, так же, как и в реальном.
                                                                                                                                    Только в реальном мире это именно что история, объектами из которой ты не можешь воспользоваться, а в ФП вполне себе можешь :) ФП более высокая степень абстракции, чем императив, а значит дальше от реальности.
                                                                                                                                    Но мы отклонились от темы. ФП и декларативное программирование ортогональные вещи на самом деле.
                                                                                                                                    Сообщение отредактировано: applegame -
                                                                                                                                      Цитата Fester @
                                                                                                                                      Ты это знаешь только из-за того, что знаешь как оно реализовано.

                                                                                                                                      В смысле «я знаю»? класс Request публичный с публичными полями. Все знают, как оно реализовано.

                                                                                                                                      Цитата Fester @
                                                                                                                                      Затем, что ты этот метод вызываешь и действуешь исходя из возвращаемого этим методом значения.

                                                                                                                                      :facepalm: может мне ещё и JDK мокать? java.lang.Math.sin, например? А то ж вдруг я его вызываю, да синус считаю. Ты сам-то мокаешь стандартную библиотеку?

                                                                                                                                      Цитата Fester @
                                                                                                                                      Это поле надо будет как-то заполнить. Как ты собираешься это сделать?

                                                                                                                                      Также как и любое другое поле: передам нужное мне значение в конструктор. Ты сходи перечитай мой пример, там в самом конце показано. Или ты не знаешь, как структурки создавать? У тебя что, одни int'ы и double'ы в коде? Не пробовал там
                                                                                                                                      ExpandedWrap disabled
                                                                                                                                        struct Point {
                                                                                                                                            double x;
                                                                                                                                            double y;
                                                                                                                                        }

                                                                                                                                      написать? Compound data type называется.

                                                                                                                                      Цитата Fester @
                                                                                                                                      (которые на секундочку должны быть полноценными объектами, а еще точнее абстракциями)

                                                                                                                                      С хера ли? Ты что, Егора Бугаенко начитался/насмотрелся? Plain data там, никаких объектов, никаких абстракций.

                                                                                                                                      Цитата Fester @
                                                                                                                                      Если у тебя в классе Transfer будет 10 функций (submit, rollback, list, rejeckt, еще что-то) и все они будут что-то рассчитывать опираять на данные из source и/или destination, то тебе придется добавлять 10 функций твой Request.

                                                                                                                                      :facepalm:
                                                                                                                                      1) у каждой функции своя область определения, значит у каждой будет свой Request
                                                                                                                                      2) нафига перегружать модуль Transfer ещё десятком какимх-то функций? Про Single Responsibility Principle слышал?

                                                                                                                                      Цитата Fester @
                                                                                                                                      А еще Account и Money чтобы работа шла так, как было задумано, а не по умолчанию

                                                                                                                                      :facepalm: Сходи int и (+) замокай, а то вдруг они как-то не так сработают и у тебя всё пойдёт не так как было задумано. И malloc заодно.

                                                                                                                                      Цитата applegame @
                                                                                                                                      ФП и декларативное программирование ортогональные вещи на самом деле.

                                                                                                                                      Ортогональные. Но на ФП как-то код более декларативен получается по-умолчанию.
                                                                                                                                        Цитата korvin @
                                                                                                                                        Ортогональные. Но на ФП как-то код более декларативен получается по-умолчанию.
                                                                                                                                        Ну не знаю. По моему все такое же описание процесса. Та же упомянутая тобой композиция функций отнюдь не декларативна.
                                                                                                                                          Цитата korvin @
                                                                                                                                          В смысле «я знаю»? класс Request публичный с публичными полями. Все знают, как оно реализовано.

                                                                                                                                          Все знают публичные поля класса Request (имя и тип), но никто не знает какие из этих публичных полей используются функцией submit. А значит, для вызова submit тебе надо либо инициализировать все поля класса Request, либо знать какие поля класса Request использует submit, чтобы не создавать лишние объекты.

                                                                                                                                          Цитата korvin @
                                                                                                                                          может мне ещё и JDK мокать? java.lang.Math.sin, например?

                                                                                                                                          java.lang.Math.sin мокать не нужно, а, скажем, доступ к файловой системе нужно.

                                                                                                                                          Цитата korvin @
                                                                                                                                          А то ж вдруг я его вызываю, да синус считаю. Ты сам-то мокаешь стандартную библиотеку?

                                                                                                                                          Я мокаю то, что мне нужно. Например доступ к реестр или доступ к файлам - тоже вполне себе стандартные штуки.

                                                                                                                                          Цитата korvin @
                                                                                                                                          Также как и любое другое поле: передам нужное мне значение в конструктор

                                                                                                                                          Ты понимаешь, что вместо вызова некой функции (в данном случае canWithdraw) ты предлагаешь создать некое поле, которое должно заполняться гезультатом работы этой функции. При этом мало того, что функцию все равно придется вызывать (в продуктивном коде), так еще у тебя и Request'e добавится куча полей.


                                                                                                                                          Цитата korvin @
                                                                                                                                          С хера ли? Ты что, Егора Бугаенко начитался/насмотрелся? Plain data там, никаких объектов, никаких абстракций.

                                                                                                                                          Не знаю кто такой Егор Бугаенко.
                                                                                                                                          Никаких plain data там нет. Смотри свой собственный код, у тебя у Account есть как минимум две функции: hasSameOwnerAs и canWithdraw плюс к этому read-only проперти owner. Ясен хрен, что тут должна быть абстракция.

                                                                                                                                          Цитата korvin @
                                                                                                                                          1) у каждой функции своя область определения, значит у каждой будет свой Request

                                                                                                                                          Отлично! Т.е. Request - это тоже нифига не plain data объект!
                                                                                                                                          Это должен быть интерфейс типа
                                                                                                                                          ExpandedWrap disabled
                                                                                                                                            public interface IRequest
                                                                                                                                            {
                                                                                                                                               Money Amount {get;} // тут хз, возможно тоже должна быть абстракция
                                                                                                                                               IAccount Source {get;}
                                                                                                                                               IAccount Destination {get;}
                                                                                                                                               Action CheckCompliance{ get; set; }
                                                                                                                                            }



                                                                                                                                          Цитата korvin @
                                                                                                                                          2) нафига перегружать модуль Transfer ещё десятком какимх-то функций? Про Single Responsibility Principle слышал?

                                                                                                                                          ingle Responsibility Principle ничего не говорит о том, что в классе должна быть одна функция.
                                                                                                                                            Цитата Fester @
                                                                                                                                            Все знают публичные поля класса Request (имя и тип), но никто не знает какие из этих публичных полей используются функцией submit

                                                                                                                                            Все используются, именно поэтому Request является типом аргумента submit. Это её (submit) область определения.

                                                                                                                                            Цитата Fester @
                                                                                                                                            а, скажем, доступ к файловой системе нужно.

                                                                                                                                            Конечно, ведь это внешний ресурс с побочными эффектами.

                                                                                                                                            Цитата Fester @
                                                                                                                                            Ты понимаешь, что вместо вызова некой функции (в данном случае canWithdraw) ты предлагаешь создать некое поле, которое должно заполняться гезультатом работы этой функции.

                                                                                                                                            Ну тебе ж не нравилось, что я вызываю простую чистую функцию. Вот тебе альтернативное решение.

                                                                                                                                            Цитата Fester @
                                                                                                                                            При этом мало того, что функцию все равно придется вызывать (в продуктивном коде)

                                                                                                                                            И что? Это дело этой функции, с точки зрения submit это просто bool.

                                                                                                                                            Цитата Fester @
                                                                                                                                            так еще у тебя и Request'e добавится куча полей.

                                                                                                                                            Какая куча полей?

                                                                                                                                            Цитата Fester @
                                                                                                                                            Смотри свой собственный код, у тебя у Account есть как минимум две функции: hasSameOwnerAs и canWithdraw

                                                                                                                                            И что? Если они будут внешние, что принципиально изменится?

                                                                                                                                            Ну вынеси их в статику
                                                                                                                                            ExpandedWrap disabled
                                                                                                                                              class Account {
                                                                                                                                                  public final ID id;
                                                                                                                                                  public final Money balance;
                                                                                                                                                  ...
                                                                                                                                               
                                                                                                                                                  public static boolean haveSameOwners(Account a, Account b) { ... }
                                                                                                                                                  public static boolean canWithdraw(Account a, Money amount) { ... }
                                                                                                                                              }

                                                                                                                                            Что-то поменялось?

                                                                                                                                            Цитата Fester @
                                                                                                                                            Ясен хрен, что тут должна быть абстракция.

                                                                                                                                            :facepalm: с хера ли? Тебе что, инъекцию абстракций сделали?

                                                                                                                                            Цитата Fester @
                                                                                                                                            Т.е. Request - это тоже нифига не plain data объект!

                                                                                                                                            :facepalm: откуда такой глупый вывод?

                                                                                                                                            Цитата Fester @
                                                                                                                                            Это должен быть интерфейс типа

                                                                                                                                            Нет, не должен.

                                                                                                                                            Цитата Fester @
                                                                                                                                            ingle Responsibility Principle ничего не говорит о том, что в классе должна быть одна функция.

                                                                                                                                            Не говорит. Он говорит, что в классе не должно быть десяток каких-то функций, не имеющих отношения к классу.
                                                                                                                                              Цитата korvin @
                                                                                                                                              Все используются, именно поэтому Request является типом аргумента submit. Это её (submit) область определения.

                                                                                                                                              Значит ли это, что других функций в классе Transfer нет? Или все другие функции должны точно также как и submit использовать все поля Request'а?

                                                                                                                                              Цитата korvin @
                                                                                                                                              Ну тебе ж не нравилось, что я вызываю простую чистую функцию. Вот тебе альтернативное решение.

                                                                                                                                              Ты это для меня делаешь? :lol: Я думал, что ты тут пытаешься избавиться от моков :lol:

                                                                                                                                              Цитата korvin @
                                                                                                                                              И что? Если они будут внешние, что принципиально изменится?

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

                                                                                                                                              Цитата korvin @
                                                                                                                                              Ну вынеси их в статику

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

                                                                                                                                              Цитата korvin @
                                                                                                                                              Он говорит, что в классе не должно быть десяток каких-то функций, не имеющих отношения к классу.

                                                                                                                                              Ну значит у класса может быть больше одной функции. Слава богу! Осталось только уточнить, могут ли эти функции использовать разные данные... Или если у тебя 10 функций, то ты шлепаешь свой Request для каждой? Так чтобы каждая функция использовала все публичные поля своего Request'а?
                                                                                                                                                Цитата Fester @
                                                                                                                                                Значит ли это, что других функций в классе Transfer нет?

                                                                                                                                                В моём — нет.

                                                                                                                                                Цитата Fester @
                                                                                                                                                Или все другие функции должны точно также как и submit использовать все поля Request'а?

                                                                                                                                                Ты чем читал? У каждой функции свои область определения и область значений. Что не ясно в этой фразе?

                                                                                                                                                Цитата Fester @
                                                                                                                                                Я думал, что ты тут пытаешься избавиться от моков

                                                                                                                                                У меня-то их и нет.

                                                                                                                                                Цитата Fester @
                                                                                                                                                Изменится то, что ты уже 2-й раз переписываешь свой код и все это ради того, чтобы не было моков.

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

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

                                                                                                                                                С чего б вдруг?

                                                                                                                                                Цитата Fester @
                                                                                                                                                Или если у тебя 10 функций, то ты шлепаешь свой Request для каждой?

                                                                                                                                                Если они требуют разные входные данные, то, естественно у них будут разные типы аргумента. А ты что, клепаешь God object'ы?

                                                                                                                                                Цитата Fester @
                                                                                                                                                Так чтобы каждая функция использовала все публичные поля своего Request'а?

                                                                                                                                                В подавляющем большинстве случаев — да. Если функция не использует какие-то поля, то зачем эти поля нужны? Для красоты?
                                                                                                                                                Сообщение отредактировано: korvin -
                                                                                                                                                  Цитата korvin @
                                                                                                                                                  У меня-то их и нет.

                                                                                                                                                  Ты пытаешься избавиться от них создавая левые параметры в Request'е :)

                                                                                                                                                  Цитата korvin @
                                                                                                                                                  Я не переписываю, я пытаюсь объяснить тебе, болезному, в чём разница между чистой функцией и объектом.

                                                                                                                                                  Функция, которая ничего не делает - это заглушка, т.е. фактически это и есть mock. Каждый объект, в котором тебе нужна "чистая функция" - это есть твой mock-объект.


                                                                                                                                                  Цитата korvin @
                                                                                                                                                  Если они требуют разные входные данные, то, естественно у них будут разные типы аргумента. А ты что, клепаешь God object'ы?

                                                                                                                                                  Если у меня есть сущность "пользователь", у которого есть фамилия, имя, день рождения, емыл и физический адрес (страна, город, индекс, улица, дом) и у меня есть 2 сервиса: 1) поздравляет пользователя с днем рождения по мылу и 2) отправляет новогоднюю открытку по почте, то я не буду шлепать 2 объекта так чтобы в одном были дата рождения и е-мыл, а в другом почтовый адрес. Я уж как-нибудь обойдуть одним объектом, который полностью описывает "пользователя" и не буду убиваться из-за некоторой избыточности. И нет, это не God object.

                                                                                                                                                  Цитата korvin @
                                                                                                                                                  В подавляющем большинстве случаев — да. Если функция не использует какие-то поля, то зачем эти поля нужны? Для красоты?

                                                                                                                                                  В подавляющем случае как раз нет и в 99% случаев функция не использует все поля. Просто из-за того, что никто не будет шлепать отдельный объект ради каждой функции.
                                                                                                                                                    Цитата Fester @
                                                                                                                                                    Функция, которая ничего не делает - это заглушка, т.е. фактически это и есть mock. Каждый объект, в котором тебе нужна "чистая функция" - это есть твой mock-объект.

                                                                                                                                                    Не совсем понял эту цитату. Ты точно понимаешь, что подразумевается под чистыми функциями(Pure function)?
                                                                                                                                                    Сообщение отредактировано: Wound -
                                                                                                                                                      Некогда использовать такое. Надо баги фиксать :lol:
                                                                                                                                                        Цитата Wound @
                                                                                                                                                        Не совсем понял эту цитату.

                                                                                                                                                        Я опирался на код:
                                                                                                                                                        Цитата korvin @
                                                                                                                                                        final var response = subject.submit(new Transfer.Request(
                                                                                                                                                        money,
                                                                                                                                                        () -> sourceAccount,
                                                                                                                                                        () -> destinationAccount,
                                                                                                                                                        () -> { /* e.g. do nothing in this particular test case */ }
                                                                                                                                                        ));

                                                                                                                                                        В данном тесте - последний параметр - это заглушка и, соответственно Request - это мок-объект.
                                                                                                                                                          Цитата Fester @
                                                                                                                                                          Функция, которая ничего не делает - это заглушка, т.е. фактически это и есть mock.

                                                                                                                                                          Нет.

                                                                                                                                                          Цитата Fester @
                                                                                                                                                          Я уж как-нибудь обойдуть одним объектом, который полностью описывает "пользователя" и не буду убиваться из-за некоторой избыточности

                                                                                                                                                          Любитель сильной связности. Понятно.

                                                                                                                                                          Цитата Fester @
                                                                                                                                                          В подавляющем случае как раз нет и в 99% случаев функция не использует все поля.

                                                                                                                                                          Только в говнокоде.

                                                                                                                                                          Цитата Fester @
                                                                                                                                                          Просто из-за того, что никто не будет шлепать отдельный объект ради каждой функции.

                                                                                                                                                          Просто убогие недоязычки не позволяют легко и просто описывать типы. Впрочем, на сегодняшний день из таких только Java осталась, да Pascal. На Джаве ты не пишешь, значит на Паскале?

                                                                                                                                                          Цитата Fester @
                                                                                                                                                          В данном тесте - последний параметр - это заглушка и, соответственно Request - это мок-объект.

                                                                                                                                                          Нет. Request даже не объект.
                                                                                                                                                            Вопрос к практикам. Вот начинаем мы какую-то новую штуку кодить по TDD. Мы начинаем же с самых высокоуровневых задач? Т.е. сначала пишем тест на высокоуровневую задачу, верно? Потом, при развитии, проверяемая такими тестами функциональность в любом случае будет раскидана по нескольким юнитам. Так вот вопрос, эти более ранние тесты становятся интеграционными? Ведь у каждого юнита появится свой тест.
                                                                                                                                                            Сообщение отредактировано: D_KEY -
                                                                                                                                                              Цитата D_KEY @
                                                                                                                                                              Так вот вопрос, эти более ранние тесты становятся интеграционными? Ведь у каждого юнита появится свой тест.

                                                                                                                                                              Не обязательно, зависит от архитектуры. У тебя же высокоуровневая задача является некоторой композицией низкоуровневых, вот смотри, в моём примере, я переделал высокоуровневый сервис (относительно Accounts, Transactions, Compliance, Notifier) Transfer, чтобы он не включал зависимости внутрь себя, т.е. вместо
                                                                                                                                                              ExpandedWrap disabled
                                                                                                                                                                Transfer
                                                                                                                                                                  + Accounts
                                                                                                                                                                  + Transactions
                                                                                                                                                                  ...

                                                                                                                                                              получилось
                                                                                                                                                              ExpandedWrap disabled
                                                                                                                                                                Composer
                                                                                                                                                                  + Transfer
                                                                                                                                                                  + Accounts
                                                                                                                                                                  + Transactions
                                                                                                                                                                  ...

                                                                                                                                                              где Transfer хоть и реализует высокоуровневую логику посредством остальных компонентов, но расположен на том же (структурном?) уровне фактически.
                                                                                                                                                              Composer в свою очередь может быть абстрактным и тривиальным и иметь свои юнит-тесты. Естественно, можно сделать и интеграционные тесты (они ж не взаимозаменяемы с юнит-тестами), а на самом-самом верхнем уровне ещё добавить функциональные/end-to-end/системные тесты.

                                                                                                                                                              Добавлено
                                                                                                                                                              Ну, собственно, если у тебя правильная инверсия зависимостей, то высокоуровневый модуль полностью независим от низкоуровневых и ты можешь писать нормальные юнит-тесты для него )
                                                                                                                                                              Сообщение отредактировано: korvin -
                                                                                                                                                                Цитата korvin @
                                                                                                                                                                Ну, собственно, если у тебя правильная инверсия зависимостей, то высокоуровневый модуль полностью независим от низкоуровневых и ты можешь писать нормальные юнит-тесты для него )

                                                                                                                                                                Это да. Но вопрос изначальных тестов. Они же исходят из высокоуровневых задач и будут проверять работу целиком. Соответственно, они должны будут или перейти на уровень интеграционных или, став юнит-тестами для самих высокоуровневых юнитов, перестанут проверять высокоуровневые задачи.
                                                                                                                                                                  Цитата D_KEY @
                                                                                                                                                                  Но вопрос изначальных тестов. Они же исходят из высокоуровневых задач и будут проверять работу целиком

                                                                                                                                                                  Э, ты путаешь тёплое с мягким, тип проверки зависит от типа теста и не зависит от уровня тестируемого компонента в системе. Работу целиком проверяют end-to-end и системные тесты. Как только ты принял решение выделить из высокоуровневого модуля под-модуль, сначала ты должен поменять тесты соответствующим образом, в этом же суть TDD: любое изменение начинается с тестов, т.е. сначала нужно убедиться, меняется ли архитектура/требование, потом отразить изменения в тестах, если нужно и только потом — в реализации.

                                                                                                                                                                  Тут всё зависит от того, выносишь ты субкомпонент как внешнюю зависимость или оставляешь его деталью внутренней реализации суперкомпонента:

                                                                                                                                                                  – если ты делаешь инверсию зависимости для этого субкомпонента, то твой изначальный тест суперкомпонента нужно разбить на два:
                                                                                                                                                                  1) юнит-тест, который будет мокать/стабать интерфейс зависимости и проверять корректность входа/выхода суперкомпонента
                                                                                                                                                                  2) интеграционный, который также будет мокать/стабать интерфейс зависимости, но проверять само взаимодействие, т.е. например, убеждаться, что суперкомпонент при каком-то входе вызывает метод интерфейса зависимости, нужное количество раз с правильными параметрами

                                                                                                                                                                  - если ты не делаешь инвресию зависимости, просто выносишь часть кода суперкомпонента в субкомпонент и используешь последний как некую библиотеку, то объект субкомпонента будет инстанциироваться внутри суперкомпонента (если это не так и ты передаёшь экземпляр субкопонента в публичный конструктор супера, то это уже внешняя зависимость и тебе лучше сделать полноценную инверсию и перейти к первому случаю) и ни мокать, ни стабать ты его не можешь и не должен, он всё также остаётся деталью внутренней реализации и изначальный тест у тебя не меняется.
                                                                                                                                                                    Цитата korvin @
                                                                                                                                                                    если ты делаешь инверсию зависимости для этого субкомпонента, то твой изначальный тест суперкомпонента нужно разбить на два:
                                                                                                                                                                    1) юнит-тест, который будет мокать/стабать интерфейс зависимости и проверять корректность входа/выхода суперкомпонента
                                                                                                                                                                    2) интеграционный, который также будет мокать/стабать интерфейс зависимости, но проверять само взаимодействие, т.е. например, убеждаться, что суперкомпонент при каком-то входе вызывает метод интерфейса зависимости, нужное количество раз с правильными параметрами

                                                                                                                                                                    Во, кажется это ответ на мой вопрос :)
                                                                                                                                                                    Осталось понять, как правильно писать интеграционные тесты в C++ :D
                                                                                                                                                                    А то я так понимаю теперь, что у меня всегда были юнит-тесты, которые пишут разрабы, и системные, которые пишут qa.
                                                                                                                                                                      Кстати, возможно, в интеграционном тесте лучше не мокать субкомпонент, или добавить к мокнуты ещё тесты с использованием реального инстанса субкомпонента, иначе некоторые изменения не отловятся тестами
                                                                                                                                                                        Цитата applegame @
                                                                                                                                                                        По мне так декларативный код - утопия.
                                                                                                                                                                        Вот погоди, нейронки выйдут из эмбрионального состояния, и всякоразные Плюсы с Питонами уйдут в нишу автокода.

                                                                                                                                                                        Добавлено
                                                                                                                                                                        Цитата Fester @
                                                                                                                                                                        Вводится дополнительный класс с дополнительной логикой.
                                                                                                                                                                        ...
                                                                                                                                                                        Не мути воду. Ты не такую задачу поставил. В том виде. как ты её поставил:
                                                                                                                                                                        Цитата Fester @
                                                                                                                                                                        Ну вот представим, что у тебя есть 2 компоненты: калькулятор и логгер. Все действия калькулятора должны быть запротоколированы. Как должен выглядить идеальный юнит-тест суммы?
                                                                                                                                                                        подход Wound не просто правилен, он единственно праведно верный. Ты же сейчас путём волшебных ментальных манипуляций придумал новую задачу: придумать реализующую исходную задачу архитектуру, которую было бы удобно тестить.

                                                                                                                                                                        Добавлено
                                                                                                                                                                        P.S. Прочитал пока только половину темы.
                                                                                                                                                                          Цитата Qraizer @
                                                                                                                                                                          подход Wound не просто правилен, он единственно праведно верный.

                                                                                                                                                                          Это чистой воды Aspect, вряд ли он это мог предложить,
                                                                                                                                                                          так что подходов уже как минимум два ;) а подход назывется no coupling
                                                                                                                                                                          или по народному private responsibility или has a, предложил его кажется кто то из Борланда году
                                                                                                                                                                          в 1993-1994, ох как Я люблю так много умных слов ;)

                                                                                                                                                                          Добавлено
                                                                                                                                                                          Цитата D_KEY @
                                                                                                                                                                          Осталось понять, как правильно писать интеграционные тесты в C++ :D

                                                                                                                                                                          Включать здравый смысл, и перестать разводить не нужные теории ;)
                                                                                                                                                                          Сообщение отредактировано: sergioK -
                                                                                                                                                                            Цитата Qraizer @
                                                                                                                                                                            Вот погоди, нейронки выйдут из эмбрионального состояния, и всякоразные Плюсы с Питонами уйдут в нишу автокода.
                                                                                                                                                                            И долго еще осталось погождать? :)
                                                                                                                                                                              Цитата applegame @
                                                                                                                                                                              Ну да. Я не против автоматического тестирования вообще, я скорее разочарован в TDD, никак не помогает в конечном итоге, скорее мешает. Особенно если проект не очень крупный.
                                                                                                                                                                              И ещё особенней, если очень крупный. Оракл вон наособничался, когда читал, волосы даже в интимных местах шевелились.

                                                                                                                                                                              Добавлено
                                                                                                                                                                              Цитата Wound @
                                                                                                                                                                              Вот есть скажем требование что размер формы должен быть 100x200, вполне себе нормальный сценарий, который тоже можно протестировать. Или скажем "в случае неудачного соединения N-раз должно выскакивать окно - "Подключение невозможно, проверьте параметры сети", которое должно располагаться по центру экрана". Вполне себе нормальный сценарий, который тоже можно протестировать. Ага?
                                                                                                                                                                              Всё просто: тестировать надо всё, что написано в спецификациях. Ничего там не заявленного, тестировать не надо.

                                                                                                                                                                              Добавлено
                                                                                                                                                                              Цитата D_KEY @
                                                                                                                                                                              Вот начинаем мы какую-то новую штуку кодить по TDD. Мы начинаем же с самых высокоуровневых задач? Т.е. сначала пишем тест на высокоуровневую задачу, верно? Потом, при развитии, проверяемая такими тестами функциональность в любом случае будет раскидана по нескольким юнитам. Так вот вопрос, эти более ранние тесты становятся интеграционными? Ведь у каждого юнита появится свой тест.
                                                                                                                                                                              Хороший вопрос. Готовый функционал лучше всего покрывается тестами снизу вверх. Пишешь юниты на каждый чих, интеграцией проверяешь их ...э-э-э, интеграцию. Разработка обычно идёт так же, а вот проектирование в обратном направлении. Я лично слабо себе представляю создание тестов без проверки их на реализованном поведении, т.к. при наличии фейла и следовательно для выявления его причины легче разобраться в чёткой логике, описанной формальным языком с формальной грамматикой, чем в его формальном описании на нечётком человеческом с множеством потенциальных неточностей, неоднозначностей и недосказанностей и местами противоречий, что не были замечены на извиняюсь спринтах.

                                                                                                                                                                              Добавлено
                                                                                                                                                                              Цитата applegame @
                                                                                                                                                                              И долго еще осталось погождать?
                                                                                                                                                                              Я чего ты ждёшь-то собсвтенно? Итогов по их спору, что ли?
                                                                                                                                                                              По Wound-у требуется один юнит на интерфейс калькулятора, ещё один на интерфейс логгера и отдельный интегрант на их композицию. Этого достаточно на все случаи жизни. Вообще все. Пока не изменятся спецификации интерфейсов, конечно. Сколько ты не будешь мутить их комбинаций, эти три теста всегда будут к ним применимы, и всегда будут адекватны. Любая отдельная реализация обязана будет проходить эти тесты, без вариантов. Откуда у Fester там взялся зоопарк, я не имею ни малейшего понятия. Понятно, что отдельные реализации каждого из этих интерфейсов могут захотеть по-разному его расширить, и тогда на каждое такое расширение потребуется отдельный юнит, дополняющий, но не заменяющий, обобщённый юнит этого интерфейса. В итоге имеем: один общий юнит на каждый интерфейс, опциональный дополняющий его один частный юнит на каждую его реализацию и один интегрант на их композицию. Всё.
                                                                                                                                                                              Проблема лишь в том, что реализации пасскритериев (именно реализации пасскритериев, а не собственно пасскритерии) для каждой частной реализации общего интерфейса могут отличаться в связи с тесной связью с этой самой реализацией, что затруднит создание обобщённых юнитов. Но это же не проблема, ведь, так? Мы тут прогаммеры или куда?
                                                                                                                                                                              Сообщение отредактировано: Qraizer -
                                                                                                                                                                                Цитата Qraizer @
                                                                                                                                                                                Я лично слабо себе представляю создание тестов без проверки их на реализованном поведении, т.к. при наличии фейла и следовательно для выявления его причины легче разобраться в чёткой логике, описанной формальным языком с формальной грамматикой, чем в его формальном описании на нечётком человеческом с множеством потенциальных неточностей, неоднозначностей и недосказанностей и местами противоречий, что не были замечены на извиняюсь спринтах.

                                                                                                                                                                                Я попробовал TDD на своем маленьком проекте, пока все очень хорошо пошло. Но я так только несколько часов покодил пару недель назад, забросил пока :(
                                                                                                                                                                                Надеюсь, что вернусь на неделе к этому, посмотрим, как оно дальше пойдет.

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

                                                                                                                                                                                Мы, кстати, на работе сейчас пробуем BDD подход, к user story сразу пишем BDD-сценарии, которые потом используем в автотестах для стори. Вроде хорошо пошло. Это так же помогает и в процессе PBR (на грумингах и не только).
                                                                                                                                                                                Попробуем несколько спринтов, потом решим, будет ли с этим жить.
                                                                                                                                                                                Сообщение отредактировано: D_KEY -
                                                                                                                                                                                  Цитата Qraizer @
                                                                                                                                                                                  Я чего ты ждёшь-то собсвтенно?
                                                                                                                                                                                  Я жду когда
                                                                                                                                                                                  Цитата Qraizer @
                                                                                                                                                                                  нейронки выйдут из эмбрионального состояния, и всякоразные Плюсы с Питонами уйдут в нишу автокода.
                                                                                                                                                                                  Ты же сказал "погоди", вот я и спрашиваю долго ли еще погоджать?
                                                                                                                                                                                  0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                                                                                                                                                                                  0 пользователей:


                                                                                                                                                                                  Рейтинг@Mail.ru
                                                                                                                                                                                  [ Script execution time: 0,2586 ]   [ 15 queries used ]   [ Generated: 27.01.23, 02:39 GMT ]