25 октября 2016 в 19:23

TDD все еще сравнивают с TLD — мнения экспертов



Специалисты из нескольких ВУЗов Европы – Давиде Фуччи, Джузеппе Сканиелло, Симоне Романе, Мартин Шеппэрд, Бойсе Сигвени, Фернандо Уйагуари, Бурак Туран, Наталья Юристо и Марку Ойиво – провели очередное исследование на тему эффективности тестирования ПО. Они рассмотрели методологии Test Driven Development (TDD) и Test Last Development (TLD).



Исследователи сравнивали их по двум показателям – суммарная скорость разработки продукта и качество исходного кода. Первая методология (разработка через тестирование – TDD) вновь не оправдала возложенных надежд: популярная ранее схема тестирования после разработки (TLD) оказалась не менее эффективной. Так что по указанным выше показателям существенных отличий они не обнаружили.

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

Автор TDD Кент Бек выделяет пять основных этапов при использовании методологии на практике:

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

TDD и TLD имеют свои преимущества и недостатки. Причем, преимущество одной методологии зачастую является недостатком для другой.

• Скорость разработки тестов

В случае с TDD инженерам приходится тратить на 16% больше времени, чем с TLD. Это объясняется дополнительным затратами на переключение разработчиков между написанием тестов и основного кода.

• Порог вхождения

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

• Производительность и сопровождение

Благодаря TDD снижаются затраты на сопровождение программного продукта. Обычно при использовании этой методологии количество тест-кейсов примерно на 50% больше, чем в случае с TLD. Это дает большее покрытие и подразумевает повышенную надежность продукта. Поэтому и сопровождать такое ПО легче. За счет хорошо продуманной архитектуры производительность системы, разработанной с использованием, TDD обычно выше.

• Объем кода

Применение TLD позволяет существенно сократить объем кода по сравнению с TDD. Кроме того, код с TLD зачастую имеет более простую структуру.

• Внесение изменений

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

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

Компромиссы


Иван Хватов:
И то, и другое – крайности. Всё зависит от ситуации. В целом, тесты – это очень больная и холиварная тема. Вопрос ещё не закрыт о том, какие тесты вообще писать надо или не надо: юнит, функциональные, или интеграционные (или все).
Приведем пример возможного диалога между менеджером и архитектором.
А: — Хотим ли мы 100% покрытий?
М: — Да, конечно.
А: — Отлично, только это затянет разработку вдвое и усложнит сопровождаемость тестового кода на порядок.
Широко распространено мнение о том, что дизайн среднего качества обеспечит покрытие ключевых кусков кода на 80-85%, а каждые последующие 5% будут отнимать все больше и больше ресурсов (труда и времени).

Более того, автор TDD Кент Бек как-то высказывал мысль, что ненужные тесты можно удалять. Такие тесты он называет delta coverage – дополнительное покрытие, обеспечиваемое конкретным набором тестов. Если это дополнительное покрытие равно 0, то тест можно смело удалять.

Кент Бек — разработчик программного обеспечения, создатель таких методологий разработки ПО как экстремальное программирование (XP) и разработка через тестирование (TDD). Бек был одним из 17 специалистов, подписавшихAgile Manifesto в 2001 году.

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

Мартин Фаулер привел два простых эмпирических правила, которые определяют нижнюю и верхнюю границы покрытия: если вы не уверены в своих изменениях, и многие из них приводят к сбоям, но не ломают тесты, значит тестов слишком мало. Если же при каждом изменении ломается слишком много тестов, то, возможно, тестов слишком много.

Мартин Фаулер — автор ряда книг и статей по архитектуре ПО, объектно-ориентированному анализу и разработке, языку UML, рефакторингу, экстремальному программированию, предметно-ориентированным языкам программирования.

Когда стоит использовать TDD?




На графике изображена скорость развития двух проектов. Синие принесли в жертву дизайн и тесты. Их цель – как можно скорее реализовать максимальный объем функциональности. Красные, напротив, уделяют большое внимание дизайну системы и её тестированию.

Очевидно, что синие оставляют в коде много «технических долгов», чем обрекают себя на трудности при расширении системы в будущем и проблемы с её поддержкой.

Но за счёт чего красные умудряются держать систему гибкой и двигаться со стабильной скоростью? Одна из причин — это использование TDD, пишет Александр Бындю, эксперт по Agile и Lean.

Он приводит ряд преимуществ TDD в проекте красных:

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

2. Код становится более качественным. Это связано с тем, что модульное тестирование подразумевает слабую связанность различных модулей системы, иначе будет очень сложно написать эти модульные тесты. Поэтому приходится применять, например, принципы проектирования классов S.O.L.I.D:
Принцип единственности ответственности (The Single Responsibility Principle)
Принцип открытости/закрытости (The Open Closed Principle)
Принцип замещения Лисков (The Liskov Substitution Principle)
Принцип разделения интерфейса (The Interface Segregation Principle)
Принцип инверсии зависимости (The Dependency Inversion Principle)
В конечном счёте, это даст всей системе большую мобильность и гибкость.

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

Поэтому, по мнению Бындю, TDD вынуждает писать более качественный код, который удобнее тестировать. А это значит, что мы оставляем в коде меньше технических долгов.
Когда же TDD начинает обгонять? Тогда, когда синяя и красная линия пересекаются. В это время команда синих вовсю начинает платить за долги. Отсюда вывод о границах применимости TDD:
Вы можете писать код без TDD, если уверены, что до пунктирной линии дело не дойдет. Например, вы пишете небольшой проект для автоматизации внутренней работы, или создаете сайт-визитку.
Надо понимать, что TDD – не гарантия качества вашего кода, но с ним проще держать систему в тонусе, заключает он.

Не одно «но»


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

Давид Хейнемейер Ханссон — датский программист, автор веб-фреймворка Ruby on Rails, основатель Instiki wiki и автогонщик, победитель в классе LMGTE Am (2014) и серебряный призёр LMP2 (2013) чемпионата мира по гонкам на выносливость, победитель 24 часов Ле-Мана 2014 года в классе LMGTE Am.

Однако у Мартина Фаулера есть важная оговорка на этот счет:
Когда система начинает разрастаться, то проблема не в количестве уровней абстракции, а в количестве уровней изоляции. То есть, сам факт того, что класс А вызывает метод класса Б, который обращается к классу В не представляет проблему до тех пор, пока между каждым из этих классов не появляется уровень изоляции в виде интерфейса.
С другой стороны на эту тему смотрят некоторые пользователи «Хабра».

