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

(сказал бы немецкий философ Фридрих Новалис, если бы жил сейчас)

Привет, Хабр! Меня зовут Владимир, я работаю программистом в компании Quadcode.  Вот уже почти полтора десятилетия я при помощи доброго десятка языков программирования разрабатываю приложения - от простых, вроде маленького плагина для Emacs, до сложных распределенных систем. Последние 4 года своей жизни я посвятил компании Quadcode, где занимаюсь разработкой транспортной подсистемы. Лет пять назад я вплотную столкнулся с адептами TDD (test-driven development) и это произвело на меня настолько сильное впечатление и оставило так много эмоций, что я написал “для своих” критический разбор наиболее часто встречаемых мною тезисов об этой технике (я бы даже сказал - учении). До сих пор мое мнение о TDD не изменилось, так что хотел бы описать его под катом и предлагаю обсудить вместе спорные моменты в комментариях.

Осторожно - TDD!

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

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

Цикл разработки по TDD
Цикл разработки по TDD

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

TDD — наглядная агитация

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

Обычно это небольшой пример на пару-тройку десятков строк кода, типично - калькулятора или чего-то похожего, обязательно с предельно простой логикой, без лишних зависимостей и как бы случайно (на самом деле нет) - легко тестируемого. На прямые вопросы “неверующих” о тестировании чего-то более менее реального - многопоточного допустим, в лучшем случае отделываются общими фразами и отсылают к священным писаниям по TDD, рассказывая про уникальность каждой твари Божьей конкретного случая.

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

Я не то, чтобы не понимаю основную идею, совсем напротив. Она мне даже импонирует: придумал сложный use case использования кода, написал на него тесты и выкинул из головы эту проблему. Но делать это с каждым куском тривиального кода, причем еще до написания этого кода - нет уж увольте. Да бывают тяжелые куски логики, с массой возможных исходов, там без тестов никак. И если там удалось предварительно все до деталей проработать, составить всякие UML диаграммы, блок схемы кода и еще фиг знает что, то можно в качестве развлечения даже тесты написать раньше кода. Но нужно понимать, что бенефиты дает детальная проработка задачи, но никак не написанный заранее тест.

Тесты как документация

Тест вообще говорит только о том, что именно в этом конкретном случае, с этими конкретными данными код ведет себя вот так. Все! Чтобы узнать как работает код в общем случае - нужно, как ни странно, посмотреть на код.

А тем кто все же сомневается и считает тесты хорошей альтернативой документации, рекомендую попробовать поизучать boost, по их тестам. Для не любителей “плюсов”, судя по отзывам, хорошей альтернативой будет spring framework.

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

100% покрытие

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

Страдают этим обычно пишущие на динамических языках - типичные представители - js\python. Что вполне ожидаемо, ведь пока каждый оператор в коде не дернуть из тестов, быть уверенным даже в правильности синтаксиса - нельзя. Справедливости ради, типизация и продвинутые линтеры постепенно проникают и сюда, делая жизнь проще.

В языках старой школы с этим все гораздо лучше. Там компилятор предоставляет определенные гарантии и не даст вызывать, например, только что удаленный метод. Тут к месту будет упомянуть набирающий популярность Rust, где в компилятор встроен весьма продвинутый анализатор кода, в том числе и актуального нынче многопоточного кода. "И увидел Он, что это хорошо" - действительно, заставлять человека заниматься тем, что может делать тупая железяка - глупо, а вдобавок еще и дорого. Этим должны заниматься компиляторы, статические анализаторы кода, санитайзеры и другие инструменты анализирующие код в динамике (типа valgrind и т.п.). Еще раз обращу внимание - речь сейчас идет не о тестировании логики, а о тестировании того, от чего защищаются 100% покрытием - опечаток, ошибок памяти, многопоточности и т.д.

Да, и что бы 2 раза не вставать замечу, что когда тесты покрывают код целиком и полностью, не оставляя живого места, любая попытка изменения функции или ее интерфейса приводит к дикому баттхёрту. И никакая самая продвинутая IDE не поможет менять вслед за кодом тесты легко и просто. А чтобы предупредить аргументы о том, что написанный по TDD код никогда переписывать не придется, перейдем к разбору следующего постулата.

С TDD сразу и навсегда

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

У адептов TDD не уживается в голове одна простая мысль: сделать идеально никогда не получится. И через месяц, два, год - вырастая профессионально, будешь смотреть на свой, написанный ранее код с улыбкой, понимая сколь много ты не учел. И никакая методология, будь то TDD или что-то ещё не позволит познать дзен и с первого раза написать правильно.

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

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

Но вернемся к “идеальному коду с первого раза”. Я не хочу сказать, что концепция “N недель проектируем, а потом сели и все по проекту написали”- совсем никогда не работает. Это работает, но для каких-то типовых решений, для десятого в вашей карьере интернет-банка с парой уникальных фишек это, наверное, идеальное решение. Но разработка чего-то нового, по крайней мере нового для вас, требует эволюционного проектирования и соответственно регулярной переделки кода. Особенно во время первых итераций, пока грабли еще не натерли мозоль на лбу, образно говоря. Про эволюционное проектирование в свое время основательно писал Мартин Фаулер.

Юнит тесты хорошо, а другие – плохо


Объясняют это обычно тем, что юнит тесты быстрые, как… (сами закончите в меру своей испорченности) и могут запускаться и работать даже на вершине Эвереста. И, блин, не поспоришь - действительно быстрые (про Эверест правда не проверял). Только вот ведь в чем беда - тестируют они только небольшие и несвязанные, строительные кубики кода - там где ошибок в большинстве случаев и нет. Ошибки, по опыту, начинаются после того, как из этих кубиков начинаешь строить что-то большее.

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

А справятся с такими ошибками, которые зависят от состояния системы - помогают функциональные (интеграционные) тесты. Они ужасно медленные, выполняющиеся по многу часов, плохо ложащиеся на TDD. А что делать? Жизнь - жестокая штука.

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

TDD нам думать и жить помогает

Еще один тезис адептов: “Только с TDD ваш код станет правильным и начнет цвести и пахнуть“. Без оного все конечно же будет печально и грустно”. Посмотрим, за счет чего это достигается.

Первая мантра – если писать код по тестам, то код будет легко тестировать. Прям КО: если код писать по блок схемам, то составить блок схему по такому коду будет легко. Вот только легко тестируемый код не дает никаких гарантий качества получившегося приложения.

Тогда в ход пускаются следующие софистские трюки, в философию TDD добавляют подобные постулаты:

  • перед тем как начать что-то писать нужно хорошо подумать, а потом разрабатывать через TDD

  • пишите код по TDD и ни в коем случае не используйте глобальных переменных

  • пишите код по TDD и старайтесь делать модули максимально независимыми 

  • ...

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

А вот без повторения слова TDD вдолбить это в головы программистов никак нельзя? Вот прям если попытаться написать тест после кода, то в классе обязательно в каждом методе будет обращение к БД (причем через драйвер реализованный прям в этом же классе). Все переменные будут глобальными (ну у продвинутых небожителей возможно они будут завернуты в синглтоны). А логи будут писаться http запросами на захардкоженный внутри кода адрес, что ли?

По моему очень скромному мнению, этому всему можно научиться вне контекста этих трех волшебных букв.

Итоги кратко

  • Тесты важные важны, тесты разные нужны.

  • Когда писать тесты - личное дело каждого и на качестве это особо не сказывается, главное не забивать совсем.

  • Тесты - спорный суррогат документации.

  • 100% покрытие или близкое к нему только мешает.

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

А что думаете насчет TDD вы? Давайте обсудим в комментариях. Возможно, эта технология все же очень важная и нужная?

