Как применить Test-Driven Development на практике

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті.

Всем привет! Меня зовут Денис Оленин, я Tech Lead Back-End Team в компании AmoMedia, которая входит в экосистему бизнесов Genesis. Это моя вторая статья. В первой я рассказывал о «чистом коде» и его базовых принципах на примерах.

Идея статьи о test-driven development (TDD) родилась довольно давно. Мне часто приходится сталкиваться с непониманием, зачем нужны тесты и как их применить в конкретном случае.

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

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

Краткий ввод в теорию разработки через тестирование

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

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

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

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

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

Суть TDD-подхода — написать все необходимые тесты до того, как мы приступим к созданию кода. Для многих это прозвучит странно: как можно писать тесты для пустого места?

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

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

Разработка новых фич по TDD

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

Представим, что нам достался новый проект и мы все-таки решили внедрить такой подход.

С чего начать

  1. Сперва декомпозируем техническое задание на фичи и заводим их в любимый баг-трекер в виде тасок.
  2. Также нужно правильно распределить приоритеты тасок: не делаем фичу Б, если она не может работать без фичи А — сначала делаем фичу А.
  3. Далее пишем модульные тесты на первую фичу и проверяем, проходят ли они. Это своего рода тестирование самих тестов. Желательно написать сразу позитивный и негативный тест под фичу.
  4. Приступаем к написанию кода бизнес-логики. Код фичи готов, когда все тесты пройдены.
  5. Рефакторим код, если это нужно.
  6. Повторяем цикл с новой фичей.

Визуально этот процесс можно показать так:

Это достаточно простой подход, но иногда возникает ряд сложностей при написании самих тестов:

  • тестирование приватных методов;
  • тестирование методов, взаимодействующих с БД;
  • тестирование методов, взаимодействующих со сторонними сервисами;
  • предварительно подготовленные данные.

Тестирование приватных методов

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

public function invokeMethod(&$object, $methodName, array $parameters = array())
{
   $reflection = new \ReflectionClass(get_class($object));
   $method = $reflection->getMethod($methodName);
   $method->setAccessible(true);
   return $method->invokeArgs($object, $parameters);
}

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

Благодаря Closure можно получить доступ ко всем свойствам и методам класса. Такой подход имеет более понятный интерфейс.

class SomeClass {
   private function somePrivateMethod(): string {
       return "somePrivateData";
   }
}
$someObject = new SomeClass;
$someValue = Closure::bind(function() {
        return $this->somePrivateMethod();
   },
   $someObject,
   $someObject
)($someObject);

Либо начиная с PHP версии 7.4 можно этот же подход реализовать через стрелочную функцию:

$someValue = ((fn() => $this->somePrivateMethod())->bindTo($someObject, $someObject))();

Тестирование методов, взаимодействующих со сторонними сервисами

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

Когда ваша реализация взаимодействует со свойствами объекта, а не с его методом или поведением, можно использовать mock.

$mock = $this->createMock(PushTransport::class);
$mock->expects($this->once())->method('send');

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

Тестирование методов, взаимодействующих с БД

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

Это достаточно легковесная БД. Если вы не используете в приложении синтаксис, который sqlite не поддерживает, то работать с БД в тестовом окружении станет проще. Иногда вместо sqlite можно использовать mock-объекты как в предыдущем случае.

Предварительно подготовленные данные

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

final class AudienceFixtures extends Fixture
{
   use WithFaker;

   public function load(ObjectManager $manager)
   {
       $faker = $this->getFaker();

       $project = new Project;
       $project->setName($faker->name);
       $project->setHost($faker->domainName);
       $manager->persist($project);

       $audience = new Audience;
       $audience->setName($faker->name);
       $audience->setSubscribedAfter((new DateTime));
       $audience->addProject($project);
       $manager->persist($audience);

       $manager->flush();
   }
}

Почему стоит внедрить TDD на старте проекта

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

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

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

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

Список полезных ресурсов

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

2. Книга Кена Бэка «Экстремальное программирование: разработка через тестирование». Автор рассматривает применение TDD на примере разработки реального программного кода и тем самым демонстрирует простоту и мощь этой методики.

3. Также о важности тестов и о том, как их лучше организовать, хорошо описано в книге Роберта Мартина «Чистый код. Создание, анализ и рефакторинг».

👍НравитсяПонравилось5
В избранноеВ избранном7
LinkedIn

Лучшие комментарии пропустить

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

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

Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Все это хорошо, но в реальном мире все выглядит вот так: youtu.be/rR4n-0KYeKQ

Спасибо за статью!

По комментариям о TDD сразу видно уровень инженеров на доу:

TDD нельзя использовать в реальных проектах«"Покажите реализацию quicksort по TDD
сферическое тдд в вакууме к реальной жизни неприменимо
Вы о каком конкретно виде тестирования? Давайте разделим TDD, и другие виды тестов. Использование TDD в бизнес-проектах увеличивает time to market и не снижает риски, которые решают другие виды тестирования.
Расскажите подробнее, в каких случаях бюджет проекта был потрачен на тесты?

Это примерно как рассказывать что кататься на лыжах это бред. «Я попробовал кататься на лыжах — мне не понравилось», «Мой друг катался на лыжах и сломал ногу», «Сын соседа учился в лыжной школе и потратил кучу денег», «Все кто катаются на лыжах не понимают что на сноуборде лучше».

Если вы не умеете в TDD, то не надо всем доказывать что TDD не работает

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

Поэтому было бы неплохо в таких статьях вначале описывать, в каких ситуациях TDD работает :)

А что тут описывать? Есть ты умеешь писать по TDD и тебе это удобно — то все работает. Ну а если не умеешь и не хочешь учиться — то тут уже ничто не поможет.

На одних проектах удобно и работает, на других неудобно... Как-то так :)

Test-Driven Development (TDD) — обманка, собранная из нескольких безусловно правильных и важных принципов, с доведением их до абсурда и сбором в неработающую комбинацию. Любое реальное внедрение на что-то более существенное, чем одноразовый несмыслоёмкий код, отклоняется от его базовых принципов и тем самым нарушая стройность идеологии.