Мнение пользователя Volch:
Нужно понимать, что применение таких методологий как TDD предполагает, что автотесты пишут разработчики, а не тестировщики. Это методологии программирования и кодирования, а не разработки готового продукта, в которую вовлечено множество людей от ПМов и техписателей до админов и техподдержки. Разработчики же зачастую относятся к внедрению TDD-like как взваливанию на них обязанностей тестировщиков.
Мнение пользователя Tagakov:
TDD действительно не то, что должны делать тестировщики. Их задача писать функциональные тесты и, если хватает квалификации, интеграционные. TDD не TDD, но разработчики испокон веков занимались тестированием, запуская свой код и проверяя как он работает, юнит-тестирование это скорее формализация этого процесса.
Мнение пользователя Kunis:
Если тест написан до и предоставлен программисту в качестве исходных данных, он становится «техническим требованием» (requirement). Разумеется, тот, кто пишет тест, должен заранее написать и запрограммировать все требуемые моки. Ха-ха, не хотел бы я быть тем программистом, в обязанности которого входит программировать тесты для других.

Ну, а может ли сам программист написать тесты к своему коду заранее? По моему опыту, нет и нет. Программисты (и я и подавляющее большинство, с кем я это обсуждал) просто думают в обратном направлении. Да, поначалу люди пытались честно следовать методологии. Но спустя какое-то время, начинали сначала писать код, а потом на него дописывать тесты. А еще спустя какое-то время, тесты потеряли свою разносторонность и дотошность. А потом и вовсе писались «штоб было», раз уж просят.

А оно вообще надо?

Адаптация TDD


Свое мнение также высказывал один из апологетов разработки через тестирование Александр Люлин:
Я не использую TDD в его классическом понимании. Вообще, вряд ли кто-то из профессионалов рассматривает энциклопедические статьи в качестве руководства к действию. Мы свой подход «выстрадали» в рамках реализации успешного проекта, поэтому за нами реальный опыт, а не «тупое использование чужих идей».

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

Олег Балбеков, СЕО Vexor:
На наш взгляд жестко разделять данные подходы довольно сложно и вообще не нужно. Так повелось, что в нашей команде мы обычно совмещаем использование обоих подходов в одном проекте, и даже иногда при разработке одного блока кода.

Ну, например, когда мы пишем часть проекта для работы с сервисами через API, мы пишем сначала тесты и уже потом код (TDD). Однако после реализации часто бывает так, что написанных заблаговременно тестов недостаточно и приходится их дописывать, добиваясь 100% покрытия (TLD).

Иногда даже на очень простые контроллеры (обычный ресурс с базовыми CRUD), сначала пишутся простые тесты, а потом уже код (TDD). Это нам дает уверенность в том, что ничего не забыто в первоначальной реализации. В дальнейшем, при развитии проекта, в большинстве случаев тесты пишутся уже после написания кода (TLD).

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

Чаще всего TLD подход применяется во время рефакторинга. Сначала мы покрываем тестами существующую реализацию и уже потом переписываем ее.