Комментарии (243)


  1. amarao
    10.08.2021 13:30
    +13

    Из инфраструктурного программирования: smoke tests + red green tests.

    Smoke-тесты проверяют очевидное (оно запустилось и не обсыпалось). red/green tests - если что-то странно сломалось, перед тем, как чинить, напишите тест, который ловит проблему, а после этого чините.

    Практика показывает, что если один раз сломалось, сломается ещё раз. Не всегда, но вероятность для покраснения уже написанного зелёного теста по мотивам "сломалось" кратно выше, чем для теста, который писался по зелёной системе в рамках фантазии "а как оно может сломаться?".

    Ещё, написание red-тестов для сломавшегося обычно адский труд, потому что ломается обычно там, где плохо, а там где плохо, трудно писать хорошо.

    Алсо, мне не нравится, когда путают интеграционные и функциональные тесты. Функциональные - проверяют, что оно "реально работает". Интеграционные проверяют, что компонента А подходит к компоненте Б (или ко всей шарашкиной конторе на серверах).


    1. Andrey_Green
      11.08.2021 16:42

      "Ещё, написание red-тестов для сломавшегося обычно адский труд, потому что ломается обычно там, где плохо, а там где плохо, трудно писать хорошо. "

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


      1. amarao
        11.08.2021 17:07

        Я не совсем понял о чём речь.


        1. Andrey_Green
          12.08.2021 19:25

          Просто - банально, как везде.

          Знать бы где упасть - соломку подстелили бы.

          Я далек от убеждения и практика доказывает прямо обратное. Узкие места в 90% случаев появляются там, где никто не предполагает. Это классика. У вас по другому ? вы пишите код и выпускаете в продакшен имея знания о всех узких местах ???


          1. amarao
            15.08.2021 21:42

            Так я про это и говорю. Если раскладывать солому всюду, где кто-либо когда-либо падал (а ещё лучше перила сделать), то в целом количество "падений" становится меньше.

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

            В IaaC основная модель тестирования такая: smoke test'ы, тесты на идемпотентность, интеграционные тесты (которые те же smoke test'ы, но с настоящим дымом), и (если хватит силы воли) функциональные тесты.

            Соответственно, из них только функциональные тесты можно писать в red-green режиме, и то редко. Зато можно писать тесты по результатам найденных багов. Если у вас один раз nginx выехал без SSL'я в продакшен (потому что не было наследования группы из-за того, что положение инвентори поменялось), то есть крайне высокий шанс, что когда-то это случится ещё раз. И алгоритм такой: научиться воспроизводить (допустим, простой случай - взять и выкатить), научиться обнаруживать (простейший тест, что SSL есть, т.е. красный тест), исправить (сделать тест зелёным).

            Всё.

            Вот про этот red-green я и говорил.


          1. sshikov
            10.08.2021 18:16
            +1

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

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


  1. zolotyh
    10.08.2021 13:36
    +2

    Мнение Кента Бека (автор методологии) насчет стратегии написания авто тестов https://stackoverflow.com/questions/153234/how-deep-are-your-unit-tests/153565#153565.


    1. amarao
      11.08.2021 17:09

      Ну, у него одна среда разработки (среда = коллектив). Бывает так, что тесты важнее кода, потому что код будут писать и переписывать, а тесты - то, что держит его в разумных границах. А бывает, что группа профи, которая почти никогда ничего не переписывает, и им тесты нужны только для "corner cases" с обработкой ошибок.

      Т.е. первый вопрос для теста: сколько раз тестируемый код будет существенно меняться в будущем? Часто? Редко? Никогда?


    1. talbot
      13.08.2021 23:28
      +2

      Ten or twenty years from now we'll likely have a more universal theory of which tests to write, which tests not to write, and how to tell the difference.

      Написал Кент Бек тринадцать лет назад, а воз и ныне там, стало только запутаннее.


      1. sshikov
        10.08.2021 17:53
        -2

        >все ли хорошо с этим кодом
        Да с этим кодом все может быть весьма плохо, без вопросов. Только вы не сможете его зарефакторить, потому что это вполне вероятно будет не ваш код. А будет это какой-то сложный фреймворк, который написан другими людьми. Он не идеален, он плохо тестируется — но в чем я уверен, так это в том, что ни вы, ни я такой же, но хороший, который легко тестировать, не напишем примерно никогда.

        Тут есть конечно исключения, не у всех и не всегда так, но у меня такое бывало много раз. Живой пример — Apache Spark. При всех его недостатках, у него в гитхабе 1703 контрибьютора, а у меня в проекте никогда не было больше 50 — т.е. все мои проекты примерно на порядки меньше спарка. Ну или там возьмите какой-нибудь Node.js. Или браузер любой. Или Postgres, Oracle, ОС свою возьмите, наконец. Вы же не станете писать новый хром, потому что на этом неудобно тестировать, да? Или линукс рефакторить. А именно такие зависимости и вызывают наибольшую головную боль при тестировании.

        >Юнит тесты это еще как лакмусовая бумажка для качества кода.
        Ну в общем-то да, но с поправкой на то, что это утверждение не универсально.


      1. andreyverbin
        11.08.2021 00:24
        +4

        А я бы сказал, что легко тестировать код, который без IO. А тяжело тот, который это IO делает или многопоточный. К качеству кода все это отношение не имеет. Даже если код это атомная лапша Гейзенберга, но его можно запустить не показ миллион сервисов и не поднимая базу, то писать тесты легко, но тестов надо будет много. Но все равно их писать легко.


        1. kemm
          11.08.2021 01:19
          +5

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


          1. andreyverbin
            11.08.2021 02:52
            +3

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

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


            1. kemm
              11.08.2021 07:59
              +1

              либо научите писать код вообще без них

              Особенно какие-нибудь базы данных. 8))


            1. ApeCoder
              11.08.2021 08:52

              А почему именно TDD - это критика ж всего тестирования. А если программировать сложно - то это критика программирования.

              А в чем сложность? Просто устанавливаешь состояние до, проверяешь состояние после?


              1. kemm
                11.08.2021 10:03
                +1

                А почему именно TDD — это критика ж всего тестирования.

                Ньет. Это критика именно TDD. Тестировать можно всякими разными способами. Можно делать интеграционные/функциональные тесты, можно доказывать корректность, можно просто на какие-то части забить и понимать, что адекватно протестировать это невозможно (например, отсутствие гонок).


                Просто устанавливаешь состояние до, проверяешь состояние после?

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


                1. sshikov
                  11.08.2021 16:49

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


                  1. kemm
                    11.08.2021 17:30

                    Ага, я упомянул:


                    можно доказывать корректность

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


                    1. sshikov
                      11.08.2021 17:33

                      Да, как-то я упустил эти слова.

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


                      1. kemm
                        11.08.2021 19:23

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


                  1. andreyverbin
                    12.08.2021 00:28

                    Тогда у меня есть пара вопросов.

                    Как замокать API memory mapped file? Есть код, который в много потоков потоков что-то в него пишет, ротирует сегменты и т.п. Хочется понять, что этот код правильно с ним работает.

                    Как замокать БД, так чтобы в тесте все же проверить, что SQL генерируется правильный синтаксически и что выбираются верные данные? Нужно учесть, что в работе с БД активно используется всякие Postgres специфические upsert, sequence и прочая.


                    1. Vilaine
                      12.08.2021 02:16

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

                      Как замокать БД, так чтобы в тесте все же проверить, что SQL генерируется правильный синтаксически и что выбираются верные данные?
                      Это бесполезная трата времени. БД нужно тоже оборачивать в абстракцию (например, в репозиторий, есть ряд шаблонов вокруг БД), вот эту абстракцию и мокать для клиентов. А SQL не надо проверять при тестировании. Для тестирования абстракции вокруг БД используйте реальную БД, поднятую локально.


                      1. andreyverbin
                        12.08.2021 02:58

                        Другими словами - все что делает IO нужно спрятать за API и замокать. Само АПИ тестировать с живой ОС, БД и т.д.

                        Тогда имеем следствия

                        • из репозитория нельзя выставлять IQueryable, а это мега удобно.

                        • Каждый клиент теперь зависит от мока. Значит как только в репо изменится сигнатура какого-то метода получим некоторый кайф.

                          Там где у меня будет extension method для DbContext у вас будет репо, интерфейс, DI и геморой с моками. Но самое главное - я буду использовать IQueryable и это сэкономит мне тысячи строк кода. А вы нет, потому что IQueryable замокать так себе удовольствие.


                      1. Vilaine
                        12.08.2021 05:14
                        +1

                        Другими словами — все что делает IO нужно спрятать за API и замокать
                        Конечно, и это к TDD не имеет никакого отношения. Это банально вопрос архитектуры кода, вопрос старый, и довольно консенсусный. Варианты архитектур бывают разные, но все примерно об одном же, о разделении ответственностей и расслоении приложения. Как пример, гексагональная архиктура, clean architecture. Если я правильно понимаю, вы из стэка C# — на MSDN тоже подобный подход продвигается, т.к. часть статей про архитектуру из своего обучения я припоминаю оттуда.
                        из репозитория нельзя выставлять IQueryable, а это мега удобно.
                        Не знаю специфики вашей платформы. Если я правильно понимаю, это что-то вроде Query Builder. Да, это должно быть отделено от бизнес-логики.
                        Я понимаю удобство использование всего и везде (это касается вовсе не только DB Query: всегда проще и быстрее прям рядом с обработкой данных написать вызов куда надо и не париться, как разделение кода организовать), но 80% жизненного цикла кода — это не его написание, а поддержка.
                        Каждый клиент теперь зависит от мока. Значит как только в репо изменится сигнатура какого-то метода получим некоторый кайф.
                        У вас же статический ЯП? То есть даже тесты смотреть не нужно. А так падают тесты, вы их правите и всё хорошо. Если мы говорим про моки, то это юнит-тесты (изолированные), то есть они запускаются и падают быстро и их можно запускать на локальной машине все сразу. После этого можно их исправлять.
                        у вас будет репо, интерфейс, DI и геморой с моками
                        Да, и к TDD это не имеет никакого отношения. Вопрос поддерживаемости кода, maintainability. Аргументы примерно такие же, как почему не стоит говнокодить.
                        Геморроя нет, т.к. я знаю, почему что делается. Мне так и так придётся воссоздавать среду для тестирования поведения юнита или подиерархии, и делать это фикстурами и более диковинными эмуляциями (среда — это не только РСУБД) намного сложнее.
                        я буду использовать IQueryable и это сэкономит мне тысячи строк кода

                        1) Я использую ЯП, в котором литералы кода бесплатны. Никто не заплатит за то, что кода получается меньше.
                        2) Написание кода, в частности нового — это не такая уж большая часть работы разработчика.
                        3) Куча техник современных техник борьбы со сложностью — DDD, CQRS, Event Sourcing требуют в разы больше дополнительных строчек. Да, это борьба со сложностью, и да, сложность и количество кода могут обратно коррелировать.
                        4) Ну там и не преувеличивайте количество работы с разделёнными слоями, это не так уж много кода даже в момент его написания.


                      1. andreyverbin
                        12.08.2021 23:25

                        Можно далеко идти в архитектуру, но зачем? У меня простые утверждения

                        >>из репозитория нельзя выставлять IQueryable, а это мега удобно. 

                        >Не знаю специфики вашей платформы. Если я правильно понимаю, это что-то вроде Query Builder. Да, это должно быть отделено от бизнес-логики.

                        Да это Query Object и это все еще удобно. На ваш аргумент "это должно ..." я ответу еще более мощным "это не должно ..." Чем внезапно repo.FindById оказался лучше db.FindById ?

                        >>Каждый клиент теперь зависит от мока. Значит как только в репо изменится сигнатура какого-то метода получим некоторый кайф.

                        >У вас же статический ЯП? То есть даже тесты смотреть не нужно.

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

                        И цепочка рассуждений такая: TDD требует тестировать практически каждый класс. Без моков этого сделать нельзя. Следовательно везде будут интерфейсы и DI. а также обертки над любом API которое делает IO. А это почти все, не побоюсь утверждать, что это 90% всего что есть в природе. Как следствие на моки будут завязаны почти все тесты. Изменение любого внутреннего интерфейса приводит к каскадному эффекту - ломаются моки в тестах клиентов этого интерфейса.

                        Какое из утверждений выше не истинно?


                      1. nin-jin
                        12.08.2021 23:29
                        +1

                        тестировать практически каждый класс. Без моков этого сделать нельзя

                        Вот тут не правда. Тестировать-то можно и без тотального мокирования.


                      1. andreyverbin
                        13.08.2021 03:27

                        Если класс А зависит от репо, то мне нужен мок. И так по цепочке. Классу Б нужен класс А, либо мокать А, либо мокать репо. Как с этим бороться?


                      1. nin-jin
                        13.08.2021 07:07

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


                      1. andreyverbin
                        13.08.2021 23:41

                        >>>>TDD требует тестировать практически каждый класс. Без моков этого сделать нельзя. ...  

                        >>>Вот тут не правда. Тестировать-то можно и без тотального мокирования.

                        >>Если класс А зависит от репо, то мне нужен мок. И так по цепочке. Классу Б нужен класс А, либо мокать А, либо мокать репо. Как с этим бороться?

                        >Запускать тесты в изолированном контексте, где репо заменён на мок, и всё.

                        "вы либо трусы наденьте либо крестик снимите" :)

                        Все, что зависит от репо явно и не явно будет зависеть от моков. Причем это справедливо не только для репо, а вообще для любого кода, который делает IO - БД, API, файлы и т.п. А это почти весь код приложения. Так что я не понимаю как утверждения

                        • Тестировать-то можно и без тотального мокирования.

                        • Нужно мокать все, что делает IO

                        • Примерно весь код типичного веб приложения так или иначе завязан на IO (через репо или иные обертки)

                        Могут быть все одновременно истинными.


                      1. nin-jin
                        14.08.2021 01:32

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


                      1. andreyverbin
                        14.08.2021 02:37

                        Мне кажется вам стоит представить более весомый пример, чтобы показать как можно "Тестировать-то можно и без тотального мокирования." Игрушечный пример, в котором 1 класс тестируется и нет IO ничего не говорит о реальной жизни. В реальной жизни у нас куча классов делает IO, и большинство остальных на них завязаны.


                      1. nin-jin
                        14.08.2021 03:23

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


                      1. andreyverbin
                        14.08.2021 03:46

                        В качестве примера силы TDD вы привели приложение, которое было создано в 2016 году, а первый тест у него появился в 2018. Вот это test first!

                        О!!!! так вы же и автор, и после этого вы мне за TDD втираете?

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


                      1. nin-jin
                        14.08.2021 03:55

                        Что я думаю про TDD вы можете понять из этого видео:

                        А в этой статье подробно разбирается упомянутая вами номенклатура.


                      1. Vilaine
                        14.08.2021 00:13

                        Какое из утверждений выше не истинно?
                        До истины далеко, пока работаем над эвристиками.
                        repo.FindById оказался лучше db.FindById
                        Локализованностью конкретного функционала. Если вам нужно изменить выборку по ID для какой-то сущности (добавить ещё один фильтр или выбирать из key-value), то это сводится к одному месту. Ещё более важно, если вы хотите узнать, как получается сущность из БД. И вообще, видя репозиторий можно узнать, как это приложение взаимодействует с БД для этой сущности. В одном месте все выборки для сущностей, легче найти, легче вспомнить. Это обеспечение high cohesion.
                        Пользы от этого действия 0, это дань архитектуре.
                        1) Пользы in se ipso от починки моков нет, но есть польза в наличии тестов, которые используют моки.
                        2) Починка моков довольно дешева. Если у вас репозиторий для каждой сущности, то он используется в ограниченном количестве мест (например, в отдельно взятой слоистой архитектуре — только на стыках слоёв Application и Domain, где и создаётся доменное окружение).
                        3) Изменение сигнатур намного чаще касается тестирования обычных юнитов, как сущностей, которые по своей природе (в самом распространном примере архитектуры) не имеют внешних зависимостей. То есть ломает код чаще всего изменение изолированных объектов.
                        4) Кроме того, даже если передавать не мок, а сам объект, подключенный ко внешнему миру, то изменение его сигнатуры точно так же ломают тест. Мок только реализует ту же самую сигнатуру.

                        Т.о. 3 и 4 делают это аргументом против тестов, дескать, изменения в коде ломают тесты даже без ошибок, а значит тесты кроме, возможно, приёмочных (acceptance) не нужны. Но цель тестов, даже приёмочных — именно в том, чтобы ломаться при изменениях (если они что-то проверяют), аргумент против тестирования вполне отвечен и я не вижу смысла на него тратить ещё больше время в этом топике.
                        TDD требует тестировать практически каждый класс. Без моков этого сделать нельзя
                        В целом, можно обойтись вообще без моков и давать подключенные ко внешнему (тестовому) окружению объекты. Никак не противоречит TDD. Но это:
                        1) Дороже покрытие многих классов функциональности. Pure code намного быстрее. Лучше на дорогие тесты отвести менее полные проверки, то есть проверять ими уже проверенные кирпичики.
                        2) Менее удобно создавать фикстуры для БД, фикстуры файлов, состояния очередей и т.п. Когда я разрабатываю что-то без всякого TDD, мне часто намного проще начать с теста, чтобы сделать рабочий компонент кода, чем воссоздавать для проверки соответствующего ему функционала всю среду.

                        Вопрос в эффективности и в борьбе со сложностью. Поверьте, я вполне могу забить на всё и делать так, как 10 лет назад, когда мне казалось это бесполезным. Это даже намного легче (собственно, для одноразовых скриптов я и не парюсь, делаю как быстрее). Но ещё мне проще раскидать одежду по приходу домой.


                      1. andreyverbin
                        14.08.2021 03:29

                        >Какое из утверждений выше не истинно?

                        До истины далеко, пока работаем над эвристиками.

                        Удобно, я вам даю набор предикатов и чтобы все расставить по местам достаточно показать, что один из них неверен, не стоит ради этого такие сочинения писать.

                        Локализованностью конкретного функционала.

                        Вы сравниваете public User FindById() с public static User FindById(this DbContext db). Какая тут разница в локализации функционала?

                        4) Кроме того, даже если передавать не мок, а сам объект, подключенный ко внешнему миру, то изменение его сигнатуры точно так же ломают тест. Мок только реализует ту же самую сигнатуру.

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

                        а значит тесты кроме, возможно, приёмочных (acceptance) не нужны.

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

                        Но цель тестов, даже приёмочных — именно в том, чтобы ломаться при изменениях (если они что-то проверяют)

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

                        Вопрос в эффективности и в борьбе со сложностью.

                        Вот смотрите, я беру код вашего repo.FindUserById и перемещаю его в статическую функцию. Затем я выкидываю сам репо за ненадобностью и все тесты с ним связанные. Так как и у вас и у меня есть тесты самого API, покрытие после этого осталось таким же. Я выкинул кучу кода и... усложнил систему и навел беспорядок? Какое такое определение сложности и порядка нужно принять, чтобы это оказалось истинным?


                      1. andreyverbin
                        12.08.2021 19:03

                        Аргумент вида "в пост мясо есть нельзя, в писании так сказано". Как только появится определение abstraction в виде понятного формализма, мы сможем серьезно об этом говорить. А пока я могу взять два куска кода, предположить возможные изменения требований и сравнить как они будут меняться. Несложно увидеть, что конкретно в этом случае репо сосет и будет меняться на каждый чих (вместо с тестами на него)

                        GET /users?email=me@me.com
                        dbContext.Users.Where(x => x.Email == email).ToJson();
                        userRepo.FindByEmail(email).ToJson();

                        изменения

                        1. Новые фильтры

                        2. Сортировка по разным полям

                        3. Ограничение на то, каких юзеров можно и нельзя видеть

                        4. Включить/выключить список ролей юзера в ответе

                        GET /users?email=me@me.com&name=Peter&role=1
                        var users = dbContext.Users.ScopedBy(user.Role)
                        .Where(x => x.Email == email && x.Name == name);
                        if (role) users = users.Include(x => x.Roles);
                        return users.ToJson();

                        Я тут сортировку не добавил, думаю вы сами догадаетесь как ее можно сделать.

                        Попытка все это реализовать с помощью репо приведет к созданию кривой версии IQueryable, которую еще придется поддерживать. Это утверждение, можете меня поправить, если не так.


                      1. nin-jin
                        12.08.2021 19:51

                        Вы для каждой сущности всё это копипастите? Страшно представить сколько вы напишете кода, когда сущностей будет штук 20 с 40 релейшенами между ними. Нет бы сделать абстрактное апи, которое единообразно работает с любыми сущностями..


                      1. andreyverbin
                        12.08.2021 20:54

                        Вы пример кода приведите на основе абстрактного апи и мы сравним. Если апи на конфигурации/аннотациях ездит, то из посчитаем, а так посмотрим сколько кода надо написать. Только чтобы было честно давайте не будем брать узкоспециализированные решения типа ActiveAdmin.


                      1. nin-jin
                        12.08.2021 22:22

                        На основе абстрактного апи никакого кода и не будет. Будет простой фасад преобразующий любой http-запрос в запрос к этому самому апи.


                      1. andreyverbin
                        12.08.2021 22:37

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

                        На 40 сущностей "копипасты" будет около 1000-2000 строк. Ваш фасад + конфиг будет сильно больше и его будет сильно сложнее менять, не находите?

                        Другой большой вопрос в том, что будет с вашим фасадом, когда появятся требования что-то кешировать, где-то рассылать нотификации и всякое такое. В случае с "копипастой" я допишу по строке там где нужно. И даже могу что-то зарефакторить и спрятать в сервис. А в случае фасада вы будете допиливать свой DSL. Ради экономии примерно 1 строки на каждую сущность :)


                      1. nin-jin
                        12.08.2021 23:22

                        А что там сложного? Распарсил запрос, вызвал нужную ручку апи. Абстрактных ручек единицы-десятки.

                        Да, это всё в конфигурацию выносится. Это всего несколько сотен строк, которые и тестировать не надо.

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

                        Допиливание нормального DSL делается в пару строк кода - это не рокетсаенс. Впрочем, у нас было и кеширование, и нотификации, и даже выдача с правами по всем полям, чтобы клиент знал что можно давать пользователю редактировать, а что - нет. Всё это через 2 фасада: REST и WS. Во втором случае ещё и обновление данных в реальном времени приходили.


                      1. andreyverbin
                        13.08.2021 02:31

                        А что там сложного? Распарсил запрос, вызвал нужную ручку апи. Абстрактных ручек единицы-десятки.

                        Одно из таких абстрактных API это OData, а вот это

                        cloc AspNetCoreOData-master 

                        Language               code

                        C# 107805

                        намекает, что тезис про простоту тула не очень согласуется с реальностью.

                        Да, это всё в конфигурацию выносится. Это всего несколько сотен строк, которые и тестировать не надо.

                        Узкоспециализированный тул (5K - 10K LoC) + несколько сотен строк конфига против 1K - 2K LoC единообразного кода, который осилит любой джун. Выигрыш не всегда очевиден.

                        Я уже даже не помню, зачем я в это обсуждения ввязался, наверное категоричность заявления про абстрактное апи меня задело. И "копипаста" и абстрактное апи имеют место быть. Для меня, когда-то было открытием, что пачка тупых контроллеров в поддержке часто оказывается дешевле, чем если тоже самое делать с наворотами ради DRY.


                      1. nin-jin
                        13.08.2021 02:53

                        Одата сильно переусложнена. Не берите с неё пример.

                        Где вы там 5к строк в туле нашли? Там 1к в прыжке.

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


                      1. ApeCoder
                        13.08.2021 15:34

                        Репо ограничивает набор запросов которые требуется качественно реализовать.

                        Если вы выставляете IQueriable то вы требуете от реализации успешно выполнять не только запросы, которые используются приложением но и все остальные мыслимые запросы. Соответственно подмена хранилища будет сложнее.

                        Оба решения обладают как достоинствами так и недостатками.


                      1. nin-jin
                        11.08.2021 18:58

                        Судя по вопросу статью вы не читали. Вы и с коллегами так общаетесь?


                      1. nin-jin
                        11.08.2021 19:55

                        И тоже считаете не своей вотчиной ходить по ссылкам и узнавать что-то новое самостоятельно?


                      1. nin-jin
                        11.08.2021 22:00

                        Даже если в них рассказывается про паттерн, который может быть применён в любом языке? Ну ок.


                      1. nin-jin
                        11.08.2021 22:31

                        Вы не просекли фишку, попробуйте ещё раз. С глобальным контекстом обычно никто не работает.


              1. andreyverbin
                11.08.2021 21:23

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


          1. kemm
            11.08.2021 10:04
            +2

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


          1. siarheiblr
            11.08.2021 10:41
            +1

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

            Когда у вас when(что-то).thenReturn(что-то) то вы описываете состояние. А потом ещё и всякими хитрыми путями выдираете промежуточные и финальные состояния чтобы проверить правильность кода. И тест потихоньку становится едва ли не сложнее кода, который он тестирует.

            Где-то в этот момент фанатики TDD тихо отходят в сторонку или начинают вещать о неправильном коде. Не, ну калькулятор можно и строго по тдд написать…


            1. Vilaine
              12.08.2021 02:22

              Разве TDD запрещает обеспечивать часть 100%-ого покрытия интеграционными тестами? Обсуждение на кучу комментариев соломенного чучела.


          1. andreyverbin
            11.08.2021 14:48
            +2

            Слышала и пробовала, тут все же профессионалы собрались. Я пишу биржу, 80% моего кода активно работает с БД, а 20% многопоточка. Подскажите, как мне замокать базу и потоки? Тестировать что класс А вызвал класс Б не интересно. Интересно увидеть, что в результате получился корректный SQL, а база в ответ на него вернул то, что ожидается.


        1. nin-jin
          11.08.2021 09:29

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

          Чем более низкоуровневая изоляция, тем больше требуется ресурсов для исполнения теста. Вариантов борьбы с этим 3 в зависимости от предметной области:
          - Замена реальной зависимости некоторой легковесной заменой (моком, адаптером или даже фасадом). Отдельное тестирование мока на соответствие реальной зависимости. И отдельное тестирование остального кода с этим моком.
          - Разработка легковесных способов изоляции (контейнеры вместо виртуализации, как пример).
          - Построение архитектуры таким образом, чтобы более глубокая изоляция не требовалась. Например, если некоторый код не лезет в глобальное состояние, а работает лишь с локальным, то достаточно только автоочистки всей выделенной в рамках исполнения памяти, и не нужно перезагружать ОС после каждого теста.


          1. andreyverbin
            11.08.2021 16:22

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


          1. sshikov
            11.08.2021 16:55

            Ну в общем да. Скажем, как только вы пытаетесь заменить что-то типа СУБД моком, все и ломается. Или даже есть такой API, JDBC, взаимодействие Java-база. Так вот, как правило попытка замокать его — это очень больно, очень. И совершенно неэффективно. Поэтому как правило этот низкоуровневый протокол оборачивают во что-то типа DAO, и уже тестируют их.


            1. Sartor
              10.08.2021 22:14

              Если код будет очень простой, то он и делать будет очень мало. А чтобы он начал что-то делать сложное - понадобится очень много "простого кода". Соотношение сигнал/шум будет всё хуже и хуже. И в итоге будет ситуация как в статье, клетка идеальна, а организм с кучей багов.


              1. rudinandrey
                11.08.2021 13:35

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


                1. Sartor
                  11.08.2021 14:23

                  Так я о том же. 1000->3000 это ещё совсем неплохо, иногда такое разбитие может в 10 раз увеличить кол-во кода.


        1. kemm
          10.08.2021 17:46
          +4

          Погодите, было написано, что юнит-тесты сложнее чем код, который они тестируют. Предложенный выход: отрефакторить код. Как из этого следует, что код станет проще?


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


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


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


  1. sshikov
    10.08.2021 18:29
    +2

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


  1. anonymous
    00.00.0000 00:00


  1. sshikov
    10.08.2021 21:45
    +3

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


    1. Vilaine
      12.08.2021 02:10

      ОС, база данных, язык и рантайм, крупные фреймворки
      Что-то из этого можно обернуть в anti-corruption layer при разработке, чтобы поддерживать высокий уровень и консистентность вашего кода. Что-то из этого всегда оборачивается по уже давним индустриальным стандартам, как БД.


      1. sshikov
        12.08.2021 07:21

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


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


  1. sshikov
    11.08.2021 16:48

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

    Кстати, реальный пример — жил был когда-то давно J2EE, старых версий. И тестировать в нем было ужасно неудобно. Сначала умные люди придумали спринг, чтобы выделить тестируемый код в такой форме, чтоб тесты стало делать не больно. А потом и авторы уже в JavaEE доперли, что можно сделать и проще — и тоже все упростили. Стало можно тестировать отдельно от контейнера, и сам контейнер стал подниматься приемлемое время.

    Но сделать такое же самому — ну где-то на уровне написания своего DI фреймворка. Возможно — но как правило непрактично.


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


    1. Olbar1
      10.08.2021 14:11
      +7

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

      Кроме TDD нет подходов по-вашему?


    1. kemm
      10.08.2021 14:42
      +5

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

      Чтобы что? Чтобы он не был проще, чем юнит-тесты? А это точно правильный подход, усложнять код ради тестов?


      1. Vilaine
        12.08.2021 02:27
        +2

        Тестируемость кода обычно сильно коррелирует с модульностью и изоляцией, с иерархичностью компонентов. Плохо тестируемые участки либо таковы из-за слишком высокой связанности, либо из-за сторонних эффектов (сторонние эффекты во «внешний мир» стоит выделять из основной логики, тестировать только связующий слой в тонких интеграционных тестах). Адаптация к тестируемости — это обычно не игра с нулевой суммой, код с высокой связностью (coupling) в принципе сложнее сопровождать, плюс такой код менее стабилен для изменений. В принципе, 80% жизненного цикла кода — это и так вовсе не его написание, а сопровождение.
        Состояние тестов и форма самой пирамиды тестирования, впрочем, зависит от характеристик приложения. UI и базовый CRUD будут иметь другие формы тестирования.


        1. kemm
          12.08.2021 09:52
          +2

          А если присмотреться к хорошо юнит-тестируемому коду, то зачастую возникает ощущение, что это не код, а обвязка для юнит-тестов, которая совершенно факультативно ещё и что-то полезное умеет делать. 8)) Вплоть до того, что ВЕСЬ код состоит исключительно из шаблонов для покрытия всего и вся юнит-тестами, не сильно страдая по скорости (правда, сколько оно собирается — думать страшно).


          1. Vilaine
            12.08.2021 22:07

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

            Если у вас от конкретных решений возникают какие-то ощущения, то приводите примеры этих решений и мы о них поговорим.


            1. kemm
              13.08.2021 00:21

              Я говорю, конечно же, о Haskell.

              А я, конечно же, не о нём (никогда ничего промышленного на хаскелле не писал). Зато можно покрыть всё контрактами и доказать через gnatprove. Я, конечно же, об Ada. 8)) Вот только это частные случаи.


              Если у вас от конкретных решений возникают какие-то ощущения, то приводите примеры этих решений и мы о них поговорим.

              Следующее же предложение. Халвы всему, живьём я это не видел, на интервью рекламировали подход...


              1. Vilaine
                13.08.2021 23:44

                Зато можно покрыть всё контрактами и доказать через gnatprove
                Есть примеры и посвежее, как Idris или ещё новее Kind, но я не знаю, как автопруверы могут быть применены в обычной разработке в обычных языках. Никакие особо доказательства и не применяются. Но вот изолирование эффектов, как в Haskell применяется в разработке очень часто и к этому сводится изрядная часть борьбы со сложностью, и вовсе не ради тестируемости. В хаскеле это тоже не ради тестируемости. Собственно, некоторые адепты дизайна кода в статьях нередко обращаются к строгим ФП и хаскелю. Так что критиковать нужно не TDD, а распространённые подходы к борьбе со сложностью. Реализованный по типичному пути DDD отлично тестируется, но имеет слабое отношение к TDD.
                Так что аналогия неравнозначная.


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


    1. Andrey_Green
      11.08.2021 17:49

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

      Извините если что


  1. dmytro_p
    10.08.2021 13:51
    +9

    У меня аналогичные ощущения от всей этой затеи с TDD. 100% покрытие кода,не гарантирует 100% правильности работы, а в сотнях и тысячах тестов будет масса ошибок, которые нужно исправлять отдельно.

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


    1. a1111exe
      10.08.2021 21:10
      +5

      в сотнях и тысячах тестов будет масса ошибок

      Хммм... Кажется, у меня в уме прямо сейчас зарождается парадигма TDTD - Test Driven Test Development. Иначе как кошерно проконтролировать корректность тестов, если не тестированием тестов? :)


      1. vladimir_qc Автор
        10.08.2021 23:07
        +2

        Я очень надеюсь, что постепенно разовьются подходы автоматической валидации\верификации кода, вроде линтеров, статических анализаторов, TLA+/TLC и т.п. И после этого фундаментально сократиться кол-во тестов, которые нужно будет писать. А с развитием ЯП сложность написания любого кода, в том числе тестов уменьшиться.


      1. mgramin
        11.08.2021 06:35

        Мутационные тесты ещё есть


      1. inemiten
        11.08.2021 12:38

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


    1. Vilaine
      12.08.2021 02:33

      100% покрытие кода, не гарантирует 100% правильности работы
      Любопытно, что тезис про гарантии полной корректности выдвигают только противники TDD, для его последующего опровержения. =)


      1. vladimir_qc Автор
        12.08.2021 03:23

        TDD разве не подразумевает, что мы пишем новую функциональность только тогда, когда имеем красный тест на неё? Это автоматически означает 100% покрытие.


        1. Vilaine
          12.08.2021 05:19

          Но я то говорю про то, что люди зачем-то оспаривают тезис о полной корректности («100% правильности работы»).


  1. ihost
    10.08.2021 13:54
    +14

    На одном проекте лет 5 назад пришлось столкнуться с ситуацией, похожей на описанной автором статье: в проекте было близкое к 100% покрытие unit-тестами и ноль интеграционных и функциональных, причем TDD-адепты были категорически против их написания

    Доходило до абсурдных ситуаций, когда нерабочий код вливали в релиз-бранч, причем настолько нерабочий, что приложение даже не запускалось (sic!) - выяснялось, что TDD-адепты никогда не запускали приложение перед влитием ветки в релиз-бранч(sic!!), а обходились запуском юнит-тестов

    Когда впоследствии TDD-адепты не могли обеспечить 100% ковераж чистыми юнит-тестами, в ход шли моки, фейки и стабы в настолько невероятном количестве, что юнит-тест фактически тестировал только мок-машину, а само приложение только прирастало багами, не говоря уже об уродовании кода ради удобства моков

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


  1. Vlad_Murashchenko
    10.08.2021 15:02
    +3

    TDD на одних юнит тестах - это, очевидно, не правильное TDD. И я считаю, что не TDD в этом виновато. Почти любую концепцию можно понять не правильно и применить там, где неуместно.

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


  1. snuk182
    10.08.2021 16:12
    +4

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


    1. euroUK
      10.08.2021 16:44
      +4

      Если код пишется вперед тестов - это не TDD


      1. snuk182
        10.08.2021 19:39

        Я это и написал. Сначала тесты, потом код. Только это при мне никто никогда не соблюдал.


        1. ApeCoder
          10.08.2021 22:40
          +1

          Сначала тест. В единственном числе. Потом код. Потом следующий.


          1. snuk182
            10.08.2021 23:38
            +2

            Утопия.


          1. kemm
            11.08.2021 01:20
            +3

            Что сразу прибивает нас гвоздями к разработке снизу вверх. Вот лично я не уверен, что это такой уж однозначно хороший вариант, мягко говоря...


            1. ApeCoder
              11.08.2021 08:49

              Говорят, top-down возможен. Я предпочитаю mixed approach.


              1. kemm
                11.08.2021 10:05
                +1

                Извините, но этот top-down выглядит как костыль. Груда моков, которые потом выкидывать...


  1. CsharpNovice
    10.08.2021 18:48

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


    1. vladimir_qc Автор
      10.08.2021 18:50

      TDD это про процесс, результат в идеале плюс минус одинаков при должной квалификации разработчика.


      1. goodnickoff
        10.08.2021 23:18

        Предположим, используя TDD и не покрывая код тестами вовсе, я напишу код приблизительно одного качества. Но как на счет остального жизненного цикла? Уверенности, что чужие изменения ничего не ломают. Какой-никакой документации поведения. Как быть уверенным (хоть в какой-то степени), что обновление версии языка программирования ничего не сломает. Дело не только в качестве написанного кода, но в большей степени в надежности, простоте поддержки и сопровождения.


        1. ReanGD
          11.08.2021 05:05

          del


        1. vladimir_qc Автор
          11.08.2021 05:08
          +1

          Наверное имелось ввиду "НЕ используя TDD"?

          Я комментарием выше имел ввиду, что в идеале в итоге несмотря на методологию мы получим выполненную задачу, которая обложена тестами. Возможно с разным процентом покрытия, но важные места ИМХО точно должны быть покрыты тестами. И типично разработка задачи ведётся одним разработчиком в отдельной ветке, где нет чужих изменений, где никто не обновляет версии языка.


          1. goodnickoff
            11.08.2021 10:12

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

            Просто вы не разрабатывали мультиплатформенный продукт, который может быть установлен на различных, поддерживаемых разработчиком, окружениях. Я встречал кейсы, когда результат математической функции был различным в одной и той же версии языка установленной на разных ОС. И эта проблема была обнаружена благодаря написанным тестам и не ушла в прод.

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

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

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

            Да, сами авторы идеи говорят, что нет смысла в 100% кода и оно только вредит.


            1. vladimir_qc Автор
              11.08.2021 13:00
              +1

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

              Я люблю тесты, пишу их сам и заставляю других по возможности, у меня проблема только с TDD

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

              Возможно не понял.

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

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

              Ещё раз, я ЗА юнит тесты, они почти всегда уместны и помогают

              Да, сами авторы идеи говорят, что нет смысла в 100% кода и оно только вредит.

              Насколько я помню авторы идеи говорят, что код пишется только после того, как написан тест, это разве не подразумевает, что весь написанный код покрыт тестами и что автоматически дает 100% покрытия?


              1. parsler
                11.08.2021 18:17

                Я люблю тесты, пишу их сам и заставляю других по возможности, у меня проблема только с TDD

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

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

                Так вот самым сильным аргументом для того, чтобы попробовать TDD еще раз, на этот раз с наставником, стало то, что TDD is more fun. Писать тесты вместе с кодом - занятие гораздо более веселое и интересное. И для меня это реально оказалось так.

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


                1. goodnickoff
                  11.08.2021 10:16
                  +2

                  Т.е. вы делали рефакторинг, запустили тест, поняли, что ваш рефакторинг сломал компонент, потому что тест не прошел, и решили изменить тест. Простите, при чем тут TDD?


                1. kspshnik
                  11.08.2021 10:16
                  +1

                  Если Вы не меняете поведение, то Вы и не меняете тест. И он сразу покажет, что Вы накосячили :)


                  1. ApeCoder
                    11.08.2021 10:41

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


                1. ApeCoder
                  11.08.2021 10:39

                  Рефакторинг это изменение терминологии в котором написано поведение.

                  Есть рефакторинги с раной степенью риска (например c использованием автоматических рефакторингов), можно делать рефакторинги в два этапа (код, потом тесты). В целом проблема есть, да. Можно писать только E2E тесты и для каких-то случаев это работает и тесты не будут изменяться при рефакторинге. Но будут изменять при изменении UI.

                  Вопрос, что лучше. С учетом всех плюсов и минусов


                  1. nin-jin
                    11.08.2021 10:53

                    Правильно - фиксировать в тестах желаемое поведение, и не фиксировать всё остальное. Если конкретный список зависимостей не является требованием, а является временной технической особенностью, то и фиксироваться в тестах он не должен. С UI тестами то же самое - если вы тестируете добавление товара в корзину, то от внешнего вида кнопки добавления тест зависеть не должен. Что не исключает отдельного теста именно внешнего вида, если он для вас важен.


                  1. nin-jin
                    11.08.2021 18:33

                    Ну вот, сейчас ещё и руки оторвут..

                    const context = $.$ambient({
                        
                        $log: function( ... what ) {
                            
                            this.$elastic.send({
                                type: 'log',
                                when: Date.now(),
                                what,
                            })
                            
                        },
                        
                    })
                    
                    context.$app_start()


        1. goodnickoff
          10.08.2021 23:07
          +1

          Во-первых, в вашем примере вы начинаете реализацию класса не понимая какие у него зависимости и на каком уровне/слое он находится. Как раз тут TDD вам сигнализирует о том, что вы, возможно не достаточно продумали решение и не понимаете до конца что хотите получить.

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


          1. nin-jin
            11.08.2021 09:54
            +2

            Андроидам на заметку:

            • Кожанным мешкам граздо проще думать над алгоритмом видя этот самый алгоритм перед глазами, даже если это всего-лишь драфт.

            • Кожанные мешки, даже если очень долго подумают, не могут учесть всего. Собственно, поэтому им и вообще нужны тесты.


            1. goodnickoff
              11.08.2021 00:25
              +2

              Вы изменили поведение компонента и изменили тест. Все предельно логично. Не вижу никаких противоречий TDD. Новое поведение системы теперь покрыто тестами и задокументировано.

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


            1. nin-jin
              11.08.2021 10:54
              +1

              Один хороший тест - как правило совсем не юнит.


        1. goodnickoff
          10.08.2021 23:27
          +4

          "Рефакторинг - процесс изменения внутренней структуры программы, не затрагивающий её внешнего поведения"


  1. Tellamonid
    10.08.2021 19:42
    +4

    У вас немного странное представление о TDD. Там не тесты пишутся до кода. И не может быть ситуации «почти все готово - половину тестов написал, осталось только вторая половина, ну и по мелочи - код, рефакторинг…», потому что пишется один самый простой тест кейс, а потом сразу код, который делает, чтобы код проходил, потом – чуть более сложный тест кейс, и код, чтобы уже два теста проходили, и так далее.

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


    1. vladimir_qc Автор
      10.08.2021 19:54

      Согласен, слегка увлекся представляя сферического TDD разработчика в вакууме и приписал ему несколько иной стиль разработки в этом месте статьи.


  1. anonymous
    00.00.0000 00:00


    1. goodnickoff
      10.08.2021 22:34
      +1

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


  1. anonymous
    00.00.0000 00:00


    1. ApeCoder
      10.08.2021 22:43
      +2

      что, TDD противоречит

      Ссылка, цитата?


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


    1. nin-jin
      11.08.2021 09:45

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

      Тут проблема в том, что вы используете модульное тестирование, а не компонентное. TDD тут ни при чём. Это лишь ритуал написания тестов.


  1. goodnickoff
    10.08.2021 22:31
    +8

    Я не ярый адепт TDD и не холивара ради. Исключительно из личных наблюдений за 11 лет опыта в разработке:

    • Код, покрытый юинт тестами обычно имеет лучшую архитектуру, чем тот, который тестов не имеет

    • Делать изменения в покрытом тестами компоненте безопаснее

    • Если для класса сложно написать тест, обычно он является проблемным местом и является частью плохой архитектуры

    • Громоздкие тесты, которые сложно читаются и понимаются сигнализируют о проблеме в юнитах, которые они тестируют

    Да, понятия хорошая/плохая архитектура очень широки, но для краткости позволю себе оставить их без пояснения.

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


    1. vladimir_qc Автор
      10.08.2021 23:11
      +4

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


      1. goodnickoff
        10.08.2021 23:30

        Соглашусь. Но тут, как и везде, нужен баланс. Иногда практичнее писать до, иногда после.

        Как обычно, проблема не в методологии, а в фанатиках.


        1. vladimir_qc Автор
          10.08.2021 23:35
          +4

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


        1. zloddey
          11.08.2021 07:32

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


        1. nin-jin
          11.08.2021 10:35
          +1

          Один хороший тест даёт профита больше (например, реализующий типичный юз-кейс), чем десяток бездумно накопипащенных, проверяющих один и тот же класс эквивалентности.


  1. anonymous
    00.00.0000 00:00


  1. Justerest
    10.08.2021 22:41
    +2

    TDD позволяет написать то, что без TDD написать практически невозможно)

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

    В процессе написания кода в голову постоянно приходят идеи альтернативных решений. TDD позволяет пробовать их на ходу - если код стал проще и тесты остались зелёными, значит все ок. Грубо говоря, вы начинаете решать задачу через добавление 10 "ифчиков", а потом заменяете это на нормальный алгоритм и убеждаетесь, что поведение осталось равнозначным. И такие повороты можно делать на каждом шаге. И что очень важно, ОЗУ головного мозга прослужит вам дольше, чем если бы вы пытались держать все кейсы в голове разом.

    На счёт 100% покрытия - это бред какой-то или фантастика.

    По поводу зависимостей, которые мешают писать через TDD... Если пишите что-то через TDD (или просто надо тестировать и прокидывать моки), вытащите этот класс в укромное место, чтобы неожиданные зависимости туда не попадали, сосредоточьтесь на определенной функциональности.


    1. vladimir_qc Автор
      10.08.2021 23:22
      +2

      У меня на практике обычно вот эта часть, что вы назвали "через добавление 10 ифчиков, а потом заменяете это на нормальный алгоритм" вызывала трудности, т.к. "нормальная" реализация всегда приводила к переименованию\удалению интерфейсов\классов\методов\аргументов, перестройке всей структуктуры кода. После чего все ранее написанные тесты грубо говоря проще выкинуть и написать заново. Как вы с этим боролись?

      Я для себя выработал следующее решение: я пишу решение задачи на "человеческом языке", упуская несущественные (но объемные в реализации на ЯП) детали. Т.к. такой с позволения сказать код сильно меньше и проще, его быстро и легко рефакторить и держать в голове целиком, до тех пор пока он не решит всех кейсов, которые я придумаю. Если задача сложная можно постепенно углублять решение, прорабатывать детали, когда высокоуровнево проблем не осталось. Потом уже дело техники перевести его на нормальный ЯП и написать тесты.


      1. Justerest
        11.08.2021 07:37
        +1

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

        У меня была задача (фронтенд) – сделать инпут БИК банка с асинхронной валидацией и автозаполнением данных названия банка/кор. счёта через ответ от сервера. Я дня 3 откладывал задачу, потому что не мог найти нужную точку, с которой было бы удобно писать тест. Изначально думал, что начать надо с валидатора – практически чистая функция) Но валидация в Angular настолько специфична, что такие тесты ничего не опишут. В итоге нашёл удобный масштаб – стал тестировать класс формы BankFormGroup целиком. Удобно описывать кейсы, которые эмулируют действия пользователя.

        // test: should fill bank data
        const fg = new  BankFormGroup(checkBicApiMock);
        fg.controls.bic.update('123456789');
        expect(fg).toBeInvalid();
        expect(fg).toBePending();
        wait(1000) // ждём, пока проверка БИК выполнится
        expect(fg).toBeValid();
        expect(fg.bankName).toBe("Название банка от сервера");


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


      1. parsler
        11.08.2021 18:04
        +1

        У меня на практике обычно вот эта часть, что вы назвали "через добавление 10 ифчиков, а потом заменяете это на нормальный алгоритм" вызывала трудности, т.к. "нормальная" реализация всегда приводила к переименованию\удалению интерфейсов\классов\методов\аргументов, перестройке всей структуктуры кода. После чего все ранее написанные тесты грубо говоря проще выкинуть и написать заново. Как вы с этим боролись?

        Декаплингом тестов от имлементации (тест не должен знать, как именно тестируемая фукциональность имлементирована, т.е. не должно быть verify (проверок вызова метода зависимого класса внутри тестируемой функции) и очень помогает сильно ограничить использование автомокеров). При этом подход к тестам довольно сильно меняется. Вместо того, чтобы писать тестовый класс на конкретный класс реализации (например, Multiplier -> MultiplierTest, 1:1), тест пишется на конкретную функциональность ("умножение чисел").

        Функциональность - это как раз то, что скорее всего будет оставаться гораздо более стабильным, чем реализация; ведь вы эту фичу не просто так добавили, а потому что бизнес/пользователи попросили. Вы можете и скорее всего дальше будете её усложнять, но нечасто просто выкините саму фичу из того, что делает ваша программа.

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

        Хороший пример того, как это делается, можно посмотреть в TDD курсе у J.B. Rainsberger (JBrains).

        Конечно тут же встает вопрос, что такое юнит и что такое юнит тест, и в этот холивар я точно лезть не хочу : )


        1. vladimir_qc Автор
          11.08.2021 22:27
          +1

          А не хотите статью запилить с объяснением, примерами и особенностями подхода, по комментарию не совсем понятно как это?


          1. parsler
            12.08.2021 08:28
            +1

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

            Тут нужно показывать не самый простой проект, написанный по TDD с тестами, которые ничего не знают про имплементацию проверяемой функциональности, то, как написаны тесты и то, как они при этом помогают рефакторингу. Из того, что на эту тему есть в сети, мне JBrains с лету приходит в голову вышеупомянутый (у него есть курс The World's Best Intro to TDD, действительно очень качественное введение в TDD), хотя наверняка есть и другие примеры. Но они вряд ли будут маленькими.

            Я чтобы этому научиться уже не первый месяц в свободное время пишу код в паре с людьми, которые давно (некоторые по 20 лет и больше) в этой тусовке и участвую вместе с ними в mob programming сессиях.

            Поэтому я тут наверно только могу порекомендовать, куда копать, если дальше интересно. Копать в сторону decoupling тестов от имплеметации. Про это есть целая школа TDD (Chicago, classicist). Можно про них почитать, можно посмотреть курс JBrains, у которого хорошо показаны тесты на фукнциональность которые не завязаны на конкретную имплементацию, есть хорошая статья про это у Martin Fowler "Mocks Aren't Stubs" (https://martinfowler.com/articles/mocksArentStubs.html), и еще вот тут есть отличный ответ на ту же тему от perfectionist https://softwareengineering.stackexchange.com/questions/5898/how-do-you-keep-your-unit-tests-working-when-refactoring (начинается "Contrary to the other answers...").

            Еще из людей, у которых скорее всего это можно увидеть на видео, вроде бы у Uncle Bob были примеры, он тоже классицист TDD-шный, хотя я не особый его фанат. У Geepaw Hill есть хорошие видео. Еще приходит на ум James Shore, у которого тоже много хороших видео в свободном доступе и можно найти примеры хорошего кода на JS с TDD.


            1. kemm
              12.08.2021 09:57

              видео
              посмотреть
              видео
              видео
              видео
              видео

              А-а-а-а-а-а!!! звук роскомнадзора об стену


            1. kemsky
              15.08.2021 23:43

              К сожалению это платный курс, а платить за кота в мешке не хочется.


              1. parsler
                18.08.2021 01:02

                Да, и если вам не особенно интересна тема TDD, то скорее всего действительно не стоит тратить время и деньги.


            1. parsler
              18.08.2021 06:25

              Еще на тему стабильных тестов, которые не ломаются от каждого чиха:

              https://www.youtube.com/watch?v=URSWYvyc42M&ab_channel=Confreaks


      1. Justerest
        11.08.2021 07:04

        Да, я схитрил, и одним предложением заменил целую эпопею) На меня сильно повлияла вот эта статья про отказ от зависимостей https://habr.com/ru/company/jugru/blog/545482/

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

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

        Пример из практики – многостраничная форма. (Да, я тупой/модный фронтендер)

        Требования: пользователь по шагам заполняет данные, может возвращаться что-то редактировать, между шагами иногда должны пробрасываться данные, на последнем шаге идёт большой POST на сервер.

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

        Поэтому сначала пишем через TDD маленький и простой класс, отвечающий за навигацию по шагам: нужно знать какой шаг активный (`+getActiveStep()`), на какой шаг можно/нельзя перейти (`+isStepAvailable(stepId)`). Используем минимальный интерфейс, который нужен только для этой функциональности – получаем игрушечный класс Stepper и крохотный интерфейс Step. Начинаем использовать нашу игрушку в большом грязном классе MainStepper. Да, большой грязный класс остаётся без тестов, но ответственность за функциональность навигации остаётся в нашем игрушечном классе и хорошо протестирована.

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


        1. ApeCoder
          11.08.2021 08:57

          Почему большой класс оставется без тестов? Можно хотя бы E2E/Integration тест написать, который его покроет?


          1. Justerest
            11.08.2021 09:49
            +3

            Ни в коем случае! TDD строго запрещает другие виды тестирования!)))


            1. ApeCoder
              11.08.2021 10:07

              Ссылка, цитата? чорт смайлик не заметил



          1. Justerest
            11.08.2021 10:44

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


          1. AnswerREST
            11.08.2021 13:46

            В таком случаи, коллега, мы говорим об одном и том же с разных точек обзора :)


          1. Raimon
            11.08.2021 16:07

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


      1. AnswerREST
        11.08.2021 11:34

        Видимо я не верно выразился либо вы не так прочитали, "67%" я имею ввиду не успешность тестов, с 60-70% покрытия тестами приложение. Иными словами ~30% кода приложения не нуждаются в тестах.


      1. ApeCoder
        11.08.2021 11:19

        А сам файловый логгер тестировать надо?

        А то, что в приложении в целом используется файловы логгер?


      1. kemm
        11.08.2021 11:22
        -1

        А потом эти же люди удивляются, почему это вдруг несчастная страничка текста на сайте отжирает 4гб памяти и целиком одно ядро. 8))


        1. Kanut
          11.08.2021 11:25

          A это здесь причём? Какое это имеет отношение к тому что вещи вроде конкретной реализации вашего логгера действительно логичнее всего прятать за фасадом/интерфейсом?


          1. kemm
            11.08.2021 15:01

            Ничего не даётся бесплатно. Поэтому квантор всеобщности там явно лишний. С одной стороны чаще всего накладными расходами можно пренебречь, с другой — иногда так посмотришь, как тут пренебрегли, и вот тут, и ещё в 100500 местах, а в итоге разбор какой-нибудь не особо развесистой pdf'ки poppler'ом занимает секунды, а то и десятки секунд.


            1. Kanut
              11.08.2021 15:07

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

              Мне интересно что вы понимаете под «накладными расходами» в случае с интерфейсом.

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

              Но я не могу себе представить таких сценариев в контексте вашего заявления про «страничка текста на сайте отжирает 4гб памяти и целиком одно ядро.»

              тут пренебрегли, и вот тут, и ещё в 100500 местах, а в итоге разбор какой-нибудь не особо развесистой pdf'ки poppler'ом занимает секунды, а то и десятки секунд.

              И сколько из этих «десятков секунд» по вашему будет «потеряно» из-за того что кто-то где-то использует интерфейсы?


              1. kemm
                11.08.2021 15:43

                Мне интересно что вы понимаете под «накладными расходами» в случае с интерфейсом.

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


                Но я не могу себе представить таких сценариев в контексте вашего заявления про «страничка текста на сайте отжирает 4гб памяти и целиком одно ядро.»

                Это всё-таки по бОльшей части (на 99.9%) шутка.


                И сколько из этих «десятков секунд» по вашему будет «потеряно» из-за того что кто-то где-то использует интерфейсы?

                Чистых интерфейсов там, емнип, нет, но наследование во все поля и прочие лучшие (или которые когда-то считались лучшими 8)) ) практики. Давно дело было, да и мерять такое тяжело. По ощущениям, оверхед там заметную долю составляет просто в силу устройства pdf, вызовы всяких виртуальных методов там на каждый чих.


      1. DirectoriX
        11.08.2021 12:53

        Отлично, вместо простого файлового логгера мы получили или один универсальный (который и в файл писать умеет, и в БД, и в systemd, и по сети отправлять в какой-нибудь сервис), либо получили простой интерфейс-обёртку + пачку реализаций, одна из которых — тот самый файловый логгер. Что дальше-то?


        1. Kanut
          11.08.2021 13:14

          либо получили простой интерфейс-обёртку + пачку реализаций, одна из которых — тот самый файловый логгер. Что дальше-то?

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

          А сами конкретные реакизации логгера вы можете тестить отдельно. Или не вы, а те кто их написал.


          1. nin-jin
            11.08.2021 14:19

            Вообще, консольный вывод программы можно легко направлять хоть в файл, хоть в сеть, хоть в dev/null. И это даже куда более гибкое решение, чем фасад и куча плагинов.


            1. Vilaine
              12.08.2021 02:43

              Вообще, вовсе даже не очень легко. Логирование — это не только строчка с текстовыми описанием, а порой и обилие дополнительного контекста. Парсить что-то из консольного вывода — часто так себе идея.


              1. nin-jin
                12.08.2021 07:18

                1. Vilaine
                  12.08.2021 22:24

                  Теоретическая и порой практическая возможность есть, и, более того, 12 factor app предписывает так делать. Но на практике получается, что вместо превращения обильных структурированных данных в текстовый формат и обратно, проще послать сразу структурированные данные куда следует. В Sentry, например. Читать глазами то, что нужно Sentry, всё равно бесполезно.


                  1. nin-jin
                    12.08.2021 23:26

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


                1. nin-jin
                  11.08.2021 15:38

                  Ну или включите им это медитационное видео:


          1. DirectoriX
            11.08.2021 18:10

            Я вам сейчас глаза открою: не все проекты (особенно довольно низкоуровневые и/или высоконагруженные) используют готовые фреймворки.
            Более того, существуют проекты, которые действительно логируют только в один файл (максимум в syslog ещё). Если вам надо — натравливайте на этот логфайл что вам хочется: хоть дампилку в БД, хоть отправлялку по сети, хоть grep. Если интересно — посмотрите на всякие Asterisk и иже с ними.


      1. Raimon
        11.08.2021 16:06

        это собеседование, оно ограничено по времени. мы обсуждаем вопросы дизайна, и тестирования, многопоточности на этом простом примере.

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


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


  1. AnswerREST
    10.08.2021 22:59

    1. 100% покрытие кода, может быть это "пушка" для педанта, но в живых проектов 60-80% этого более чем достаточно (сужу по свои кейсам).

    2. TDD как подход к реализации... Лично мне не зашёл, поскольку чаще всего прибегает "бизнес" и дедлайны уже вчера ))) в итоге рефакторинг

    3. А вот с посланниками TDD под калькулятор - тут 100 балов))))


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


  1. queses
    10.08.2021 23:22
    +3

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

    • Лучше всего писать по TDD код, реализующий бизнес-логику. Инфраструктурный код, контроллеры, UI и прочее поддается хуже; в этих случаях я не пытаюсь в TDD

    • Как следствие предыдущего пункта, для работы по TDD в приложении уже должна быть кое-какая инфраструктура, готовая к тестированию; писать по TDD с нуля не особо заходит

    • Перед написанием тестов нужно определить их тип. Я разделяю юнит- и интеграционные тесты. Чтобы не увязнуть в моках, следую правилу - если в модуле нет зависимостей от внешних ресурсов, то пишется юнит тест (соответственно, юнит тесты никогда не содержат моков); иначе пишется интеграционный тест, в котором моками заменяются только внешние зависимости, которые невозможно/неудобно поднимать локально (например, СУБД всегда использую реальную)


  1. Raimon
    11.08.2021 00:53

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


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


  1. zloddey
    11.08.2021 07:23
    +6

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

    Как пресловутый сторонник TDD, я даже не знаю, с чем тут спорить. Могу лишь подписаться под этими словами. Вот только все эти абсолюты к самому TDD имеют мало отношения, разве нет?

    Как мне кажется, сложность с TDD нет, не так.

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

    Хотя и сам по себе TDD бывает сложен в освоении. Эта техника контринтуитивна (по крайней мере, для явного большинства программистов). Но только до определённого времени. Если наработать достаточное количество практического опыта, то что-то в голове переключается. Начинаешь понимать суть подхода и видеть вещи под новым углом. Где-то в этот момент ты превращаешься в "фанатика" и пытаешься применять все эти подходы, неплохо описанные в статье. А после определённого числа набитых на подходах шишек начинаешь лучше понимать границы применимости, отделять булшит от реально полезных вещей.

    Вот только сходу, без опыта, бросаться на тестирование многопоточного кода - это верный рецепт получить тяжёлую психологическую травму (сочувствую). Лучше всего начинать с code kata или пробовать силы на маленьком стороннем проекте. Каты дополнительно хороши тем, что их можно гонять в качестве быстрой утренней разминки. Выделил 20 минут, провернул за это время десяток-другой red-green-refactor циклов, стёр код и пошёл работать. На сложном рабочем коде такое количество циклов может занять гораздо большее время, а опыта в TDD принесёт не сильно больше.

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


    1. parsler
      11.08.2021 13:20
      +5

      +1 к "Вот только все эти абсолюты к самому TDD имеют мало отношения, разве нет".

      Владимир, вас, похоже, сильно травмировали, возможно не очень адекватные коллеги. Бывает.

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

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

      2. Тесты как документация не является целью TDD! Совсем. Вы не с BDD перепутали?

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

      4. "Да, и что бы 2 раза не вставать замечу, что когда тесты покрывают код целиком и полностью, не оставляя живого места, любая попытка изменения функции или ее интерфейса приводит к дикому баттхёрту." - а вот это очень интересная тема. Дело вообще не в тестах, дело в моках. Есть две школы TDD, Чикагская и Лондонская. Чикаго - "мокисты", которые изолируют каждый класс и мокируют все зависимости. Лондонская школа избегает того, чтобы тесты знали, КАК метод делает то, что он делает - они проверяют только результат! И при таком подходе тесты - первейший инструмент рефакторинга, который помогает менять код, не ломая его. В этом собственно и основной смысл тестов.

      5. "TDD позволяет сформировать хорошую архитектуру на ранних этапах, которую потом почти не придется менять (как и код), ибо TDD заставляет подумать до написания кода". TDD помогает писать код, который легко тестировать (юнит тестами, да). Задачи не менять её не стоит вообще! Совсем наоборот, при добавлении любой фичи - смотришь на то, а не поменялось ли чего более глобально, как эту фичу встроить; рефакторишь всегда! И тесты, если ты их сохранил гибкими и не знающими имлеметации того, что они тестируют, очень в этом помогают.

      6. "Юнит тесты хорошо, а другие – плохо". Ну блин. Откуда вы это взяли? То, что есть тестовая пирамида, не означает, что не нужно ничего, кроме юнитов. И опять же, TDD тут мало при чем.

      7. "Вот только легко тестируемый код не дает никаких гарантий качества получившегося приложения." - какие-то дает, но конечно не панацея. Если сначала не поговорили с клиентом или поговорили криво и сделали не то, то все в помойку.

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

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

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


      1. vladimir_qc Автор
        11.08.2021 13:41
        +1

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

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


        1. nin-jin
          11.08.2021 14:25

          Подкину ещё немного дровишек: https://habr.com/ru/post/520264/


          1. zloddey
            11.08.2021 19:42

            Да, там неплохие комментарии ;)


            1. nin-jin
              11.08.2021 14:41

              QA cвоего пригласите лучше.


            1. kemm
              11.08.2021 16:04

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

              Это да. У меня просто "детская травма" от абсолютистов TDD и юнит-тестов. 8))


            1. numitus2
              12.08.2021 10:38

              Ну и я в частности. Мне вот интересно как с подобным подходом:

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

              Вы будете писать юнит тесты для класса который реализует стек. Как вы протестируете метод push не вызывая других методов этого же класса


      1. parsler
        11.08.2021 18:57
        +2

        Упс, только я тоже ежа с апельсином спутал выше. Про mockist vs classicist TDD школу, мокисты - это Лондонская школа. : ) Чикаго - classicist и как раз они тестируют все через state и избегают знания имлементации. Сорри!


      1. zloddey
        11.08.2021 19:39
        +1

        Если уж начинать постить рекомендации, то я бы посоветовал посмотреть видео от James Shore. Он рассматривает много разных случаев, в том числе и традиционно сложные случаи: сетевое взаимодействие, таймауты и т.п. Да и рассказывает хорошо, душевно так.


        1. parsler
          18.08.2021 00:58

          James Shore очень толковый чел, +1.


      1. vsh797
        11.08.2021 20:29

        Существуют возможности делать наброски даже по TDD, нащупывать первоначальную архетектуру — которую потом тридцать раз может быть поменяешь

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


      1. Nick_Maverick
        16.08.2021 13:07

        отлично написано, тот случай когда каменты великолепны )

        А не находите ли аналогии ТДД с процессом научного поиска, где перед тем как начать чтот делать, надо прикинуть критерии проверяемости и какими экспериментами проверять, что получилось)


        1. nin-jin
          11.08.2021 14:31

          Вот-вот, пишут тесты, совершенно не понимая как и зачем их писать. Ещё и имеют совесть учить других как правильно, даже не проконсультировавшись со специалистами, которые на тестировании собаку съели.


        1. kemm
          11.08.2021 15:15

          "Неправильное понимание" я тоже отношу к "просто непониманию". :)

          Я к тому, что оценка "понимающих" может быть сильно завышена. 8))


          Могу тут сформулировать что такое, по моим понятиям, "кошерный" юнит-тест.

          Спасибо, полезно. Хотя местами несогласен:


          1.


          Для меня предел это всего 2-3 приватных метода в классе, хотя я вообще любой приватный метод, даже если он всего один, всегда рассматриваю как возможного кандидата на вынос вовне класса.

          Это какой-то экстремизм, имхо. 8)) Какая-нибудь достаточно сложная логика просто для декомпозиции легко может потребовать значительно бОльшего кол-ва приватных методов, при этом выносить их в отдельные классы оверхед и размывание ответственности.


          2.


          Для этого в тестируемом коде все это должно быть при надобности завернуто в абстракции и инжектиться в класс через DI (собственно это даже просто один из принципов SOLID — "инверсия зависимости").

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


        1. numitus2
          12.08.2021 02:16

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


    1. vladimir_qc Автор
      11.08.2021 13:36

      Моя ошибка, нужно было более явно выразить мысль в статье. Тут наверное ближе всего будет аналогия с Библией и подобными вещами. Мало кто читает Библию сам, особенно в оригинале, обычно есть переводы с акцентами на что-то, есть адаптированные версии, есть толкования и пояснение. Проблема в том, что с одной стороны такие вольные изложения могут иметь мало общего с оригиналом и взглядом автора. А с другой - они сильно более популярны, чем оригинал.

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


  1. anonymous
    00.00.0000 00:00


    1. nin-jin
      11.08.2021 11:14

      Ещё больше печально, что адепты юнит-тестирования собственно тестирование не понимают вообще.


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


    1. kemm
      11.08.2021 11:20

      Просто из доброты душевной, что б добить: а если Вы попробуете этой половине очень подробно и понятно объяснить юнит-тестирование, то выясните, что из второй половины, которые понимают, минимум 3/4 понимают неправильно (ну, по крайней мере, не так, как Вы) 8))


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


  1. sshmakov
    11.08.2021 16:21
    +1

    Интересный факт. Как известно, TDD появилась и стала популярной благодаря усилиям Кента Бека, который был РП системы расчета зарплаты в Крайслере. Это история. А факт в том, что эта система была запущена в 1997, а в 1999 были прекращены какие-либо ее доработки, и в 2000 полностью от нее отказались.

    https://en.m.wikipedia.org/wiki/Chrysler_Comprehensive_Compensation_System

    И вот на основе этого не самого долгоживущего проекта делаются далеко идущие выводы об универсальной применимости TDD во всех проектах.


    1. sshikov
      11.08.2021 18:26

      Не долгоживущего это бы ладно. Так он же скорее всего был и неудачен?


      1. sshmakov
        11.08.2021 18:27

        Смотря для кого. Кент Бек только выиграл.


        1. sshikov
          11.08.2021 18:35

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


          1. sshmakov
            11.08.2021 18:43

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


            1. zloddey
              11.08.2021 20:09

              На вики так написано:

              Chrysler was bought out by Daimler-Benz in 1998, after the merger the company was known as DaimlerChrysler. DaimlerChrysler stopped the C3 project on 1 February 2000.

              Т.е., параллельно с развитием продукта шёл процесс поглощения компании со стороны более крупной фирмы. Когда процесс слияния был завершён, Большие Боссы устроили зачистку в полученном наследстве.

              Наверно, они были большими экспертами в XP, раз так быстро увидели бесперспективность проекта? Ну, или у них был огромный собственный департамент, который занимался финансами (читайте про Debis AG на этой странице)? Оставим выводы читателям. Лично я никаких определённых выводов из этой истории сделать не рискну.


  1. anonymous
    00.00.0000 00:00


    1. ApeCoder
      13.08.2021 10:16

      Пишут маленькие интеграционные тесты?


    1. DirectoriX
      13.08.2021 12:11
      +1

      Мне кажется, тут нет именно хейтеров юнит-тестов, тут есть согласные с мнением автора статьи. Согласные с тем, что TDD, как его часто понимают («тесты важнее кода») — ещё одна крайность, которая оказывается полезной/применимой в ну очень ограниченном подмножестве случаев.


      1. Kanut
        13.08.2021 12:12
        -2

        То есть проблема не в TDD, а в том, что какие-то люди принимают за TDD? «Сама придумала, сама обиделась»? :)


  1. nick1612
    15.08.2021 21:42
    +1

    Практически полностью согласен со всеми тезисами автора статьи, прямо как будто сам писал)