Начнём с банального: зачем нужны тесты и что они дают? Тесты не могут подтвердить корректность реализации юнита (буду употреблять это слово здесь, без ограничения по размеру и сложности юнита, но намекая на юнит-тесты). У любого юнита есть спецификация (техническое задание, ТЗ — будем считать эти термины эквивалентными): что он должен делать. Например, функция mul() умножения чисел, если процессор такого не умеет, определяется как «аргументы — 2 32-битовых числа со знаком в дополнительном коде, результат — младшие 32 бита произведения». Из этой спецификации строятся тестовые примеры для проверки корректности реализации функции. Но у нас размер пространства входных значений — 2**64. Как из них выбрать тот десяток-два, что будет проверять функцию? Есть два подхода:
1) Представление о возможных проблемах в реализации «чёрного ящика». Например, раз числа со знаком, надо проверить 3*3 варианта (отрицательное, 0, положительное) с каждой стороны. Раз результат усекается, каждую из 3*3 комбинаций надо проверить в двух вариантах — когда усечение сработало или нет.
2) Представление на основе выбранного алгоритма реализации, зная его потенциальные проблемы. Например, если случай со входными числами менее 32768 по модулю рассматривается отдельно, надо на него и не на него иметь разный комплекст тестов.

Заметим вот что: пусть у нас есть десяток-другой тестов. Что мешает написать функцию в варианте if(x==0&&y==1) { return 0; } и т.п. на все случаи в тестах? Формально она будет проходить все тесты. Практически же она будет бесполезна. TDD никак не рассматривает эту проблему, более того, он её усугубляет, рекомендуя решать любую задачу по частям.

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

Что тогда остаётся на долю тестов? Им остаётся следующий безусловно важный набор ролей:
1) Подтверждение корректности для какого-то внешнего (по отношению к кодерам) контролёра (например, заказчика, или собственного отдела качества).
2) Подтверждение корректности для самого автора кода. И тут критически важным является то, что человек и компьютер воспринимают один и тот же текст программы совершенно по-разному. Человек может пропустить множество тонкостей интерпретации, подразумевая что-то своё (классика — висячий else, auto вместо auto& в получении ссылки в C++). Человек может получить «замыленный глаз» и, читая код, видеть то, что хотел сказать, а не то, что сказал. Компьютер же не видит общей цели и алгоритма за деталями. Введение теста позволяет сократить затраты на поиск таких ошибок.
3) Подтверждение корректности исполняющей среды. Тесты типа «функция xfoobar2() в этой версии некорректна, надо использовать xfoobar_mimumo()» типичны в практике.

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

«Тесты — не для поиска багов, а для написания верифицируемого в контролируемых условиях кода.»

Что происходит в TDD? Проблемы этого подхода:
1. Отсутствие, как уже сказано, формального требования соответствия кода реализации исходной спецификации, а не тестам.
2. Полагание на то, что цикл «упало — реализовали — починилось» даст 1) реально работающий код, 2) работающий и в будущем код.
3. Test-first до осознания принципа реализации.

Пусть у вас задача реализовать, что foo(x) вызывает bar(x+10). Вы действуете по TDD. Вы пишете тест, который проверяет вызов bar() с любым аргументом. Тест упал. Вы пишете код foo(), который вызывает bar(x+010), а дело в языке, например, Go, где 010 — восьмеричная константа. Реально вызывается bar(x+8), но вам пофиг — вы тест удовлетворили. Где запрет на такое? Нет его.

Вывод: код тестов тоже должен проходить верификацию. В TDD этого нет.

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

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

Test-first имеет только один важный смысл — преодоление лени кодера, и второй — ограничение реализации тем, что требуется для конкретного теста. Обратной стороной является невозможность реализации сложных алгоритмов: они не укладываются в прокрустово ложе поэтапной реализации. В результате, любые подходы на test-first подходят только для одноразовой разработки несмыслоёмкого кода.

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

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

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

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

Поскольку полное покрытие тестами в общем случае невозможно, искусство разработки состоит в покрытии максимального количества случаев и возможных проблем минимальным набором тестов.

4. Должны применяться методы проверки актуальности, корректности и полноты набора тестов. Это включает в себя:
1) Автоматизированные средства подсчёта покрытия. (Следует помнить, что этого недостаточно в общем случае.)
2) Инверсионное тестирование — для проверки того, что конкретные тесты не являются всегда ложноположительными.
3) Мутационное тестирование.
4) Тестирование на свойствах.

Это устраняет обязательность test-first (которая недостаточна для уже написанного кода).

5. Test-first должно быть рекомендовано как средство выбора русла и ограничения объёма реализации (как сейчас в TDD), но в пределах его полезности.

Вот тогда получится честным путём технология не только для сайтиков-визиток.