Если говорить про соотношение использования TDD и TLD в проектах, то выигрывает обычно TLD. Разработчиков, которые просто пишут тесты всегда больше тех, кто в состоянии «думать тестами» и писать тесты заблаговременно.
Григорий Гринштейн @semen_grinshtein
карма
25,0
рейтинг 58,6
Редактор
Самое читаемое Разработка

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

  • +1
    Пришлось искать информацию о том, что такое TLD во внешних источниках. Догадаться конечно можно, что это противоположность TDD, но из названия это не очень очевидно.
  • 0
    Мне кажется автор методологии TDD не совсем правильно поступил, когда выделил в этой концепции конкретные этапы. TDD все-таки не процесс, а принцип разработки ПО. А суть принципа в том, что тесты являются неотъемлемой частью кода, а не его опциональным дополнением.

    При этом не так важно в какой очередности будут добавляться тесты к коду. Главное тут то, что и написание кода, и тестов к нему — есть единая активность, а не две разные задачи. Я хочу сказать, что в TDD тесты и код пишет один и тот же человек, и тесты в этом случае являются способом проверки работоспособности кода (гипотезы).
    • 0

      TDD это Test Driven Development — то есть тесты должны двигать разработку — то есть быть впереди. Если вы хотите убрать этот принцип, тогда надо придумывать другое название.


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

      Так же чтобы убедиться в корректности дизайна, документировать и т.д. См. Why we test

      • 0
        TDD это Test Driven Development — то есть тесты должны двигать разработку — то есть быть впереди

        Все зависит от интерпретации на самом деле. Driven не всегда означает pull (тянуть за собой), вполне себе неплохо получается и при использовании push (толкать впереди себя). Поэтому я считаю, что driven в контексте TDD должно означать «разработку через тестирование», что не указывает явно на то, в какой конкретно момент должны появится тесты — до кода или после.
        • 0

          Интересно, как тесты, которых еще нет могут управлять разработкой

          • 0

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

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

            И мне не обязательно в этом случае писать тесты до кода. Если задача маленькая, то тесты просто дописываю в конце, если сложнее — делю на этапы: написал метод/класс, сразу написал на это тест и т.д.
            • 0
              адская действительность многих разработчиков, которые не используют в своей работе тесты, то есть TDD.

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

              • 0
                управляемой

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

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

                Аббревиатура TDD используется чтобы описать совершенно другие аспекты раработки

                Вот поэтому в своем первом комментарии я и высказал мнение, что автору данного понятия не стоило выделять конкретные этапы или аспекты данного подхода. Небольшое отличие — и это уже не TDD, все в точности как со Scrum, его тоже в эталонном виде никто не применяет.
                • 0
                  И опять же, driven — не означает «управляемый», это скорее «движимый».

                  В случае с TDD это скорее управляемый, чем движимый.


                  автору данного понятия не стоило выделять конкретные этапы или аспекты данного подхода

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

                  • 0
                    Просто писать тесты недостаточно.

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

      Но вообще, гипотеза не очень хорошая аналогия, по-моему, даже теорема ближе: дано и доказать — тесты, собственно код — доказательство.
      • 0
        Если сравнивать с теоремой, то тесты являются доказательством работоспособности кода, а не наоборот, разве нет? Заставлять человека думать иначе — это значит сознательно вводить его в когнитивный диссонанс (все давно привыкли, что доказательством работоспособности любого проекта являются успешно завершенные тестовые испытания).

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

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

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

              Отлично подмечено. Много кто не понимает этого принципиального момента.

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

            Ну это как-то через край.
            Баян даже.
            Программистстко — человеческий словарь


            Работает — компилируется
            Не работает — не компилируется


            Классика

        • 0
          Теоремы тут не причем. Если прибегать к метафорам из математики, то тесты в TDD — это аксиомы, или инварианты (кому как удобно оперировать терминами).
          Весь смысл TDD как раз таки и заключается в том, чтобы выделить набор утверждений для системы к-ым она должна удовлетворять, а затем уже писать саму систему.
          Еще можно провести аналогию с use case. По-факту use case один в один ложится в тест и тест является с одной стороны формализацией требований к программе, а с другой возможностью подтвердить наличия этого свойства у системы (на самом деле только выявить случаи отсутствия этого свойства)
  • 0
    TDD или TLD, как мне кажется одинаково эффективны в умелых руках и одинаково неэффективны в руках неумелых. Намного важнее правильно выбрать архитектуру проекта (в которой должна быть достаточная гибкость и тестируемость).

    TDD повышает покрытие тестами, но понижает скорость работы. TLD позволяет покрывать только нужные участки кода и не писать тесты ради тестов. Поэтому тут скорее важно стремиться к тем 85% покрытия тестами. Пусть каждый разработчик решит для себя сам, как ему удобнее. (Или выбрать общий подход в рамках команды).

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

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

  • 0
    del
  • +1
    А можно я подкину пару ссылок на уже готовые размышления по этой теме: Размышления о TDD, TDD: Test-Driven vs. Type-Driven development, Is TDD Dead, Часть 5.

    А вообще, я очень рекомендую ознакомиться с оригинальным трудом Кента и эпичной баталией под названием Is TDD Dead, чтобы увидеть, насколько автор этой практики является здравомыслящим и прагматичным человеком. Который, кстати, неоднократно писал и говорил о том, что этот подход с короткими итерациями тест-код подходит именно к нему и что у других разработчиком может и должно быть другое мнение по этому поводу.
  • 0
    Можно комбинировать TDD и TLD. Сначала написать минимальный тест, который нужен, лишь бы настроить окружение и запустить код, а также приблизительно задать API. Затем написать реализацию, а тест использовать для отладки кода. Затем, когда реализация написана, дописать assert-часть теста.
    • 0
      Т.е по сути писать код внутри контролируемого теста который тоже пишется на лету. Я так делаю например. У меня динамический язык поэтому я могу еще в любой момент залезть и попробовать код в консоли, если с ним все ок, то засунуть в реализацию/тест.
  • +3
    Читал книгу про TDD-козла, все время не покидало чувство, что это какое-то садомазо.Т.е. начинаем решать задачу не с задачи, а с его решения — мозг просто отказывается это признавать «логичным» и всячески сопротивляется.
    • 0
      Похожие ощущения приходят при изучении много чего нового… Функционального стиля программирования, например… Или наследования в JS… После пары месяцев привыкаешь, понимаешь что к чему и научаешься извлекать пользу и из этого… TDD в своё время было одним из самых тяжелых для усвоения, и одним из самых просвещающих вещей (в отличии от прототипического наследования). Попробовать все-же стоит.
    • 0

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

    • 0
      Задача — написать код делающий что-то. Тест — формализация этой задачи до такой степени подробности, чтобы даже компьютер мог проверить решена задача или нет. Вспомните школьную математику: задача написана человеческим языком типа «найти площадь прямоугольника со сторонами 5 и 10 см», мы её переписываем формальным типа «Дано: a = 5, b = 10, Найти: S», а в конце учебника ответ «50 см2». Но учителя просто переписанный ответ не устроит, ему нужно решение, общая формула и нам нужно в учебнике найти формулу которая для 5 и 10 даст 50 :)
      • 0
        Здравствуйте. Скажем если взять пример из физики, то решая задачу и получив ответ, мы выполняем проверку размерности, тоесть выполняем проверку, действительно ли то что мы нашли, соответствует ответу, хотя бы по возможности. И мы не можем это сделать, до написания решения, поэтому даже в таких простых задачах, я вижу не только тесты для формализации задачи.
        • 0

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

        • 0
          Мы получаем ответ не из решения, а из раздела учебника «ответы на задачи». А когда-таки решим, то сравниваем наш ответ с заранее известным.
  • +3
    Я ленив. ОЧЕНЬ ленив. Особенно писать тесты. Особенно в соответствии с TDD: т.е. до того, как напишу код, до того, как пойму, что вообще буду писать. Особенно при том, что в процессе разработки я активно перемалываю рефакторю код. Инлайн\выделение методов, свертывание и развертывание объектов (в последовательности и более простые структуры), перенос-переименование классов и т.д. согласно ходу мыслей — настолько, что уже через час код может драматически измениться.
    И в этой связи написание\изменение тестов может практически остановить этот процесс. А потому я и не пытаюсь следовать заповедям TDD.
    И все же я пишу тесты. Это происходит тогда, когда мне необходимо убедиться, что мои предположения в коде верные, а защитные механизмы (типа assert) слишком дороги, чтобы ими присыпать функции, словно снегом. Мне тесты нужны, чтобы или выделить маленькую часть и проверить те детали реализации, которые уже не различает замыленный глаз (выход за пределы массива, переполнение или потеря точности нулевые указатели и т.д.). Т.е. тестом я поднимаю не все приложение, а беру нашпигованный логикой сервис, эмулируя окружение и возможные граничные ситуации, и смотрю, насколько хорошо класс с ними справляется. Я пишу тесты, чтобы ничего не забыть.
    Или наоборот, я пишу тесты не на каждый объектно-функциональный чих, а на длинную связку процессов, таким образом повышая вероятность отсутствия (но, конечно же, не полное отсутствие!) ошибок — т.е. если результат сложного процесса корректен, то, скорее всего, корректна также и работа его составляющих.
    Иногда я пишу тесты для того, чтобы понять, что и как я буду кодить — что уже ближе к правильному подходу. Иногда проще написать такой тест, который мне покажет, как написать гибкий, расширяемый, а главное — тестируемый класс. Он же мне поможет избежать создания как и чересчур зависимого класса, так и слишком универсального — конечно, для этого тест должен моделировать более-менее правдоподобную ситуацию (или несколько)
    Такой подход позволяет уделять меньше времени написанию тестов только ради покрытия, а также меньше потея из-за мелочей, держа в уме только примерный алгоритм, без деталей — а уж детали выплывут при запуске тестов, и останется только их исправить, чтобы все в конце концов работало, а силы ушли на обдумывание решений, а не на реализацию деталей.
    Таким образом, на выходе получаем достаточно надежный код, который будет в большинстве случаев работать, при минимуме тестов и утечкой сил на их написание, а также минимум адаптации старых тестов к новому (чит. сырому) коду, а потому легче не только писать тесты, но и адаптировать их, усложнять и лучше проверять основной код.
    Это особенно важно, когда нет времени\сил\желания на скрупулезность (ведь должны быть готово уже вчера!), ведь тестирование слилось в процессе с разработкой — ни до, ни после, а именно во время (да и вовремя) разработки, при этом свежая функциональность работает в большинстве очевидных сценариев.
    И только когда основная часть выверена, тогда можно заниматься повышением покрытия сверх 80%, описывая никому не нужные тесты, просто ради того, чтобы были, а также проверяли корректность простых ситуаций.
    • +4
      TDD не про тестирование, а про разработку. TDD про формализацию требований к каждому участку кода до (или во время) его написания. И это те тесты (спецификации), которые переписываются вместе с кодом, выкидываются точно так же часто, как и переименование функции.
      Именно TDD-подход дает понять что вы хотите написать до того, как сядете писать, но:
      1. Заставляет делать это формально, а значит сам код уже будет написан гораздо более обдуманно.
      2. Заставляет писать код, с заранее заложенными возможностями тестирования.

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

      • –3
        > использование статической типизации уже шаг к TDD

        А перхоть ваша статическая типизация еще не лечит?

        Слушайте, ну есть у нее свои плюсы (мало и спорные, но есть). Но вот не нужно, пожалуйста, лепить ее в любую щель. Вот я прямо сейчас смотрю на тест, который проверяет, что моя функция корректно обработает _любой_ тип принимаемого параметра. И это, да, TDD. Такой контракт, знаете ли.
        • +1

          Зря минусуете. Как так, я на js пишу приложеньку через TDD (прям вот реально сначала тесты), а вот такого важного шага к TDD не сделал.

        • 0

          А как раз в этом контексте вспомнить это вполне уместно. Такие вещи как статическая типизация и контракты кода помогают снизить кол-во необходимых проверок в тестах.
          Почему-то вспомнилась эта статья Сергея — Контракты vs Юнит тесты. Там интересные размышления на эту тему.

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

            Шаг к TDD, например, в 99% случаев может быть осуществлен только в рамках одного языка, потому что смена языка обычно не менее судьбоносное решение в продакшене, и принимать их вместе просто страшно. Я могу в приказном порядке допускать задачу к выполнению только после ревью тестов. А вот заставить людей фигачить на хаскеле или расте — не могу.

            Ну это если говорить про реальные use cases, а не про диванных теоретиков со статической типизацией.

            • 0
              и принимать их вместе просто страшно.

              С одной стороны, да, страшно. С другой — как раз при написании кода с нуля по TDD она наиболее эффективна, а на новом языке код вы будете писать с нуля, причём уже имея довольно четкое представление о том, что нужно написать (что редкость в целом). И собственно переходу может помочь, выявляя некоторые неочевидные особенности нового языка на стадии написания кода.
              • 0
                Большое спасибо за ценный совет. Когда вы будете согласны спонсировать остановку разработки на неопределенное время, потому что «тимлид решил все переписать с нуля», я с удовольствием вернусь у рассмотрению этой блистательной идеи.

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

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

                При том, что, повторяю еще раз (я все еще помню, с какого тезиса стартовала эта ветка, да) статическая типизация — совсем не панацея и на бэкенде, например, мне пока представляется скорее злом. Ну как злом, так, не сто́ит выделки. А вот законодательно «сначала тест, потом код» — запросто. Безо всякой статической типизации, насчет которой я могу и ошибаться, но в контексте данной дискуссии это вообще абсолютно нерелевантно.
                • 0
                  Я скорее о том, что если принято решение перевести сервис (не важно, монолит, SOA или микро) на новый язык, но добавить ещё и TDD с первого коммита не так страшно, как может показаться с первого взгляда.
                  • 0
                    А, ну это-то безусловно, это настолько очевидно, что даже и упоминания не сто́ит. Только при чем тут это?

                    Лечить перхоть усекновением головы, конечно, вариант. Но вот фразу «использование статической типизации уже шаг к TDD» — к которой я и прицепился — профессионал с опытом работы написать не может, только диванный теоретик. Потому что «а давайте заиспользуем с сегодняшнего дня статическую типизацию» — это глупость вот прямо со всех сторон. Во-первых, решения о выборе языка принимаются не по принципу приятственной типизации. Во-вторых, покупка яхты — это, конечно, шаг к «научиться плавать», но для большинства людей есть более приемлемые способы. В-третьих, TDD вообще никак не связано с типами; в ЛИСПе, например, тип вообще один, строго говоря. И идите скажите этим бедолагам, что шаг на пути к TDD для них — перейти на Rust.

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

                    • 0
                      Ну, как минимум, есть динамические языки (или их расширения) позволяющие внедрять статическую и(или) строгую типизацию (или их подобие типа type hint в PHP) плавно, не с нуля переписывая весь код. И как раз при таком переходе, особенно для традиционно интерпретируемых языков типа PHP и JavaScript, её внедрение с обязательным шагом статического анализа перед, хотя бы, мержем в основную ветку, а лучше до первого реального запуска, близко к внедрению TDD по влиянию на цикл разработки. Не обязательно такая статическая типизация первый шаг, он может быть после внедрения TDD, когда устаешь писать тесты типа «метод должен бросить исключение TypeError если ему передано не целое число» и добиваться их выполнения, но по влиянию на процесс разработки они близки, если не было ни тестов, ни какого-нибудь статического анализа.

                      Не также просто форсировать, а проще, вернее решение переписать кодовую базу с нуля упрощает форсирование TDD и снижает риски фейла перехода на TDD. Язык вторичен в данном случае, первично решение всё переписать с нуля. Но TDD способно помочь именно переходу на новый язык или, не столь радикально, но новый фреймворк и прочий стэк без смены языка.
                      • 0
                        Да никто не спорит, что type hint в частности и понимание типов вообще хорошо. И статический анализ — безусловное добро (в отличие от строгой типизации, кстати). Не спорит никто.

                        Именно поэтому во всех динамических языках, рожденных с пониманием того, зачем они нужны, статический анализ и контракты есть или из коробки, или были добавлены на очень ранних стадиях. Вместо тестов типа «метод должен бросить исключение TypeError если ему передано не целое число» люди давно придумали mutation-based тесты (https://github.com/mbj/mutant — это для руби, уверен, что что-то подобное есть и для языков, которые исторически заимствуют инфраструктуру у руби, наподобие PHP и JS).

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

                        Это тот же эффект, что и в случае резиновой уточки на столе: проговаривая постановку задачи, формализуя ее, мы фактически на 90% воплощаем ее решение. Остается только набросать код. Например, я всегда готовлю красивые презентации по новым архитектурным решениям, и в процессе подготовки последние зачастую претерпевают серьезные изменения. Когда объясняешь тезисы для неподготовленной аудитории — многие казавшиеся очевидными вещи становятся не такими очевидными, а зачастую — и неверными. Тесты — то же самое. Лишний способ заставить самого себя пройти тест (пардон) на продуманность архитектуры.

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

                • 0

                  Перейти, например, на TypeScript — проще, чем кажется. Можно миксовать JS и TS, увеличивая статическую определённость постепенно.

                  • 0
                    Я не вижу смысла. Статическая типизация вытащит один камешек из огромной груды проблем js. У нас довольно специфический бизнес, кроме того, поэтому клиент все равно очень тонкий и с ним как раз проблем особых нет. И уж если мы решим куда-то переходить — то точно не на TypeScript.

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

                    • 0

                      Я ничего не говорил про TDD :-) Просто заметил, что переход не так сложен. Другой вопрос, нужен ли он.

                    • 0
                      И кроме хайпа я не слышал внятных аргументов в ее пользу.

                      Если вы пользуете статическим анализом — вам она нужна. Если нет — не нужна.


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


                      type Email = string;
                      type EmailOrPhone = Email | Phone;
                      
                      class User {
                          constructor(private email: Email, private password: Password);
                      }

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


                      TDD, как вы и сказали, это про другое. Это регрессии ловить. Например когда мы заменили элиас строки типа Email на объект с типом Email, который на самом деле хранит инварианты и предусловия этого типа.

                      • 0
                        TDD, как вы и сказали, это про другое. Это регрессии ловить.

                        Регирессии ловить — для этого просто тесты нужны. А TDD это для разработки.

                        • 0

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


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


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

                          • 0

                            Я не совсем понял. Вы написали:


                            TDD, как вы и сказали, это про другое. Это регрессии ловить.

                            Я ответил:


                            Регрессии ловить — для этого просто тесты нужны. А TDD это для разработки.

                            И далее вы пишите:


                            То есть сами по себе тесты — это второстепенно в TDD, они там тупо что бы ловить регрессии

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

                            • 0

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


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


                              А TDD — именно дизайн, приучает писать тестируемый код.

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

                            Ловля регрессий — это то зачем нужны тесты. Тесты и ловля регрессий нужны для рефакторинга. Рефакторинг обязателен в TDD. Все же TDD НЕ для ловли регрессий. Для этого и просто тесты сойдут. Он для рефакторинга и "приучения" делать тестируемый модульный код.

        • +1
          А что эта функция делает, если не секрет?
          • 0
            Не секрет, конечно. Это DSL поддержки pattern-matching’а в языке, который его из коробки не поддерживает.
            • 0
              Ну, это общее описание. А что функция возвращает, какой алгоритм реализует?
    • +2
      Или наоборот, я пишу тесты не на каждый объектно-функциональный чих, а на длинную связку процессов, таким образом повышая вероятность отсутствия (но, конечно же, не полное отсутствие!) ошибок — т.е. если результат сложного процесса корректен, то, скорее всего, корректна также и работа его составляющих.

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

  • +3
    Многим людям лень писать тесты когда код уже написан и вроде работает, хочется поскорее приступить к другим задачам, да и начальнику сложно обьяснить, почему тебе нужно еще Н времени на тесты, когда уже все написано, думаю, это важные причины, почему в методологии тесты пишутся до кода — чтобы они наверняка были написаны. Хотя лично я обычно покрываю тестами код уже когда он есть.
  • 0

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

    • 0
      Всё логично, если где-то будет внедряться TDD, то там, где люди скорее всего не владеют TDD. Вот и возникает вопрос: даёт ли TDD какую-либо выгоду, чтобы оправдать затраты на внедрение? Именно TDD, то что тесты дают выгоду, уже давно выяснили.
      • 0

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


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

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

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

        Думаю если бы взяли команду опытных разработчиков результат вполне мог бы выйти иным.
        • 0
          Опыт сам по себе позволяет писать хороший код. А вот добавляет ли к этому что-нибудь TDD — вопрос.
          • +1
            Мой личный опыт показывает что да.
            У меня сейчас есть несколько небольших проектов в виде вебсервисов, которые обрабатывают различные запросы, перенаправляют их на другие апи и пишут логи. Объем работ там примерно одинаковый во всех, при этом я на них испробовал все три варианта: самые старые вообще без тестов, те что поновее — я покрывал тестами после написания, самые новые сделаны по TDD. Схожие задачи, один и тот же разработчик, разные подходы. Могу сказать что последние — наиболее стабильные и изменения в них вносить проще и безопаснее всего.
            • 0

              А промежуток времени? Быть может качество пришло с полученным опытом?

              • 0
                Год-полтора где-то. Не так много на фоне моего общего опыта (около 8 лет).
                • 0
                  «Если вы смотрите на свой код годовой давности и вам не стыдно, то что-то не так». Полтора года — большой промежуток.
                  • 0

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

          • 0

            А вот тут снова вопрос про то, что называть хорошим кодом. Мне, например, нравится ответ, про тот, который хорошо тестируется.

  • 0
    Пишу тесты только на сложную логику, вычисления, расчёты. В какой момент — зависит от ситуации. Иногда сразу видно, какой будет функция и можно сначала написать несколько тестов, написать функцию, дописать тестов ещё. А иногда, вот так понапишешь тестов, потом начнёшь писать функцию и поймёшь, что нужно передавать ещё вот этот и этот параметр, и вообще функцию лучше разделить на части, и все тесты становятся полностью непригодными. Тогда лучше сначала написать функцию и уже после — тесты. Естественно, держа в уме, что эту функцию придётся тестировать.
    • 0
      А если по одному тесту писать?
  • 0
    Пока пишешь код понимаешь, что вообще нужно было сделать, и как нужно было, но переписывать уже нет ни возможности ни желания.
    Поэтому этапа осмысления, формирований требований и архитектуры замечательно выносится в предварительное написание тестов.
    В проектах с большой текучестью, это осмысление постфактум может плохо кончится для проекта.
    Соответственно задачи с ГУИ выпадают из ТДД, ГУИ сложно поддаются покрытию, в любом случае, и различные системные сервисы, которые замечательно покрываются.
    • 0

      GUI прекрасно поддаются TDD.

  • 0
    Использую TDD только когда пишу код с нетривиальной логикой, и сложными функциями, где можно легко допустить ошибку в реализации. Вижу рядом много дебилизма, когда пишутся тесты к функции из одной строчки которая вызывает внешний сервис который к тому же замокан(Mocked). Считаю что это перебор.
    • 0
      На текущий момент — да. Но в будущем, если проект будет развиваться, то могут быть самые неожиданные изменения, кто-то решит, что сюда надо передавать все по-другому. Сам внешний сервис изменит API, и придется сюда лезть и фиксить. Если оно все плотно не покрыто тестами, то в другом месте может оторваться.
      • 0

        Но если тестируется поведение мока, то тест все-равно не поможет.

        • 0

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

          • 0

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

            • 0

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

              • 0

                Ну вот недавно просили от одного из самых важных для нас — сказали писать свой мок :(

          • 0

            Не лучше. Если хотите дергать внешние сервисы — то это не unit test-ы.


            1. Внешний сервис может не завестись на билд сервере — прощай CI
            2. Внешний сервис может упасть
            3. Внешний сервис не будет работать достаточно быстро
            4. У тестового аккаунта могут быть ограничения по количеству запросов и рано или поздно вы в них упретесь.
            • +1

              Пусть это будут не unit-, а интеграционные тесты.
              Решение для нестабильного тестирования есть — flaky-тесты. Каждому тесту говорите "запускайся 3 раза, если 2 раза отработало — то, все хорошо". Тогда нестабильность решается повторами. Если у вас просто идет неправильная работа — значит, ошибка на вашей стороне.

              • 0

                Ну вот с этим соглашусь. Но когда надо писать интеграционные тесты? До создания системы или после? Если до, то как? Как можно понять, что внешний сервис будет корректно работать на lower environments до того как мы напишем код и тесты

                • +1

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

                  • 0

                    Ну так речь-то шла о TDD vs TLD, а вы говорите, что не важно :)

                    • 0

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


                      Другое дело что есть еще ATDD, но там именно приемочные тесты а не просто интеграционные.

                      • 0

                        Так об этом я и написал в моем ответе el777 4 сообщениями выше.

    • 0

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


      Например сущности ORM-ок любят мокать. А ведь это тупо данные. Мы таким образом создаем лишнюю зависимость тестов от реализации.

      • 0

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

        • 0

          ORM по сути шаблон отделения объектов с данными и поведением(sic!) от механизма их персистентности.

          • 0

            Объектно-реляционный маппер — не более чем способ двусторонней трансляции между двумя типами моделей. Для объектных субд орм не нужен вообще.

            • 0

              Может не до конца понимаю что такое объектная СУБД, но разве не нужен транслятор между объектами языка и объектами СУБД? Пускай не ОРМ он будет называться, а ООМ, но всё равно нужен же?

              • +1

                Адаптер к конкретной субд, конечно, нужен (и ОРМ не избавляет от его необходимости), но трансформация модели — нет.

                • 0

                  Адаптер это одно, трансформация — другое, но я именно про маппинг, если угодно то про трансформацию 1:1. Вот есть объект в базе, а есть объект в рантайме языка со своими методами, нужно между ними установить соответствие, пускай 1:1, но именно соответствие, а не просто получать структуру данных из базы.

                  • +1

                    Не стоит забывать так же о синхронизации данных, отслеживании изменений, unit of work и подобное. Это уже как раз зона ответственности ORM.

                    • 0

                      Справедливости ради не каждая ORM реализует тот же unit of work, но зато практически каждый разработчик считает что надо реализовывать свой unit of work поверх ORM.


                      Ну и разумеется нагородить самодельный маппинг поверх ORM-нутых объектов.

                  • 0

                    В реляционной базе нет объектов. Там есть таблицы и записи. Данные одного объекта обычно разбросаны по множеству таблиц, так как реляционная модель гораздо менее мощная, чем объектная. Чтобы не заниматься этим маппингом (который далеко не 1:1) вручную и приходится использовать орм, который выступает в качестве удобного объектного фасада перед неудобной реляционной базой данных.

                    • 0

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

            • 0
              Для объектных субд орм не нужен вообще.

              А есть такие? Ну то есть, вы про документно-ориетированные или про те ОО СУБД про которые в былые времена говорили но так и не взлетело?

              • +1
                • 0

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


                  Можете чуть больше рассказать об опыте работы с этой штукой?

                  • 0

                    У меня-то впечатления позитивные. но особых нагрузок у нас не было. Зато развитие проекта шло очень быстро.

          • 0

            И как часто требуется смена механизма персистентности?

            • 0

              Главное преимущество ORM — отделение слоя бизнес-логики от слоя хранения. Возможность "бесплатно" сменить последний — бесплатный бонус.

              • 0

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


                И еще как вы относитесь к постулату YAGNI?

                • 0

                  Сейчас в процессе смены MySQL на PostgreSQL на основном проекте. Операционная часть, реализованная на ORM была заменена меньше чем за день. Аналитическая, реализованная на чистом SQL — уже полгода меняется, правда в режиме "если время есть", но тем не менее.

                  • 0

                    Смену MySQL на PostgreSQL планировали на этапе зачатия проекта?

                    • 0

                      Вероятно, да, судя по комментам типа // MySQL only в операционной части, собственно процентов 90 правок за день пришлись на такие места, остальные 10 на аналогичные, но без комментов. Но потом, видимо, было принято решение на полную использовать возможности MySQL для аналитики. Сейчас сложно восстановить историю, потому что когда я пришел на проект летом 2013-го никого с опытом работы с ним хотя бы больше полугода не было, при старте где-то в конце 2010.

                      • 0

                        Ну т.е. не понятно планировали или нет. А даже если и да — то сколько проектов не планирующих миграцию тем не менее используют ORM?

                        • +1
                          А даже если и да — то сколько проектов не планирующих миграцию тем не менее используют ORM?

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


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

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

                            Миграция базы данных — плохое оправдание. Согласен. Какое оправдание будет хорошим?


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

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

                            • +2
                              Какое оправдание будет хорошим?

                              Изоляция бизнес-логики от логики хранения.


                              Ну не работает это так

                              Может бывают кейсы, когда не работает, но, субъективно, там и ООП даст крайне нежелательный оверхид, равно как и различные VM. Там надо писать максимально близко к архитектуре машины, то есть на C.

                              • 0
                                > Изоляция бизнес-логики от логики хранения.

                                Логика хранения уже изолирована в DB. Бизнес логика уже изолирована в BL. Кроме того поверх ORM регулярно городят еще и unit of work или repository. Отдельным вопросом встает использование хранимых процедур или функций.

                                > Может бывают кейсы, когда не работает, но, субъективно, там и ООП даст
                                > крайне нежелательный оверхид, равно как и различные VM

                                ООП в виде инкапсуляции, наследования и полиморфизма или ООП в виде объектов, сообщений и обработчиков?

                                Различные VM дают крайне желательную оптимальную загрузку железа.

                                • 0
                                  Логика хранения уже изолирована в DB.

                                  Обычно она реализуется в приложении всякими SELECT, INSERT и UPDATE


                                  Кроме того поверх ORM регулярно городят еще и unit of work или repository.

                                  Для удобства.


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

                                  А что они реализуют? Если логику хранения, то в принципе ничто не мешает их запускать как из приложения, так и по триггерам и т. п.


                                  ООП в виде инкапсуляции, наследования и полиморфизма или ООП в виде объектов, сообщений и обработчиков?

                                  И в том, и в другом.


                                  Различные VM дают крайне желательную оптимальную загрузку железа.

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

                                  • 0
                                    > Обычно она реализуется в приложении всякими SELECT, INSERT и UPDATE

                                    Вы серьезно? Логика хранения — это фильтрация вставка и обновление?

                                    > городят еще и unit of work или repository.
                                    > Для удобства.

                                    Точно. Для удобства. Одного уровня абстракций мало — надо как минимум три. Для того чтобы одно и то же отделять. Абстракция ради абстракции — тоже способ конечно…

                                    > А что они реализуют? Если логику хранения, то в принципе ничто не
                                    > мешает их запускать как из приложения, так и по триггерам и т. п.

                                    По триггерам — очень хорошо. Из приложения — тоже хорошо. В обход ORM или средствами ORM?

                                    > И в том, и в другом.

                                    В другом — производительность вообще не страдает. Почитайте хотя бы «Communicating Sequential Processes».

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

                                    Ах вы про эти VM. Я думал про IaaS. Но отвечу на остальное.

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

                                    Наличие дополнительного слоя трансляции безусловно усложняет оптимизацию под платформу. Но заточка под железо дает намного мненьший выигрыш чем разработка правильного алгоритма для конкретного случая. Кроме того практически всегда узким местом является либо база данных либо взаимодействие с ней и оптимизация кода приложения не дает прироста сколько-нибудь сравнимого (хотя бы порядком величины) с просто хорошо оттестироваными запросами к базе.
                                    • 0
                                      Вы серьезно? Логика хранения — это фильтрация вставка и обновление?

                                      Грубо говоря, да. Ещё удаление.


                                      Одного уровня абстракций мало — надо как минимум три.

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


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

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


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

                                      Тогда какие претензии к оверхидам многоуровневых абстракций? :)

                                      • 0
                                        > Грубо говоря, да. Ещё удаление.

                                        80% веб приложений на 100% состоят из логики хранения?

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

                                        И таким образом достигается удобство или что-то еще?

                                        > Если говорим про ООП, про объекты, значит говорим про инкапсуляцию,
                                        > полиморфизм, объекты, сообщения им и т. д.

                                        Ясно. С определением ООП вы знакомы только послестрауструповским.

                                        > Тогда какие претензии к оверхидам многоуровневых абстракций? :)

                                        Знаете что такое рациональное обоснование?

                                        > оверхидам

                                        http://dictionary.cambridge.org/pronunciation/english/overhead

                                        • 0
                                          80% веб приложений на 100% состоят из логики хранения?

                                          я бы сказал 80% каждого проекта из 90% общей массы проектов это тупо CRUD.


                                          для таких проектов есть всякие фаербэйзы и похожие подходы.


                                          И таким образом достигается удобство или что-то еще?

                                          инкапсуляция, декомпозиция.


                                          Ясно. С определением ООП вы знакомы только послестрауструповским.

                                          Вы лучше расройте каким образом мы лишаемся красоты actor model (с поздним связыванием, сообщениями и прочим) при использовании ORM? Если мы опять говорим про "юзать для всего" то это пустой разговор. Если мы про OLTP то тут они очень удобны и как раз таки не противоречит тому что завещал Алан Кей.

                                        • 0
                                          80% веб приложений на 100% состоят из логики хранения?

                                          Нет. Как минимум ещё логика отображения, пускай даже примитивная в виде json-сериализации объектов.


                                          И таким образом достигается удобство или что-то еще?

                                          Удобство как следствие понижения связанности подсистем приложения и сокрытия их сложности друг от друга, а то самого факта их наличия. Бизнес-логика вообще не подозревает о существовании системы хранения, она прозрачно (предварительная загрузка или ленивая — не суть) обеспечивает наличие в памяти необходимого бизнес-операции графа объектов. А система хранения ничего не знает о бизнес-логике, её задача только обеспечивать персистентность.


                                          Ясно. С определением ООП вы знакомы только послестрауструповским.

                                          Не только.


                                          Знаете что такое рациональное обоснование?

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


                                          http://dictionary.cambridge.org/pronunciation/english/overhead

                                          Это вы зачем приводите ссылку на английское произношение, если пишу на русском?

                            • +1
                              Ну не работает это так.

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


                              ORM же хороши при OLTP, когда у нас есть кучка объектов и мы с ними что-то делаем. Для операций на чтение к примеру я просто делаю SQL запросы и мэплю их прямо на DTO что дает мне возможность сохранять изоляцию и использовать преимущества платформы.

                              • 0
                                > работает, причем прекрасно работает.

                                Видимо вам повезло видеть какие-то супер успешные реализации проектов на ORM, буду признателен если обозначите что за системы это были, уровни нагрузки и объемы железа, и что за ORM там был. Мне, увы, попадались крайне неудачные. Из последнего $200000 в месяц на железо за 3000 одновременных пользователей.

                                > ORM же хороши при OLTP, когда у нас есть кучка объектов и мы с ними
                                > что-то делаем. Для операций на чтение к примеру я просто делаю SQL
                                > запросы и мэплю их прямо на DTO

                                Ну что-то типа такого звучит как приемлемый компромис. Особенно если вы потом не начинаете перемапливать DTO в BL структуры и обратно.
                                • 0
                                  Из последнего $200000 в месяц на железо за 3000 одновременных пользователей.

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


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


                                  К примеру мне дико нравится когда у меня в контексте операций записи есть сущности (нормальные, а не property bag с геттерами и сеттерами), нравится иметь возможность протестировать всю бизнес логику без базы данных. Это сильно упрощает сопровождение кода. С другой стороны, на чтение всяких списочков делать просто sql запрос с мэппингом хоть на динамические структуры (по сути сразу в json например). Это уже будет сильно упрощать имплементацию выборок, и соответственно удешевлять поддержку, лучше с точки зрения разделения ответственности и т.д.

                                  • 0
                                    > С другой стороны, на чтение всяких списочков делать просто sql запрос с
                                    > мэппингом хоть на динамические структуры (по сути сразу в json например).
                                    > Это уже будет сильно упрощать имплементацию выборок, и соответственно
                                    > удешевлять поддержку, лучше с точки зрения разделения ответственности и
                                    > т.д.

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

                                    Скажем так выборка тем же sql запросом нужных полей из которых все идет прямо в JSON минуя фазы десериализации из курсора БД в коллекцию объектов, потом фазу перемапливания либо с портянками типа объект1.свойство1 = объектИзБазы1.свойство1, либо того же самого через reflection (без разницы с помощью сторонних библиотек или напрямую) — мне видится гораздо более чистым способом работы с данными и хранилищем. Как с точки зрения производительности так и с точки зрения поддерживаемости.

                                    > Есть и такая проблема, что многие разработчики почему-то считают что
                                    > если есть ORM то все операции должны происходить через нее. Это уже
                                    > вопрос выбора инструментов.

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

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

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

                                    > К примеру мне дико нравится когда у меня в контексте операций записи
                                    > есть сущности (нормальные, а не property bag с геттерами и сеттерами)

                                    Что-нибудь типа repository? У любой БД сущности есть side-effects, как то открытие/закрытие транзакций, соединений, блокировки, и тд. Вот вы написали хорошую такую реализацию куска кода, который выбирает что-то на основании от n параметров вызывая ее один раз на сессию, а другой разработчик взял и позаимствовал вашу реализацию и засунул ее в цикл (а что код ведь уже оттестирован и не дублировать же функционал?). И вот у нас уже прекрасное взаимодействие с БД в виде миллиона запросов на каждый чих.
                                    • 0
                                      Но ведь ORM никак не помогает и зачастую мешает в этом. Одно дело когда вам нужно написать тест, который проверит что определенный запрос с нужными параметрами ушел к БД, а другое когда вам нужно проэмулировать всю обвязку ORM просто для того чтобы тест не свалился.

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

                                    • 0
                                      Скажем так выборка тем же sql запросом

                                      поддерживаю


                                      Вечный поиск панацеи.

                                      именно


                                      Но ведь ORM никак не помогает и зачастую мешает в этом.

                                      моя ORM (data mapper, unit of work, репозитории, persistence ignorance) помогает. Но вот для простых выборок мешает, потому в них я пропускаю операции мэппинга.


                                      У любой БД сущности есть side-effects, как то открытие/закрытие транзакций, соединений, блокировки, и тд.

                                      это не сайд эффекты сущностей, за это отвечают репозитории и unit-of-work.


                                      И вот у нас уже прекрасное взаимодействие с БД в виде миллиона запросов на каждый чих.

                                      зависит от задачи. Если задача — надо за 5 минут сделать bulk операцию — вполне себе норм, надо только не забывать делать чистый unit of work на каждую итерацию.


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

  • 0
    Применение TLD позволяет существенно сократить объем кода по сравнению с TDD.

    За счет чего простите? За счет ненаписанных тест кейсов? При TDD "лишние тесты" тоже стоит удалять. Да и есть еще много разных способов уменьшить количество кода для тест кейсов. Например — property based testing. Спасает как дополнение к триангуляции тестов.

    • 0

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

      • +1
        Придумывают тысячи тестов, которые ничего не делают.

        Ну так с TLD шансы на это выше чем с TDD. Нет?

        • 0

          Придумывать ненужные тесты к чему-то несуществующему проще чем к существующему. Существующее задает рамки.


          Не поймите меня неверно. Я считаю TDD полезной практикой. Мы ее применяли на паре проектов с неплохими результатами.

          • 0
            Придумывать ненужные тесты к чему-то несуществующему проще чем к существующему. Существующее задает рамки.

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

            • 0

              Возможно. Что-то в этом есть.

  • +1
    По поводу этого исследования о сравнении TDD и TDL отлично написал Robert C. Martin. Вот несколько выдержек, но советую прочитать.
    ...all the participants were trained in TDD. And then some of them were asked to do TLD in small chunks...In their effort to reduce the number of variables they inadvertently eliminated them all. They forced the participants doing TLD to use the TDD process of short cycles, and that forced the participants to drive the production code by thinking about tests first

    И отличный вывод
    What did it show? I think it showed that you can't interpret the conclusions of a study without reading the study.

    Еще раз рекомендую перейти по ссылке и прочитать оригинал.

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