На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
! Правила раздела:
1. Название темы - краткое описание кто/что против кого/чего
2. В первом сообщении - список параметров, по которым идет сравнение.
3. Старайтесь аргументировать свои высказывания. Фразы типа "Венда/Слюникс - ацтой" считаются флудом.
4. Давайте жить дружно и не доводить обсуждение до маразма и личных оскорблений.
Модераторы: Модераторы, Комодераторы
Страницы: (6) 1 [2] 3 4 ... Последняя » все  ( Перейти к последнему сообщению )  
> TDD vs не TDD
    Цитата 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 @
                                Если мы пишем так как в статье, значит нам уже не надо писать дополнительно ЮТ. Мы с них начинаем писать. Пишем тест, потом функционал. А уж потом дополнительно пишем интеграционные тесты, где с помощью моков эмулируем различные сценарии.

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

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

                                Вот если вместо "говорит о том, что модули сильно взаимосвязаны" написать "может говорить о том, что модули сильно взаимосвязаны", то я соглашусь :)
                                0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                                0 пользователей:


                                Рейтинг@Mail.ru
                                [ Script execution time: 0,0599 ]   [ 16 queries used ]   [ Generated: 25.04.24, 06:10 GMT ]