У меня продолжает складываться впечатление, что единственная причина критики юнит — тестов и TDD в том, что девелоперы НЕ ПОНИМАЮТ что такое правильные юнит-тесты и путают их с другими видами тестирования. После опыта написания таких неправильных юнит — тестов у них складывается впечатление что юнит — тесты это сложно, долго и бесполезно.
В ответ я попробую описать свое, надеюсь простое, понимание юнит-тестов. Начну с пресловутых SOLID принципов.
Первый из них говорит о том, что каждый класс, компонент, метод должен быть атомарным — то есть нести только одну не делимую функцию. Для аналогии буду приводить механику: атомарные модули — это болт, гайка, шестерня, вал.
Второй принцип говорит что однажды написанный код не должен меняться! И это вполне логично: однажды изготовленный болт может использоваться пока не сломается. Если он не подходит — то проще взять другой, чем перетачивать старый.
Что бы убедиться что наш условный болт соответствует стандарту — его тестируют на ОТК. Аналогично и девелопер, когда написал код, должен его протестировать. Поскольку используется white box тестирование (мы знаем как устроен болт «внутри») — то, естественно, мы можем и подобрать набор тестов, которые покроют все требования (диаметр, шаг резьбы, шероховатость и т.д.) и проверят граничные условия (твердость, сопротивление на разрыв и т.д.).
На ОТК раньше сидели женщины с измерительными приборами — это ручное тестирование. Логично что девелоперу намного удобнее написать код юнит-теста, чем все сделать вручную.
На этом месте наc ждет первый подвох: многие девелоперы говорят что написать тест для кода — это в 2 раза дольше, чем написать только код. А некоторые даже считают что покрыть все нужные сценарии — займет больше времени на тесты, чем сам код, который тестируют.
О чем это говорит? Во-первых о том, что такие девелоперы не хотят тестировать свой код ВООБЩЕ! То есть даже если их не заставляют писать тесты — они никогда не проверяют все нужные сценарии вручную (ведь это скучно и снижает перфоманс). Отсюда первая польза от юнит-тестов: они гарантируют что девелопер протестировал свой код. Во-вторых это говорит о том, что девелопер, скорее всего, сильно не заморачивался декомпозицией и напедалил большие классы и методы, которые делают всю работу сразу. Естественно — потом у него не получается написать простой юнит-тест на код, который делает сложную работу. А вот и вторая польза от юнит-тестов и в особенности TDD: они заставляют девелопера думать как разбить монолитный код на «атомы» и написать простые модули с простыми тестами.
Но сложная машина состоит не только их болтов и гаек. Что если нам нужен модуль, который делает сложную работу — например коробка передач? Ведь ее то-же нужно тестировать! Тут мы вспоминаем Dependency Injection принцип. Да — коробка делает сложную работу, но мы-то тестируем ее не как black box, а как white box! В этом огромная разница: нам не нужны тесты, которые проверят все передачи и все режимы (это задача другого типа тестов). Обязанность коробки только в том, что бы шестерни и валы внутри были правильно расположены. Сама коробка — это то же атомарный компонент, который содержит просто места для установки других компонентов. И тестируем мы ее именно «пустой»! А что нужно что бы протестировать «пустую коробку»? Да просто проверить что она использует внутренние компоненты в правильном порядке!
Таким образом у нас в коде вообще не должно быть сложных, больших компонентов — есть только атомарные и составные, которые просто вызывают другие в правильном порядке. И тогда наши юнит-тесты даже для любых сложных (снаружи) модулей будут всегда простыми — потому что они тестируют не весь функционал (это делают другие тесты), а только небольшой код внутри! И в этом третья польза юнит-тестов: они заставляют девелопера писать слабо-связанные компоненты, использовать интерфейсы (и разделять из по 4 принципу), применять Dependency Injection, делать композицию и делегировать работу другим компонентам, вместо создания «монолита».
Чем TDD — то есть писать тесты ДО кода лучше чем писать их после? Девелопер сначала напишет простой тест или несколько простых тестов — при этом продумает интерфейсы. Когда он будет писать реализацию — он будет помнить о тестах и писать небольшие классы и методы, будет думать как подставить моки. А вот если он пишет код сначала — то велика вероятность что получится «монолит», который придется разбивать или лепить сложные тесты (отсюда и мнение что «с тестами дольше»).
Но написанием функционала дело не заканчивается. Завтра поменяются требования — и кому-то другому, а может и самому девелоперу придется менять функционал. Почему-то в этот момент большинство забывают про 2 принцип SOLID и меняют ранее написанный и протестированный код! Это перво-источник большинства багов. Потому что автор кода держал в голове полное понимание как оно должно работать — но теперь его нет. Юнит-тесты могут послужить документацией того, как автор хотел что бы его код работал. Но даже в этом случае — полного понимания не может быть, тем более что требования уже поменялись и все должно работать по-другому. Если новый автор начнет менят и чужой код и тесты — то скорее всего будет беда. Юнит-тесты, как и код, будучи написанны раз — НЕ должны меняться. Таким образом юнит-тесты защищают мой продуманный и протестированный код от какого-нибудь криворукого «исправлятеля багов копи-пастом»! Новый автор пускай для начала напишет свои, новые юнит-тесты, в которых отразит новое понимание как должно работать теперь. А дальше будет решать что из существующего использовать как есть — а какие компоненты заменить на свои. Это гарантирует что те места, которые он забыл поменять — не сломаются а будут работать по-старому.
Подводя итог: если ПРАВИЛЬНО использовать TDD то это не добавляет лишнего времени на разработку. НО: по сравнению с так же хорошо написанным кодом, протестированным вручную! Если педалить абы запустилось — то конечно будет намного быстрее. Только все это пойдет в счет технического долга, который рано или поздно напомнит о себе в самый неподходящий момент.

У меня продолжает складываться впечатление, что единственная причина критики юнит — тестов и TDD в том, что девелоперы НЕ ПОНИМАЮТ что такое правильные юнит-тесты и путают их с другими видами тестирования. После опыта написания таких неправильных юнит — тестов у них складывается впечатление что юнит — тесты это сложно, долго и бесполезно.

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

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

В частности, это приводит к таким вещам, как:

Второй принцип говорит что однажды написанный код не должен меняться! И это вполне логично

Что для одного «логично», для другого просто недопустимо. Я вот даже не пытаюсь представить, какие слова мне бы сказали на предложение выбросить весь уровень диалога и переписать с нуля из-за введения 100rel или preconditions. Да, меняется код, меняется и его интерфейс — под новые требования. И это неизбежно.

Отсюда первая польза от юнит-тестов: они гарантируют что девелопер протестировал свой код.

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

А вот и вторая польза от юнит-тестов и в особенности TDD: они заставляют девелопера думать как разбить монолитный код на «атомы» и написать простые модули с простыми тестами.

Да, это так для юнит-тестирования (чем бы «юнит» тут ни был). TDD тут ни при чём.

Чем TDD — то есть писать тесты ДО кода лучше чем писать их после? Девелопер сначала напишет простой тест или несколько простых тестов — при этом продумает интерфейсы. Когда он будет писать реализацию — он будет помнить о тестах и писать небольшие классы и методы, будет думать как подставить моки. А вот если он пишет код сначала — то велика вероятность что получится «монолит», который придется разбивать или лепить сложные тесты (отсюда и мнение что «с тестами дольше»).

И это хорошо для случая, когда интерфейсы можно продумать заранее до написания кода. «Хорошо живёте», сказал бы я. Ну или архитекторы выполнили уже 99% работы.
Вот потому я про «миры» и вспоминаю: у меня обычно чтобы интерфейсы продумывались полностью заранее — это сильно меньше половины случаев; ну а их изменение под новые требования через год-два (без возможности выбросить код!) — считаем, 99% компонент.

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

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

«Могут» послужить. А могут и не послужить.
Пусть есть проверки на то, что f(0,0) = 0; f(2,2) = 4; — можно по этому узнать, это было сложение или умножение?

Юнит-тесты, как и код, будучи написанны раз — НЕ должны меняться.

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

Подводя итог: если ПРАВИЛЬНО использовать TDD то это не добавляет лишнего времени на разработку.

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

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

Именно! Если код УЖЕ написал монолитно, то покрывать его тестами — самое бесполезное занятие.
Суть правильного подхода — у вас НЕ должно быть сложных компонент. Если код слишком сложный — его надо разбивать и инкапсулировать. Вся цель правильных подходов — это борьба со сложностью. Человек не способен держать в голове слишком много — но при этом любого слона можно прожевать по-кусочкам.
Если уже накоплен технический долг («длительно разрабатывающиеся продукты») — то нужно не тестами покрывать, а «выплачивать» его — писать новую версию со слабой связностью.
Я большую часть времени работал именно на легаси проэктах и именно поэтому постоянно думал как надо работать что бы новый проект через год не превращался в такую помойку, как обычно. И решил что способ только один: с самого начала внедрить правильные практики и не отступать от них.

Суть правильного подхода — у вас НЕ должно быть сложных компонент.

Ну вот мы и пришли закономерно к «мышки, станьте ёжиками».
Мне со всем этим одно интересно: попробовать как-нибудь убедить начальство оплатить хотя бы месяц того, кто говорит на dou/аналогах такие вещи, и скормить парочку наших компонент попроще :)) основное блюдо я пока и не пытаюсь предлагать :))

скормить парочку наших компонент попроще

Если они у вас уже написаны — то о чем говорить? Если вы хотите проверить можно ли такие-же компоненты внутри декомпозировать на простые кусочки — то скорее всего да. Хотя тут есть нюанс: если вы УЖЕ используете какой-то чужой фреймвок для компонентов, то очевидно что вам придется подстраиваться под чужую архитектуру. Выбор библиотек и фреймвоков это отдельный разговор.

Если они у вас уже написаны — то о чем говорить?

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

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

«Скорее всего»... ни о чём.

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

Никак! У меня достаточно большой опыт работы с легаси кодом что бы понимать что в большинстве случаев переписать чужой код будет быстрее и проще, чем исправить. Во-первых я не зря постоянно пишу что раз написанный код можно расширять, но НЕЛЬЗЯ менять. Во-вторых работая с чужим легаси кодом ты берешь на себя весь технический долг, который накопился. В-третьих технологи, использованные в старом коде — скорее всего уже устарели и есть новые, которые позволяют решить ту-же задачу намного быстрее.
На закуску анекдот в тему:

Мать говорит дочери:
— Запомни, доченька, выбирать мужа нужно с умом. Вот твой папа может починить всё что угодно: машину, электричество, сантехнику. И мебель, если поломается, тоже починит... Так вот, если ты найдёшь себе такого мужа, то у тебя никогда не будет ничего нового!
В-третьих технологи, использованные в старом коде — скорее всего уже устарели и есть новые, которые позволяют решить ту-же задачу намного быстрее.

Ви використовуєте bleeding edge CS?
Чи у вас проекти на перфокартах?

что в большинстве случаев переписать чужой код будет быстрее и проще, чем исправить

А есть какие-то реальные доказательства этому, а не ссылка на непроверяемый опыт?
Серьёзно, я тут не принимаю решение, но готов агитировать перед начальством. Но перед стартом хочу получить для себя более увесистые аргументы, чем «мамой клянус».

раз написанный код можно расширять, но НЕЛЬЗЯ менять.

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

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

А вы предлагаете поступать по принципу — не брать его?
Если бы я такое требовал, меня бы просто не взяли на работу :)

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

Считайте, что тут такого нет.

Так что? Надеюсь на конструктивный ответ.

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

ты ж сам понимаешь что выступаешь за «забивание шурупов камнем»?

Безусловно нет, и никакого риторического «ты ж сам понимаешь?» тут быть не может.

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

Безусловно да.

А бороться против общепризнанных подходов, практик и т.п. как минимум странно

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

А есть какие-то реальные доказательства этому, а не ссылка на непроверяемый опыт?

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

апример, надо добавить один атрибут в параметры авторизации.

Хороший пример — слышали про версионирование интерфейсов? Добавите параметр — а знаете ли вы все места где эта авторизация используется? Обычно добавляют новую версию с новым параметром что бы работало и старое и новое.

Так бы я добавил по параметру в пару функций, а теперь что?

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

А вы предлагаете поступать по принципу — не брать его?
Если бы я такое требовал, меня бы просто не взяли на работу :)

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

Так что? Надеюсь на конструктивный ответ.

Конструктивный ответ, который я нашел для себя: единственный способ годами писать хороший, поддерживаемый и расширяемый проект — это с самого начала применять правильные практики! Даже когда мы только начинаем и у нас минимум функционала — сразу делать декомпозицию, TDD, придерживаться SOLID. Пускай это овер-инжениринг для первой версии — но внедрить эти практики ПОСЛЕ уже практически не реально.

На практике обычно никто не позволяет переписывать код — потому что учитывают только сиюминутную выгоду: забить костыль всегда будет проще.

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

Хороший пример — слышали про версионирование интерфейсов? Добавите параметр — а знаете ли вы все места где эта авторизация используется?

Да, знаю. Это всё внутри моего же кода и легко отслеживается. А вы стали додумывать какие-то доступные извне API? А с чего вы решили, что я говорю про такие API?

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

:))

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

Так он же всё равно уже будет чужим кодом и легаси. Зачем стараться-то?

Пускай это овер-инжениринг для первой версии — но внедрить эти практики ПОСЛЕ уже практически не реально.

Однако. Я не буду пытаться вспоминать, сколько раз я сам лично такое делал, и сколько книг, начиная с классики Feathersʼа, рассказывают, как это делать... ну не реально так не реально, больше вопросов нет ;))

«Жаль, что мы так и не заслушали начальника транспортного цеха» (tm).

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

Код, написанный для клиента пошарить не могу по понятным причинам.
Код, написанный для себя шарить не хочу по 2 причинам:
— Это отдельные небольшие эксперименты «для себя», а не рабочий продукт. Поэтому как доказательство не подходят.
— Пошарив свой код анонимно я потом не смогу его больше использовать для работы или для пет-проектов.
Когда-нибудь мое галерное рабство закончится — и возможно тогда у меня будет время сделать несколько шаблонов и примеров проектов на дотнете по тем подходам, которые я считаю правильными.

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

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

Типичная конференция: -«Наш %вельюнэйм% повысился благодаря %методнейм%, от чего мы все счастливы!»
-«Ооокей, покажите код.»
-«Не можем, у нас НДА. Но вот прекрасный пример использования нашего чудесного метода на задаче сложения 2+2»

посмотри 10 летней давности, когда программисты еще программировать умели

То есть ни ваш единомышленник, ни вы уже не умеете программировать? Oook...;)

У тебя есть проблемы с логическим мышлением. Попробуй порешать задачки на логику, что-то вроде: «розы — цветы, некоторые цветы быстро вянут, значит ли это что некоторые розы быстро вянут?». Бо я пишу что раньше выступающие были более подкованными в том, что рассказывают, а ты каким-то образом впутываешь в это меня, хотя я не выступаю на конференциях вовсе =\ пытаться прикрыться сарказмом или иронией тоже не стоит, бо придется тогда ещё и в литературе пробелы закрывать

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

Цитирую тебя:

посмотри 10 летней давности, когда программисты еще программировать умели

Шаг 1: из этого следует, что сейчас — не умеют. Или ты хочешь как-то иначе объяснить свою фразу? Внимательно слушаю.

Шаг 2: раз «программисты не умеют» без уточнения, какие, то это квантор общности.

Шаг 3: поскольку множество всего невозможно, в любом кванторе общности подразумевается какое-то «универсальное множество» (тут — известных тебе программистов). Безусловно, оно включает тебя (или ты не программист?)

Шаг 4: поскольку ты защищаешь коллегу Beaver Green, то ты, наверное, включаешь его в рассматриваемое множество программистов.

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

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

Попробуй порешать задачки на логику, что-то вроде: «розы — цветы, некоторые цветы быстро вянут, значит ли это что некоторые розы быстро вянут?».

Зачем, если тут есть более интересный материал? ;)

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

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

ну и душнила...

сам первый начал :)

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

Давайте я разбавлю ваше кидание какашками.

Я ещё даже не начинал :)

то если ему дать для начала таск «почитай тз и напиши список тестов, которыми ты бы хотел покрыть этот код», то после трех-четырех итераций человеку уже проще въехать в бизнес процесс

Хм, это прикольный подход, хоть и очень частный случай.
В моей типовой обстановке задача «не сломать» решается через peer review, автотесты в CI, и до прода ещё нужно очень постараться добраться... поэтому мы не сильно боимся коммитов по сути от новичков. Но сама тренировка заранее подумать «а как я буду это проверять?» действительно очень дисциплинирует. Опытный программист сам по себе держит в голове ответ на этот вопрос (хотя бы приблизительный и частичный), новичка же надо этому учить. Главное тут — не перестараться. Непреодолимая фиксация тестов до написания кода и кода до использования, как у Beaver Green — образец такого перегибания палки, как и TDD.

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

Пусть неделю сочиняет названия тест кейсов — ничего в этом такого нет, это дешевле, чем потом чинить поломанное.

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

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

Не, ну я с неделей ясное дело загнул. Дается итерация 15-30 минут на создание описания тесткейсов. И неограниченное количество таких итераций с peer review, c возможным привлечением к обсуждению всех заинтересованных личностей. У нас таки-да плоская структура, и поймать пробегающего рядом СТО и допросить не считается чем-то позорным.
Затем действительно, идут автотесты до тех пор, пока все не будут проходить. У меня проект маленький, всего тыщи полторы их, но у коллег по несколько сотен тыщ.
Потом снова peer review, и лишь затем коммит (с повторным прохождением всех автотестов). Да, это долго. Но лучше один раз сделать хорошо и неспеша.

Наприклад, Concourse.
Більшість коду написана з TDD.
github.com/concourse/concourse

Спасибо! Я вижу более 700 открытых ишьюсов из них 182 с пометкой «bug», при этом 1100 бага уже закрыты. Получается, если бы не применялся TDD, то эти цифры можно было бы смело умножать на 3? Или в чем лютый вин TDD?

Швидкість розробки, легкість онбоардінгу та зменшення регрессій. Я бачу тільки 35(github.com/...​issues?q=label:regression) з яких більшість UI.

Нет, не похоже это на UI github.com/...​urse/concourse/labels/bug И судя по описанию, косяки в проекте весьма неприятные.

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

Швидкість розвобки потрібна для проекту, що буде йти більше одного року. Бо без тестів рефакторити набагато важче.

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

Наприклад, Concourse.
Більшість коду написана з TDD.

А какие-нибудь подтверждения этому есть? Или как оценивать что тут «большая часть»?
А то я вытянул код и смотрю историю — вот например 62114ba6 — правка функциональности, нет правки тестов. ef7d6866 — «add tests» — а к чему эти тесты, они же должны создавать какой-то код вслед? 32b214db — а где тесты на все изменения? 4001add8 — где тесты?
Или надо было с самого раннего смотреть? Так и там десяток не-инфраструктурных коммитов посмотрел — то же самое.
... вот так и распространяют мифы ...

Так документального підтведження не знайду, проте я знаю людей, які працюють (чи працювали) над проектом і знаю, що там було TDD.

Зараз подивився. Бачу, що core-team свої комміти помічає з B: на початку і в цих коммітах є частіше тести. Бачу, що змінилося дещо.
Але ящо піти в історії назад роки на два версія 5.0.0 і раніше, то видно, що тестів, що додані пізніш не було.

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

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

І як в пітоні тоді писати без остраху код при відсутності перевірок на рівні компайлера?

Не могу согласиться с наличием страхов. Если в вашем проекте это важная часть, то конкретно для этих задач можно использовать проверку типов или, с недавних пор, синтаксис типизации в Python’е.

Як перевірка типів допоможе виявити помилку в коді, якщо вона прихована в блоці, що не часто запускається?

Як те саме зробити в командній розробці?

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

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

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

Ні
Мені потрібно знати, що те що я написав не поламало функціонал, що написаний до того

__

ценность тестирования в реальных задачах не находится на начальном этапе написания

А як тоді на «середеньому етапі» виділити час хоча б на фукнкціональні тести на ті фічі що зара пигуться?
І на тести які покривають основний функціонал?
Фічі на «середньому етапі» потрібні ще швидше

__
Як робити рефакторінг коду який написаний як попало підчас фази початкового росту?

__

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

Тобто ви покладаєтесь на лінтери і статичний аналіз?

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

Ну тобто на ранніх етапах, коли декілька людей одночасно поклали проект на пару днів, вважається нормальною ситуацією

Mkay

Мені потрібно знати, що те що я написав не поламало функціонал, що написаний до того

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

Якщо e2e проблеми не зловить, то так і буде поки не дійдуть туди руки

Ну ось.
А з інверсними тестами я ловив деякі дуже гарно сховані проблеми задовго до того, як вони вибухали навіть у QA...

Я б сказав, що в пітоні взагалі нічого не допоможе, тому що можна на ходу підмінити все, включаючи клас обʼєкта або його методи поокремно :)

Але, якщо ми вважаємо, що таке не трапляється, то необхідність тестування ніяк не означає необхідність test-first підходу з засад TDD.

Необхідність випливає з zen пітона
Simple is better than complex.
Complex is better than complicated.

Залишилось довести, що test-first це хоча б одне з simple або (minor) complex...

Спасибо, Михаил. Расскажите подробнее, в каких случаях бюджет проекта был потрачен на тесты?

Вы о каком конкретно виде тестирования? Давайте разделим TDD, и другие виды тестов. Использование TDD в бизнес-проектах увеличивает time to market и не снижает риски, которые решают другие виды тестирования. Но увеличивают риски не выхода на рынок.

Использование TDD в бизнес-проектах увеличивает time to market и не снижает риски

І звісно це твердження має під собою якісь статистичні дані?

виды тестирования

Які саме види тестування не включені в тдд?

Які саме види тестування не включені в тдд?

Регресійне тестування. Після того, як за TDD тест зазеленів, він нікого не цікавить. Ніщо не заважає появі фактору, що викликає хибнопозитивний результат тесту.

Мутаційне тестування.

Тестування на властивостях.

Всі види інтеграційних тестувань.

Які саме види тестування не включені в тдд?

Всі види інтеграційних тестувань.

facepalm

Это безусловно. Если речь идёт об агрессивном занятии рынка в стартапе, то TDD увеличит time to market и не даст benefit.
Но не всегда идёт разработка для стартапа. Привожу пример: компания выиграла тендер, уже имеет опыт и репутацию, и в сроки и стоимость разработки уже заложено автоматическое тестирование, позволяющее снизить maintenance cost в длительной перспективе

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

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

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

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

Я соглашусь, но лишь отчасти. На очень больших и протяженных во времени проектах сложно держать в голове всю картину. Часто меняются люди ещё до того, как поменялся подход/эксперт в предметной области/нужное подчеркнуть. Упавший тест — как минимум повод задуматься «не делаю ли я что-нибудь не так?»

TDD это вредная и дорогая методика

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

TDD гарантує, що елементи та блоки з яких зібрано телевізор відповідають специфікаціям ще до того як з них зібрали вироб.

TDD нічого такого не гарантує.

1. Ніякий тест не може такого гарантувати. Гарантію може дати тільки верифікація при наявній гарантії коректности виконавчого середовища.

2. TDD з його байдужістю до долі тестів, які вже задоволені, не сприяє контролю якості розробки у багато етапів.

якщо тести написанні адекватно, а не як завжди

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

Теоретики программирования очень сильно ошибались — в 2021 году рулят практики программирования! Спека, документация, тестирование только увеличивают time to market: сейчас продавать программы начинают ДО того, как их начали писать. Поэтому некогда думать: ***-***к и в продакшин!

Но так и есть. Сам о**ел, когда узнал.

Глупости. То что у вас нет опыта с TDD не значит, что он бесполезный. Это настолько просто как, например, сделать HTTP запрос из тесткейса и проверить результ его выполнения до написания кода. Это не какое-то вуду. Что здесь «дорогого», простите? Я уже несколько лет применяю TDD на реальных проектах. Правда, я в основном бэкендами занимаюсь и не знаю как бы я подошел к TDD при написании интерфейсов, например, но уже больше вопрос инструментария и индивидуального опыта.

Anyway, вот неплохое старое видео, которое объяснят методолию на пальцах.

Anyway, вот неплохое старое видео, которое объяснят методолию на пальцах.

Роскошно — открывается видео в окошке ~150*100, увеличить размер или открыть в отдельной вкладке нельзя, просто нет кнопок для этого (полноэкранный режим заведомо не подходит). Считаю это достаточной оценкой всего подхода и конкретного источника.

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

«Fear of the unknown and the other is the root of almost all hate»

Если бы хотели, посмотрели.

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

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

Вот на этом пункте у меня возникает диссонанс. Если мы тестируем фичу на основе требований — то это уже не юнит, а функциональные тесты!
Если мы правильно пишем код, то каждый метод у нас не больше 50 строк, а каждый класс не более 200 — 300. Таким образом никакой класс сам по себе не реализует не только фичу, но даже бизнес логику. Весь смысл небольших классов и простых юнит-тестов в том, что написанный однажды код и тест к нему никогда не меняются!
Так же, на мой взгляд нет смысла писать тесты, которые изначально падают с NotImplementedException. В чем польза что мы напишем такой тест до кода?
Я считаю что начинать нужно всегда не с теста — а с интерфейса!
Тогда вторым шагом действительно можно написать тест для интерфейса — и это буде иметь глубокий смысл. Потому что интерфейс — это контракт декларации, а вот юнит тест — это контракт поведения. Интерфейс может задать типы данных, но не диапазон валидных значений, и не порядок вызова, и не ожидаемые исключения — все это как раз легко понять из юнит-тестов. Третьим шагом можно действительно сделать минимальную имплементацию, которая удовлетворяет тестам. По сути это и будет мокап, который может пригодится в других тестах или может быть полезен как демо публичного API. Это позволит понять насколько полный и насколько удобный наш интерфейс. Возможно уже на этом шаге имеет смысл что-то зарефакторить. Например заменить параметры объектом или вместо одного метода, который возвращает много данных сделать несколько для разных кусочков.
Четвертых шаг это уже реальная имплементация интерфейса. Только на этом этапе мы узнаем какие зависимости нужны нашему новому классу и какие «побочные эффекты» (обращение к базе, измениние стейта) имеют его методы.
Пятым шагом мы возвращаемся к тесту и здесь уже создаем и проверяем моки для всех зависимостей.

Все правильно, только по TDD интерфейс тоже проектируется в процессе написания теста. Например,
1. мы пишем тест, который вызывает несуществующий метод.
2. Тест не компилируется.
3. Значит переключаемся на написания кода. Пишем объявление несуществующей функции, но функция будет пока с пустым телом (заглушка).
4. Тест компилируется, таким образом интерфейс доработан.
Далее, если тесты проходят, вносим следующее изменение в тесты, если не проходят, продолжаем дорабатывать код.

Все правильно, только по TDD интерфейс тоже проектируется в процессе написания теста

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

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

Следуя логике ТДД
ты хочешь

переміститись з точки А в точку Б, або розвинути специфічну групу м’язів в нижній частині тіла, або зайнятись спортом

І йдучи від задачі ви робите вибір

У вас же задача не написати x LOC, а вирішити проблему клієнта (внутришнього чи зовнішнього)

Ахінея якась.

Допустим, ты хочешь купить велосипед.

Ти прочитав яка задача стоїть? А ти шо пишеш? )

Яка задача в велосипеда?

Велосипед — це інструмент чи об’єкт, він не задача (якщо не йдеться про його конструювання)

Якщо потрібно купити велосипед і невідома задача для якої він купується, то найкращий вихід його не купувати взагалі

Да, если нужен велосипед для езды из точки А в Б нужно его протестить машиной. Возможно на маршруте есть места, где езда на велосипеде запрещена.
Вообще так себе пример.
Из рабочего, когда впервые пришлось столкнуться с ТДД.
Делал метод, который на вход принимал строку с адресом проживания, на выходе возвращал заполненную структуру со стандартными полями: страна, индекс, город, адрес, дом, квартира.
В итоге вариантов написания адреса было насчитано 35 штук, это всё покрывалось 11 регэкспами. Пока добавлял один формат, ломалось что-то в другом.
Тогда я для каждого формата, который находили QA, добавлял тест и добивался его работоспособности.

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

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

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

Это нормальный сценарий, где тдд как раз хорошо работает.

Ні, бо у вас навіть задача нормально не поставлена

ТДД пропонує думати про великі речі помаленьку і не ускладнювати завчасно

Але 23-річним архітекторам це звісно не буде подобатись
xD

ТДД пропонує думати про великі речі помаленьку і не ускладнювати завчасно

Покажите реализацию quicksort по TDD.
Пошагово, с соблюдением всех правил методики.

Вы сами-то их читали? Сборище петросянов, которые нарушают всё что можно ради картинки.
(вот уж точно facepalm)

Іноді рішення потребує гнучкості подходу, іноді просто треба менш прискіпливого суддю
¯\_(ツ)_/¯

Іноді рішення потребує гнучкості подходу,

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

іноді просто треба менш прискіпливого суддю

Так не я ж ці аксіоми вводив... ;)

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

Yup
те саме з іншими аббревіатурами

Так не я ж ці аксіоми вводив... ;)

Але чомусь вирішили, що можете судити
dou.ua/...​rums/topic/33909/#2165045

Але чомусь вирішили, що можете судити

Кожен може, якщо чесно це робити.
Але захисники по більности обманюють ;(

А неосілятори кажуть, що воно не працює, бо в них не вийшло
Ось така «чесність»

Пока добавлял один формат, ломалось что-то в другом.

То есть тесты совсем не использовались?

Тогда я для каждого формата, который находили QA, добавлял тест и добивался его работоспособности.

И где тут преимущество test-first?

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

Ты должен протестировать маршрут от А до Б

Це E2E тест і не має ніякого відношення до TDD.

В веб розробці мануали і туторіали часто починають тдд з e2e / functional test

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

Это утверждение иллюстрирует некорректность типового представления про деление на юнит- и функциональные тесты.

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

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

Это невозможно. Что-то будет неизбежно меняться.

Я считаю что начинать нужно всегда не с теста — а с интерфейса!

Ни один из этих вариантов не является панацеей.

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

А зачем этой реализации тесты?

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

Сперва декомпозируем техническое задание на фичи и заводим их в любимый баг-трекер в виде тасок.
Также нужно правильно распределить приоритеты тасок: не делаем фичу Б, если она не может работать без фичи А — сначала делаем фичу А.
Далее пишем модульные тесты на первую фичу и проверяем, проходят ли они. Это своего рода тестирование самих тестов. Желательно написать сразу позитивный и негативный тест под фичу.
Приступаем к написанию кода бизнес-логики. Код фичи готов, когда все тесты пройдены.
Рефакторим код, если это нужно.
Повторяем цикл с новой фичей.

Я не согласен с таким пониманием основного цикла TDD. Вот как по моему мнению его понимал Кент Бек (ru.wikipedia.org/wiki/Бек,_Кент) и понимаю и выполняю его я и рекомендую всем понимать его именно так:
1. Убедится что все тесты проходят
2. Написать минимальный тест который падает или внести минимальное изменение в существующий тест, чтобы он упал.
3. Максимально быстро, не думая о качестве кода, внести минимальное изменение в код, такое чтобы тест перестал падать, причем добавлять только код, необходимый для того чтобы тест прошел и не добавлять код, который «все равно понадобится»
4. Убедится что все тесты проходят
5. Провести рефакторинг
6 Вернуться к началу цикла

Весь цикл в идеале должен занимать две минуты.

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

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

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

— Клас повинен приховувати деталі реалізації.
— Тест не повинен тестувати деталі реалізації.
— Тест повинен тестувати деталі бізнес логіки.
Передаю особливий привіт дядечку Бобу

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

Почему?

При рефакторинге могут быть удалены или переименованы приватные методы. И от этого не должны ломаться тесты.

При рефакторинге могут быть удалены или переименованы публичные методы. От этого тоже не должны ломаться тесты?
Если что-то вообще решили тестировать, то публичность или приватность интерфейса — дело определения границы доступа, которое может иметь множество значений, а не только базовые 3. Вон в Java/C# есть package-internal.

Я думал всех уже попустило с хайпом по тдд.
Ан нет. Ну штож, тут только ждать.

Почему стоит внедрить TDD на старте проекта

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

А где же, простите, попустило? В каких отраслях?

Из разговоров и комментариев в целом мне показалось, что уже года два существует публичный, ничем декларативно не закрепленный, консенсус о том, что идея ТДД изжила свое, оставшись забавной, но нереализуемой концепцией. Или ограниченно реализуемой на проекте с уровнем сложности «примитив».

У меня противоположное мнение по этому вопросу

У тебя есть тест для этого мнения?

Ок, допустим разрабатывается драйвер Linux. Сколько времени, по твоему, надо будет разрабатывать mock объекты, которые бы эмулировали поведение ядра (выделение памяти, вытеснение страничек из памяти) и железа (работа с PCIe например)?

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

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

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

Потому что ты получаешь kernel panic если из IRQ попробуешь обратиться к страничке, которая вытеснена из памяти.

То есть, mock-объект, это просто заглушки, которые возвращают константы.

Железо IRQ пуляет. Которое может прийти в любом ядре.

Понимаешь, если бы это давало профит, то это бы и использовалось. Но TDD в разработке драйверов я не видел от слова «совсем». Большинство ошибок это нетривиальное взаидодействие межлу компонентами, зато большой упор на функциональные тесты — можно протестировать работу всего устройства целиком.

Ну и просто конкретный пример. Вот есть библиотека libwebsockets. Допустим мы хотим написать unit-тест на функцию __lws_close_free_wsi. Мне страшно представить, какой нужен mock для того, чтобы воссоздать контекст для её выполнения :)

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

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

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

Если бы моей целью было покрыть эту функцию тестами (я не говорю что это надо делать), то первый тест, который бы я написал для функции __lws_close_free_wsi_final проверял бы что ей можно передать NULL и она не крешится.
Следующий тест проверял бы, что она вызывает wsi->a.context->pt[(int)wsi->tsi]; задав свои заглушки в тесте для a.context и tsi (эти заглушки и есть мок объект конкретно для этого теста). Больше этот тест ничего бы не проверял. И так далее, отдельный тест со своим мок объектом для каждого конкретного теста.

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

Слабкі місця TDD — UI, networking, concurrency. Тут вже доведеться на функціональні тести покладатися.

Но TDD в разработке драйверов я не видел от слова «совсем».

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

Проблема в том, что Linux не может себе это позволить...

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

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

Как оно было бы пригодно для тестирования:
1) Функция режется на десяток функций, помеченных inline, каждая из которых делает свою часть работы — типа.
2) Если C, то каждая из таких функций проверяется отдельно, но затем для теста полной функции (если он вообще нужен) они препроцессором подменяются на моки. То же можно и для C++, хотя можно переопределить виртуальные функции в тестовом подклассе.

Железо IRQ пуляет. Которое может прийти в любом ядре.

Если убрать неуместный здесь TDD и необязательный для задачи test-first, остаётся вопрос — можно ли протестировать такую ситуацию?
jIMHO, можно. Будет две вариации тестов — с прерыванием на этом же ядре и на соседнем ядре.

... другой вопрос, что стандартные юнит-тесты фиг покажут проблему типа «между записью X и записью Y надо было вставить write barrier», тут уже все записи надо мокать хитрой прослойкой, которая только и жаждет найти отсутствие положенного упорядочения...

Собственно говоря поэтому, ИМХО, «как применять TDD» это второстепенный вопрос. Главный же «когда применять TDD».

Если обратиться к первоисточнику, то Кент Бек выдвинул концепцию «экстремального программирования», куда входило сразу несколько техник/требований (парное программирование, постоянный рефакторинг, игра в планирование, заказчик всегдя рядом, ...). Также надо отметить, что Кент большую часть проектов писал на Java, а это на тот момент Simula-like ООП. Кроме того, он был приверженцем небольших классов и методов (в районе 10 строк), в таком себе стиле языка Smalltalk, где сам синтаксис не очень то благоворит к созданию длинных методов.

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

Что же нам дают Unit-тесты, раз их до сих пор ещё используют? Всё-таки, как ни крути, это лишний код, который надо поддерживать, и он должен давать некоторые бонусы, чтобы отбить затраты на его написание.

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

Один из важных аспектов это частый рефакторинг. Как ни крути, даже с крутой IDE, программист часто допускает глупые ошибки. И тут Unit-тесты, как по мне, полезны в диагностике. Причём особенно полезны в динамических языках типа Python, PHP (не случайно, автор позиционирует себя как PHP разработчик), потому что тут Unit-тесты могут словить часть проблем, которые с статически-компилируемых языках ловит компилятор.

Если брать оригинальную Java, то тут надо отметить, что построение ООП иерархии в Simula-like языках программирование достаточно сложная задача. И эта проблема усиливается в случае небольших классов. Конечно, я не спорю о том, что такое представление очень привлекательно для нашего мозга. Но часто решения по иерархии классов оказываются не идеальными в свете новых требований/сценариев. Я много раз был видел неудачные примеры иерархий, особенно там где они выстраивались не в рамках существующего фреймвёрка, а с нуля.

Вообще, сама идея реализовывать копределы через полиморфизм мне сегодня не сильно по душе. А Smalltalk-like ООП больше пугает, потому что возникает знакомая всем Smalltalk программистам проблема, когда нужное тебе действие делается где-то, но непонятно где. И ты прыгаешь по этим классам стремясь найти нужные тебе три строчки кода во всём этом спагетти из классов. Лично у меня самые кошмарные воспоминания были о коде LLVM, где спагетти-ООП смешано с шаблонами и особенностями C++. Ну и появление Go, где наследование заменено композицией, Rust и т. п. показывает, что сама идея наследования в духе Simula не беспорна. Ну это немного оффтопик, но в данном контексте он вылазит часто, что апологеты TDD, глядя конкретный пример, часто говорят: «Говнокод, надо срочно всё рефакторить!» По факту, если команде более комфотно писать средние и длинные методы (100+ строк), более процедурный стиль, рефактиринга не сильно много и т. п., то я бы не рекомендовал Unit-тесты.

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

Осталось только найти реальный модуль ядра, написанный с применением TDD. Хотя бы одну штуку :)

Я в этом не специалист, но придумал как найти. Надо зайти в гугл, набрать linux kernel unit test и кликнуть на первом же результате поиска. www.kernel.org/...​ev-tools/kunit/index.html

Я в этом не специалист

Это видно — на запрос про TDD даёте ответ про unit testing.

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

В случае, когда железо еще не изготовлено

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

У меня противоположное мнение

Это нормально.

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

TDD — как сова. Она хороша для ловли крыс, но не стоит натягивать её на глобус.
Скажем так: насколько просто внести изменения в уже согласованный набор тестов, когда в процессе реализации обнаруживаются косяки прямо таки в ТЗ? Когда те люди, которые НЕ БУДУТ ЗАКАНЧИВАТЬ проект, не будут за него отвечать — взяли на себя птичьи права вносить и/или фиксировать изменения, что-то разрешать или запрещать, как решить формально неразрешимую проблему?

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

Возможно здесь вы имеете ввиду интеграционные тесты, а не юнит тесты.
ru.wikipedia.org/...​теграционное_тестирование
ru.wikipedia.org/...​ki/Модульное_тестирование

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

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

1) «обычно» ;)
2) «редко» это сколько? раз в месяц? год?
Вероятно, если раз в год, то для текущей команды точно «трава не расти» — её уже перебросят на другой проект.

У меня два ключевых компонента текущего продукта развиваются с 2003 года. По его опыту могу сказать — изменения затронули ~99% кода.

функционал, который ещё неизвестно как будет работать

Ну так в процесі написання тесту ви і визначаєте, як він буде працювати

Не все можно покрыть тестами адекватно

Так. Але гівно-код — це ж погано. Чи ще debatable?

В погоне за кавереджем рождаются кадавры

Інколи — так

І ви чомусь не назвали реальну проблему з тдд
xD

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

Ну ок

А можна я ще на опенсорс підтягну?

Вибачте, я хотів Олексію Пєніє відповісти але жирним пальцем не туди пхнув.

А можна я ще на опенсорс підтягну?

Звичайно, можна! ;)

Ну так в процесі написання тесту ви і визначаєте, як він буде працювати

Ти писав щось складніше крудів?

Але гівно-код — це ж погано.

Див. моє попереднє питання. Не все можно покрити тестами. Моки — це палка о двох кінцях. Так, ти можешь замокати все, але чим більше ти мокаешь, тим більше твій тест перетворюється на тест заради тесту або щоб подрочити сонару.

не назвали реальну проблему з тдд

Реальна проблема з ТДД — сферичність та вакуумність концепції, яка не пристосована до реального життя і не витримує clash with reality на практиці, коли проект складніше круду.

Тобто ви не зрозуміли, як декомпозувати код, щоб зменшити кількість моків
Mkay

clash with reality

Ага, як і KISS, DRY, SOLID, etc
І це не та проблема на яку я натякав

Взагалі у вас виходить, що manual QA — це єдина надія людства на отримання хоч якогось софту

ви не зрозуміли, як декомпозувати код, щоб зменшити кількість моків

Лол, ага, ага

Ага, як і KISS, DRY, SOLID, etc

Ні, це якраз робочі практичні концепції, які були створені і виведені з практики, а не як чиясь розумова вправа. Тому вони є природніми, а тдд — штучна. Шкода, що ви цього не розумієте.

Ти писав щось складніше крудів?

Лол, я розумію чому ти вирішив замовчати це питання )

Взагалі у вас виходить, що manual QA — це єдина надія людства на отримання хоч якогось софту

Подписаться на комментарии