Как стать автором
Обновить

Комментарии 199

Интересно, многие ли дочитали до конца это словоблудие? :-)

Сломался посреди предпоследней темы… но после вашего коммента решил таки добить и добил :)
Это не словоблудие, а блудословие!

Словно ветку комментов с Хабра прочитал :-D Реквестирую статью к этим комментариям!

По факту так и было. Мы где-то в каментах с asolntsev поспорили на эту тему, и IvanPonomarev предложил нам пофлудить в формате статьи. Почему-то Иван в статье совсем не упомянут, хотя он по факту сделал основную работу — подготовил вопросы и какие-то исследования нашёл. Мы-то ни как не готовились, просто говорили, что в голову придёт.

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


От себя могу добавить что исчерпывающий комплект хорошо написанных модульных тестов представляет собой:


  1. Актуальную документацию по коду. Эта документация никогда не врет (в отличие например от комментариев), потому что она исполняемая.
  2. Объективный и что немаловажно автоматический инструмент контроля качества кода.
  3. Облегчает и поощряет повторное использование кода (потому что пункты 1 и 2)
После картинки в заголовке статьи дальше можно не читать.

А с картинкой-то что не так?

И все-таки хочу понять, над чем надо «думать», а над чем нет. Т.е. TDD, как правило, отождествляют в принципе с написанием тестов, но если мы сначала пишем код, а потом покрываем его тестами (как это и делает Тагир), значит, это уже не TDD?
Должен ли я писать тест, на проверку Content-Type, если он всегда text/html? Кто-то скажет — «А ты не можешь знать, что он всегда будет таким?». Если я один занимаюсь проектом, то я точно могу сказать о невозможности внезапного изменения такого поведения. В ином случае, логично бы написать такой тест, хотя бы ради той эмуляции документации, для других разработчиков.
Т.е. есть достаточно тривиальный функционал, над которым и думать/«думать» то и не нужно, но по TDD нужно «думать», иначе это будет не TDD?
Сам подход, вернее в том виде, в котором я его вижу в статьях и в не очень свежих книгах, противопоставляется подходу проектирования архитектуры. Но как раз в случае проектирования сложных процессов обработки данных(которые, чаще всего, генерируются динамически, приходят из сети или селектятся из базы), я тоже не вижу плюсов в написании тестов до кода. И гораздо быстрее набросать прототип(не этот, который из Design Patterns, а обычный прототип), понять, что тут не так, и удалить его. Что мы получили? Мы именно подумали над предполагаемыми подводными камнями, не потратив на это время. И делали это достаточно удобно, с code completion и прочими прелестями, любезно предоставляемыми IDE.
Кто-то скажет — «да это же не про написание кода, а про подход и философию!». Однако, в каждой статье и книге кровью написано — «сперва тест, потом — код», а это уже не подход, а механизм, что ли!

TL;DR
Почему малейшее написание кода до тестов не приемлимо для TDD?
Ничего не могу сказать против TDD, хочу достичь понимания в этом вопросе.
Один из ответов — написав тесты до реализации функционала вы будете уверены, что написанный вами функционал действительно можно покрыть тестами.
Почти так. Увидеть тест красным до написания кода — это единственная гарантия, что этот тест действительно что-то тестирует.

Есть другой вариант: написать код, написать намеренно ошибочный тест, проверить, что он действительно падает, исправить тест. Получается этакий Code Driven Testing.

Не пойдёт.
Так нет гарантии, что этот новый (изменённый) тест что-то тестирует.

Есть. Давайте я приведу пример:


Код:


function inc( a : number ) {
    return a + 1
}

Неправильный тест:


assertEqual( inc( 2 ) , 4 )

Проверяем — свалился и ругается, что функция вернула 3, а должна была 4.


Правильный тест:


assertEqual( inc( 2 ) , 3 )

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

Да, пожалуй, это даёт гарантию того, что тест — тестирует. И, кстати, я сам похожим образом иногда поступаю, если делаю не по TDD.

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

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

Это проблема тестового фреймворка — опеспечить тестируемость любого кода.

Нет. Вы не правы. Приведу утрированный пример: напишем такой код, который ведёт себя по-разному в зависимости от версии ОС. Скажем, под Windows он рисует синий квадрат, под MacOS — жёлтый квадрат.

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

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

Пример утрированный, но проблемы с тестируемостью — из моей практики — примерно такие.

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

vintageIvanPonomarev Нет, вы что, такой способ как раз не даёт никаких гарантий.
Допустим, у нас есть уже написанный метод `hypotenuse` (и в нём есть баги).

По вашей методологии, мы пишем красный тест:
assertEqual(hypotenuse(3, 4) * 0 , 99);


Потом меняем константу в тесте:
assertEqual(hypotenuse(3, 4) * 0 , 0);


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

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

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

В данном случае тест работает не правильно.

А я о чём!

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

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

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

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

Мутационное тестирование есть еще такое. В контексте TDD хорошо работает.

Задачка: обычный полный тест-сьют выполняется 5 часов. Сколько будет выполняться pitest? Ну и, кстати, как помнится, он находит непокрытый код, но не находит бесполезные тесты.

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

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

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

Ну, видимо, вы мало знакомы со статическим анализом. Простой пример:


@Test
public void testSomething() {
  new Thread(() -> {
    assertEquals(foo(), "bar");
  }).start();
}

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


// code
String foo() { ... }

// test
assertNotNull(foo(), "Return value of foo is null");

Это компилируется, но ничего не тестирует, потому что перепутали порядок аргументов метода assertNotNull (проверяется на null константная строка, а результат метода foo() используется в качестве сообщения при исключении). Это тоже легко отлавливается статическим анализом. Таких примеров много.


Статический анализ плохо работает в контексте языков с динамической системой типов

Про ваш контекст ничего не знаю. Не используйте такие языки. Я вот на Java пишу и горя не знаю.

Ассершн выбросит исключение, но оно прилетит не в текущий тред, а в другой

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


Вот такие вещи мутационное тестирование выявляет.


Не используйте такие языки.

Уж простите, на вкус и цвет. Java8 при всей своей красоте не удовлетворяет моих нужд. Ладно бы котлин, он мне нравится. Но система типов в java так себе.

И статический анализатор говорит что все хорошо.

Почему? Как раз я о том, что статический анализ это может выявить, причём сразу и указав на конкретное проблемное место в тесте, а не просто сказав, что такой-то мутант не был убит, а почему — сами разбирайтесь.

статический анализ это может выявить

Неужто не может быть ситуаций которые проверить можно только в рантайме?


Вообще весь разговор начался с этой цитаты:


обычный полный тест-сьют выполняется 5 часов.

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

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

Это без UI-тестов. Возможно, вы не понимаете, что такое действительно большой проект.

UI-тесты обычно не являются unit-тестами, а TDD/TLD об юнит-тестах, максимум об интеграционных.
Это без UI-тестов.

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

Поддерживаю lany. Мутационное тестирование — красивая идея, но на практике мне не удалось его применить. Именно потому, что
1. Оно очень долгое
2. Оно даёт много ложных срабатываний. В них приходится бесконечно копаться вручную. Это прост нереально.

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

Повторюсь. Мутационное тестирование обычно призвано улучшить именно юнит тесты. На интеграционных например запускать мутационное смысла нет особо.

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

Зависит от разработчиков. У большинства в целом так и выходит. Тесты ради тестов. Причем у людей которые пишут тесты после кода это происходит чаще.

Fesor Пять баллов!
RaaaGEE Мне кажется, это миф. Это говорят те, кто сами тесты не пишет. Придумали проблему.
Я вот лично никогда такого не видел, чтобы тесты писались ради тестов.
Я вот вижу ежедневно, как тесты пишутся ради надёжного результата и хорошего дизайна кода.
чтобы тесты писались ради тестов.

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


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


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

Почему малейшее написание кода до тестов не приемлимо для TDD?

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


TDD он как бы больше про проектирование.


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


У меня были ситуации когда вроде как что-то надо писать… но потом ты еще на этапе когда логику работы описываешь понимаешь, что вроде как можно и без "этого" обойтись. Мол мы последовательно идем а не спантанно, поправим тут а потом там...


Другое дело что зацикливаться не стоит. Инфраструктуру к примеру через TDD не особо продуктивно выходит разрабатывать. Можно через ATDD, собственно идея остается той же. Что-то удобно, что-то не очень. А еще есть property based testing, specification by example, BDD…

А можно подробнее про инфраструктуру? И какие еще элементы не TDD-шатся?

Ну смотрите. Например пишем мы DAO, или например, репозиторий, или запрос какой-нибудь через query builder. Юнит тестами такое априори не покрывается — это уже идет взаимодействие с внешним миром. Тут нужны интеграционные тесты. А это уже выходит за рамки TDD, это можно через ATDD реализовывать. То есть написать интеграционные тесты покрывающие только позитивные сценарии и вперед.


Организуется таким образом пирамида тестирования. Логика покрыта быстрыми юнит тестами, инфраструктура — медленными интеграционными и UI логика — медленными тестами. Причем каждый раз можно не выполнять все имеющиеся тесты а например сначала прогонять все быстрые тесты, затем по каждому уровню critical path и т.д. Да и покрывать всю инфраструктуру тестами дорого выходит.


Если у нас проект уровня CRUD, мы можем там использовать различные active record и т.д. — тут как бы все просто. Если у нас проект с логикой посложнее мы уже будем заварачивать модель данных в обертки или вообще использовать data mapper, и тестировать уже объектную модель. А потом уже интеграционными тестами проверять что sql запросы например правильно выполняются. И для этого составляются тест кейсы, и покрывать мы должны то что ломаться может часто а не один раз в год.


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

Должен ли я писать тест, на проверку Content-Type, если он всегда text/html?


В общем и в целом практический (не рассматривая идеального коня в вакууме) ответ на это вопрос совпадает с ответом на вопрос: если бы вы не писали тестов вообще, вы хотя бы раз посмотрели бы глазами на raw ответ, чтобы убедиться, что он действительно text/html.

Если вы просто хотите получать пользу от покрытия тестами, не добиваясь 100% покрытия каждой строчки в репозитории проекта как самоцели, то тесты нужно желательно писать на все те вещи, которые вы проверяли бы лично руками и глазами, если бы о тестах ничего не знали. Ведь практически наверняка вы запускаете написанный вами код локально перед тем как отдать его тестировщикам/заказчику или, хотя бы, на конвейер CI/CD, чтобы убедиться что он хотя бы запускается, а не крашится сразу.

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

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

TDD не противопоставляется проектированию архитектуры. TDD — это методология проектирования в целом или одного из его этапов. TDD — способ, который предлагается использовать при проектировании относительно небольших изолированных, сводимых к понятию «юнит» (есть ещё BDD и прочие test first DD, TDD — про юнит-тесты), компонентов приложения, вернее даже не самих компонентов, а их контрактов, интерфейсов. Грубо, вы можете нарисовать UML-диаграммы, а можете выразить ту же идею в коде теста. Только диаграммы обычно рисуют глобально, в одной итерации описывая множество методов, поведений, потоков данных и т. п., только потом приступая к реализации, а TDD предлагает это делать маленькими итерациями, описывая каждый юзкейс компонента отдельным тестом и тут же реализуя описанное.

Почему малейшее написание кода до тестов не приемлимо для TDD?

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

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

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

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

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

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

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

А думают все люди по-разному (ваш КО). Особенно по-разному думают люди, когда они, как специалисты, выросли в разных традициях, на разных практиках. Например, по тексту интервью я могу предположить, что когда Тагир думает о «формулировке задачи» (==что надо сделать) — для него эта формулировка выглядит как интерфейс будущего кода. А для Андрея та же «формулировка задачи» выглядит как тест для еще несуществующего кода. Важна ли эта разница? Мне кажется, нет. Мне кажется, важно, чтобы в мозгу инженера вообще была эта мысль «а что за задачу я сейчас решаю». Каким способом эта мысль в мозге разработчика поселилась — дело десятое. TDD предлагает один из методов вдолбить эту мысль в голову разработчика — но это явно не единственный метод. И уж если разработчик уже как-то сам себя приучил думать о формулировке задачи — то зачем ему теперь test first?

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

На самом деле никто ведь не пытается вам что-то доказать — ни исследованиями, ни мнением экспертов. По крайней мере, в этой статье. Что мы пытаемся — это привести аргументы обеих сторон, чтобы читатели могли сами разобраться. Пытаемся логически объяснить обе позиции. Для этого и нужен диалог. Эта статья — это диалог, вы заметили? :)

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

Не, я не заметил диалога. Я заметил «я думаю, что действий больше» — «я думаю, что меньше». Мой фаворит «Это неправда». Для меня это не диалог, а попытка навязать свои догмы, в ситуации, когда объективных доказательств нет, а хочется быть правым.
cheremin Знаете, в чём отличие между заинтересованным человеком и троллем?
Заинтересованный человек всегда спросит, а как именно это работает. Засчёт чего. Каким образом решает проблему. А тролль требует доказательств.

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

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

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

А насчет опытов: лично мне в жизни повезло. Я обучался ставить эксперименты в таком контексте, где не было заранее известных «правильных» ответов. Так что я знаю разницу между мотивацией «сдать физпрактикум на 5 за отведенное время», и искренним любопытством.
Успешно разрабатывает, говорите?
Ну да, люди на лошадях тоже успешно добирались из одного города в другой.
А кто сказал, что он без TDD работает эффективнее, чем с TDD?
Меня именно это интересовало. Чтобы это понять, я задал кучу вопросов. И из ответов сделал вывод, что не эффективнее. Фанатизм тут не при чём.

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

НЛО прилетело и опубликовало эту надпись здесь
s-kozlov Да, вы правы, я думаю, что TDD — это автомобиль.

Только во-первых, я никому ничего не впариваю, а во-вторых, обоснуйте вашу позицию? Обоснуйте, почему TDD — это другая лошадь? Давайте эти ваши доказательства!
НЛО прилетело и опубликовало эту надпись здесь
s-kozlov Вы исходите из предположения, что то, что было раньше — по умолчанию лучше.

Но ведь это нелогично!
Вся история человечества показывает, что всё, что лучше, было позже. Скорее старой технологии нужно доказывать, что она всё ещё в строю.
НЛО прилетело и опубликовало эту надпись здесь
> а во-вторых, это не означает, что всё, что было позже, лучше.
Всё верно! Я вас проверял. Достойный ответ :)

Ладно, я позволил себя увести в сторону — в дебри споров о доказательствах.
На самом у меня не было цели кому-либо что-либо доказать.
Я пришёл на это интервью-беседу, чтобы побеседовать. Рассказать о том, как я это понимаю и как у меня это работает. Я рассказал. Если какую-то тему недостаточно раскрыл — you are welcome, спрашивайте, отвечу с удовольствием. А доказывать кому-либо что-либо я не хочу и не буду. Это бесполезная трата времени.
НЛО прилетело и опубликовало эту надпись здесь
s-kozlov Всё верно, такой способ мышления кажется противоестественным. Не только вам, но и мне, и всем адептам TDD.

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

P.S. Чешем, ещё как чешем. Мало TDD, мы ещё и практикуем парное программирование. То есть сидеть одному и шпарить «по-своему» по-любому не получится. Мы об этом рассказываем на собеседовании (да собственно, это и на сайте написано), поэтому те, кто не готов меняться и учиться новому, просто изначально не приходят к нам на работу.
НЛО прилетело и опубликовало эту надпись здесь
Понятно, что не пошёл бы. Таких на самом деле большинство. Ну и хорошо, нам ведь нужны только целеустремлённые люди.

На самом деле у нас свобод гораздо больше, чем в других конторах. У нас нет «архитектора» или «тимлида», который указывает тебе, что делать. Ты можешь выбирать проект, фреймворки, даже языки программирования. И IDE тоже. Это то, о чём большинство людей в IT даже не мечтает.

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

Ну конечно же, для самое главное — это качественный продукт в срок и поддерживаемый код. Только как этого достичь? У нас собрались единомышленники, которые считают, что TDD (ну и ряд других практик) — самый эффективный способ. Эффективные менеджеры тут не при чём.

P.S. Про целеустремлённых людей это был лёгкий троллинг, конечно.
Я анекдоты про них слышал, а в жизни никогда не видел.

Я с такими сталкивался.


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

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

Согласен. Достаточно лишь продумать.
Но в том-то и проблема — как этого добиться? Стабильно. Чтобы не было такого, что сегодня продумал, а завтра не продумал — ну настроение не то, не срослось. Лекарства лучше, чем TDD (+парное программирование), человечеству пока неизвестно.
НЛО прилетело и опубликовало эту надпись здесь
s-kozlov Нет, не фанатизм. Это выводы, основанные на многолетнем опыте. Я ведь много лет работал и так, и так. Точно так же как с IDEA и Eclipse. Там, кстати, я тоже приводил совершенно понятные логичные доводы. При чём тут фанатизм?
НЛО прилетело и опубликовало эту надпись здесь
в первую очередь делают плагин Eclipse

Может к VIM?

А я вот усматриваю фанатизм в вашем стремлении во что бы то ни стало обвинить меня в фанатизме. :)

Чем тогда этот «фанатизм» отличается от фанатизма прораба, который заставляет всех ходить по стройке в каске? Каждый, кто ходит по стройке, тоже может сказать, что он мастер, что умеет ходить аккуратно, у него своя техника…

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

P.P.S. Статью про IDEA/Eclipse вы, очевидно, читали очень невнимательно. Там в самом первом предложении сказано: «IDEA лучше как IDE для Java». А вы мне говорите — «когда выходит новый язык». При чём тут новый язык? Я же говорю — для Java. А в конце статьи я писал, что есть целый перечень ситуаций, в которых Eclipse вполне может быть лучше. Внимательнее надо быть, товарищи!
НЛО прилетело и опубликовало эту надпись здесь
Отлично сказано!
Значит, TDD — это правила, написанные кровью.
НЛО прилетело и опубликовало эту надпись здесь
s-kozlov Вы упорно не замечаете сходства. По вашей логике и каска — всего лишь один из инструментов. Нигде нет доказательств, что он лучший. И наконец, ходить в каске — сюрприз-сюрприз — противоестественно!
НЛО прилетело и опубликовало эту надпись здесь

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

НЛО прилетело и опубликовало эту надпись здесь
s-kozlov Перед тем как фейспалмить, вы немножко задумайтесь, к чему я клоню. Я говорю, что ваша логика не работает. Все ваши доводы против TDD точно так же распространяются и на каску. Но вот сюрприз — она общепризнанный стандарт. Значит, ваши доводы не работают. Либо каска стала общепризнанным стандартом без всяких доказательств, а просто на основании многолетнего опыта. Так же как у нас TDD.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь

Потому придумали BDD, чтобы вывести идею "сначала разберись а потом делай" на другой уровень.

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

А как ваша команда оценивает какой код ты выдаешь? Каковы формальные критерии?

НЛО прилетело и опубликовало эту надпись здесь

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


Парное программирование это очень прикольно. У меня уже три года не было парного программирования. :-)

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

P.S. И вообще, вы же должны понимать, что это было сказано так, для красного словца.

TDD это инструмент. Инструментом надо уметь пользоваться и знать в каких ситуациях оно работает а в каких нет. И знать об альтернативах.


Например помимо классических тестов есть еще property based тестирование, или надо знать о разных уровнях тестов...


Лично я воспринимаю идею TDD ближе к тому что преподносится "адептами BDD" — сначала разбираемся что делать и зачем, а потом уже делаем.

И из ответов сделал вывод, что не эффективнее.

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


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

А почему надо делать выводы об исходном объекте?
Я как раз хочу, чтобы объект изменился :)
Включать ли это время в подсчёт эффективности?

Обязательно. По крайней мере пока (и если, конечно) навыки TDD не будут считаться дефолтными для разработчика.
Кто его оплатит?

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

«Тот, кто верит, что это уменьшит стоимость поддержки моего кода в долгосрочной перспективе». — починил.

НЛО прилетело и опубликовало эту надпись здесь
Да вы всё передёргиваете. Никто и не просит никому верить на слово. Я вообще не хочу, чтобы кто-нибудь верил. Надо не верить, а понимать. А для этого не доказательства нужны, а объяснения. Вот я и пытаюсь объяснить, как я это вижу, почему я считаю это правильным, засчёт чего, по моему мнению, это работает лучше.
Тьфу блин… я уж подумал было, что один человек сам с собой спорит… а это, оказывается, просто аватарки очень похожи +_+
НЛО прилетело и опубликовало эту надпись здесь
Aingis Тогда тем более! Если нет разницы, чего вы вообще требуете тут? Тут собрались специалисты поделиться личным опытом и обсудить качественные различия, а вы требуете количественных доказательств. Не о том разговор.
НЛО прилетело и опубликовало эту надпись здесь
Типичная логическая ошибка.
Отсутствие доказательств не является доказательством отсутствия.

Допустим, у нас нет доказательств преимущества TLD или TDD. Что теперь? Не будем ни о чём говорить? Ну не хотите — не говорите, а есть люди, которые хотят поговорить. Обменяться мнениями, рассказать о своём опыте. Я думаю, что это гораздо полезнее для понимания, чем доказательства.
НЛО прилетело и опубликовало эту надпись здесь

Хорошо сформулировал, спасибо. Прекрасно дополняет моё мнение. Сейчас вот пишу новый и довольно сложный компонент, в том плане, что надо подумать о куче мелких деталей, сделать внутреннюю модель, которая учтёт все тонкости, и адаптировать эту модель к множеству сценариев. Я мыслю именно с точки зрения реализации: сперва начал писать одно, понял, что возникают проблемы в другом месте, начал переделывать модель. По ходу дела приходят мысли, как реализовать ту или эту деталь, надо срочно эти мысли выражать в коде, пока не потерялись. Пока это прёт, на тесты совершенно нет времени, мысли разбегутся. Да и тестировать странно. Компонент снаружи выглядит просто (на входе кусок кода и на выходе кусок преобразованного кода), публичный интерфейс у него уже фиксирован давно (это инспекция в IDE, которых уже тысячи), а кишки я всё равно постоянно переделываю, писать тест на приватные методы, которые постоянно меняются, мало смысла. Как только получается минимальная полная цепочка, которая может корректно обработать хотя бы один возможный входной кусок кода, я пишу тест, который не тестирует кишки, а тестирует вход и выход всего алгоритма. Потом тестов становится всё больше, но до первого теста прошло несколько часов разработки.

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

Но такой подходит не скалируется! Станет контекст чуть-чуть больше — и расплескаешь. Отвлечёшься не на тесты, а на что-то другое — расплескаешь. По-любому расплескаешь.

Поэтому я и говорю про концепцию «workspace». Это то, что ты пытаешься одновременно удерживать в голове, и надо стараться его минимизировать. Смотрел доклады Максима Дорофеева? «Джедайская техника пустого инбокса», «Мыслепатроны» — вот это всё. Удерживать что-то в голове — крайне расточительно. Наш мозг очень плохо умеет это делать и тратит на это много энергии — быстро устаёт. Зато мозг хорошо умеет думать — этим и надо его максимально нагружать. А задачу удержания контекста перекладывать на компьютер. То есть случае TDD — на тесты.

P.S. Писать тест на приватные методы — очень даже неплохая мысль. Ничего, что они постоянно меняются. Тесты как раз будут помогать их менять. Приватные методы — это тоже API, пусть невидимый снаружи. Это API для самого важного человека во вселенной — для тебя! :) Он тоже должен быть удобным.
Писать тест на приватные методы — очень даже неплохая мысль. Ничего, что они постоянно меняются. Тесты как раз будут помогать их менять. Приватные методы — это тоже API, пусть невидимый снаружи. Это API для самого важного человека во вселенной — для тебя! :) Он тоже должен быть удобным.

Тут возникает два вопроса:

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

Второй уже больше теоретический — если хочется протестировать что-то приватное, то это разве не прямой сигнал, что пока выделять этот кусок в отдельный класс со своим апи, которое тоже покрывать тестами?
Я работаю так: методы, которые надо тестировать, но которые не должны быть открыты как public API, открываю до package-уровня и усаживаю юнит-тест в тот же пакет. Но есть и private. Его не тестирую. Но его обычно и не возникает желания тестировать. Николай xpinjection говорит в этом интервью: если возникло желание тестировать private, то это значит, что твой класс слишком многое на себя взял и пора его дробить.
Спасибо! Сам делаю примерно так же, но периодически натыкаюсь на поборников чтрогой чистоты кода, которые начинают ныть «А почему этот метод не приватный? А почему остальные приватные? А давай как-то так, что бы однообразно было?», и начинаааается… =(
На эту тему есть отличный пост Боба Мартина: http://blog.cleancoder.com/uncle-bob/2015/07/01/TheLittleSingleton.html

Вкратце содержание таково:
«Tests trump Encapsulation.»
if a test needs access to a private variable the variable shouldn't be private

А вот уволоку ка я это себе :)
Но с выставлением публичной переменной только потому, что «мы доверяем своей команде» он явно погорячился.
What good is encapsulated code if you can't test it?


Вот это даа, вот это мысль!!! )))Пост отличный. Но он не отменяет необходимости постепенно переходить с использования классической реализации синглетонов на Dependency Injection :-))
Второй вопрос: совершенно верно, это прямой сигнал. Часто так и получается. Это и имеют в виду, когда говорит, что TDD приводит к лучшему дизайну.

Первый вопрос: тут есть разные мнения. Одни считают, что тестировать надо только публичные методы. Другие считают, что ради теста не грех и убрать модификатор «private».

Я лично считаю, что это зависит от сложности метода. Если он простой (комбинаций особо нет) и его легко проверить через какой-то другой публичный метод, то можно через публичный метод.
Если же метод сложный, то для него стоит написать много разных тестов с различными комбинациями входных параметров, то лучше убрать «private» и протестировать этот метод отдельно.
Спасибо!
Смотрел доклады Максима Дорофеева? «Джедайская техника пустого инбокса», «Мыслепатроны» — вот это всё. Удерживать что-то в голове — крайне расточительно.

Смотрел, Дорофеев прикольный, но у меня нет времени на джедайские техники, надо код пилить :D


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

Ха-ха, это классика :)
«Что бы такого съесть, чтобы похудеть».

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

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

Да, я тоже примерно так пишу.


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


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


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


Я вижу немало разработчиков, ставших адвокатами TDD уже в зрелости. Ну так, возможно, просто у них изначально "способ думать" был более совместим с этим подходом — отсюда и энтузиазм?


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


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

cheremin Я думаю, что не только для вас способ мышления «test first» контринтуитивен, а вообще для всех. И для меня тоже! Для нас всех более естественно сразу писать код.

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

Это хорошо, что вы привели айкидо в пример: у меня много ассоциаций с телесными навыками возникает, но я их не приводил, потому что полагал, что программисты не поймут.


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


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


И это мне очень напоминает изложение TDD.

cheremin Я не понял, какие выводы-то?

Я привёл айкидо как пример того, что сначала кажется неестественным, а оказывается очень эффективным. Всем же кажется, что дать в табло эффективнее, чем делать какие-то непонятные выпендрёжные движения? Вот точно так же с TLD vs TDD.

P.S. Видимо, мне повезло больше. Учителя принципы объясняли, ученики понимали.
Но вообще-то я думаю, что и в айкидо, и в TDD одних объяснений недостаточно. Можно объяснять сто раз, но начнёшь понимать, как это работает, только тогда, когда сам попробуешь. И попробуешь не один раз, естественно.

А я вам вернул ваш же пример: айкидо может быть эффективным, вот только далеко не все, что вас заставят заучивать — действительно важно для этой эффективности. Часть правил — просто порожняк, или лично вам не подходит — а вы тратите своё время и ресурсы нервной системы. Иначе айкидо было бы самым лучшим видом единоборств — а это, очевидно, не так.


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


Проблема с подходом "200 раз попробовать" в том, что человек ко всему привыкает. И через 200 раз уже не понятно: стало удобнее, или ты просто привык к неудобству? Как говорит старая поговорка — да, медведя можно научить ездить на велосипеде. Но будет ли от того медведю польза и удовольствие?

Конечно объясняли. Да, и точку приложения сил, и про действующие мышцы.

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

Да, конечно, у подхода «200 раз попробовать» есть такая особенность.
Но если не попробовать 200 раз, то уж точно ничему не научишься.
Поэтому я не вижу иного варианта, кроме как попробовать по 200 раз оба варианта и уже тогда сравнивать. Скажем, 5 лет изучать айкидо, 5 лет карате — и тогда можно предметно обсуждать. А если у вас нет лишних 10 лет, то найдите человека, который так сделал, и послушайте, что он расскажет о своём опыте.

А у нас тут собрались комментаторы, которые не пробовали по 200 раз ни TLD, ни TDD, и сидят осуждают всех. :)
Вы что-то путаете. Никто из тех, кто занимается спортом, не жалуется на то, что он тратит время и ресурсы нервной системы. Люди приходят туда, чтобы научиться новому, прокачать скилы. И кстати, там никого ничего не заставляют.

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


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


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

принцип test first — это прежде всего педагогика. Он должен выстроить у человека определенный способ мышления. Если такой способ уже выстроен — то уже неважно, когда именно писать тесты.

полностью поддерживаю.

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

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

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


По-моему, такой вывод в диалоге как раз-таки прозвучал!

В общем до самого интересного так и не дошли, к сожалению.


Что же, по-Вашему, самое интересное в этой теме?
По-моему, такой вывод в диалоге как раз-таки прозвучал!

Если я правильно помню, то звучало что-то вроде «при TDD налететь на эти грабли гораздо проще», что по-моему не так. Возможно я не внимательно прочитал.

Что же, по-Вашему, самое интересное в этой теме?

Как это что? :) Конечно же применимость и крайние случаи!

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

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

В общем собеседники на мой вкус зависли на совершенно не принципиальных вещах так и не добравшись до мякоти вопроса, но это тольк мое мнение.
IvanPonomarev А я поддержу AstarothAst. Мне тоже кажется, что количество нажатых клавиш сравнивать непродуктивно. Я так и сказал: «Абсолютно без разницы!» :)

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

P.S. AstarothAst Насчёт сторонних библиотек — TDD тут тоже хорошо подходит. Ты пишешь тест на свой код, который использует библиотеку X. Запускаешь тест и видишь, как эта библиотека ведёт себя. Это ведь гораздо эффективнее, чем экспериментировать с этой библиотекой на живом коде, где после каждого изменения нужно рестартовать/редеплоить/логиниться/и пр.
Насчёт сторонних библиотек — TDD тут тоже хорошо подходит. Ты пишешь тест на свой код, который использует библиотеку X. Запускаешь тест и видишь, как эта библиотека ведёт себя. Это ведь гораздо эффективнее, чем экспериментировать с этой библиотекой на живом коде, где после каждого изменения нужно рестартовать/редеплоить/логиниться/и пр.

Нет, я имел ввиду другое. Я реализую класс Сипулька. По TDD я должен сначала написать на Сипульку тесты — пишу. Красивое апи, возвращающее то, что мне надо. Начинаю реализовывать Сипульку, и в какой-то момент использую библиотеку Сипуление. И тут выясняется, что эта библиотека коварно… ну, не знаю… возвращает объекты ошибок вместо того, что бы кидать исключения, что очень и очень плохо ложится в то апи, которое я заложил в тесте. Переосмысляю, и иду переписывать тест с нуля. Смыть, повторить. Такие натыкания на «неожиданное поведение» могут быть постоянными и болезненными, они будут отбрасывать к началу, и так до тех пор, пока инструмент не будет изучен до того уровня, который позволит писать тесты с учетом подводных камней. Это больно! Это тем более больно, если такой источник не очевидного поведения закопан глубоко в кишках реализации. TLD в данном случае эту боль сократит за счет отсутствия тяни-толкая, проблемы будут решаться по мере возникновения, а тестами покроется «то, что выросло», которое потом придется рефакторить.

Отчасти тут спасут «учебные тесты», но этому же тоже нужно учить :)

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

Дядя Боб предлагает использовать для этого учебные тесты. Пробовал — и правда помогает!
Да, учебные тесты — это хорошо.
Вообще это концепция «спайка». Когда имеешь дело с неизведанной областью (в т.ч. новой библиотекой), надо с ней сначала поэкспериментировать. Пишешь main метод, пишешь там какой угодно говнокод без тестов, экспериментируешь. Когда ты наконец понимаешь, как работает эта библиотека и как её надо будет использовать, стираешь все свои эксперименты и пишешь код с нуля через TDD.

P.S. Если Сипуление коварно возвращает объекты ошибок, то не ваша ли задача скрыть это от вашего пользователя? Возьмите эти ошибки, превратите в нормальные исключения — пусть ваш API будет хорошим. Почему все остальные должны мучаться?
Вообще это концепция «спайка».

Вот до этого в заглавной статье и не дошли :)

Если Сипуление коварно возвращает объекты ошибок, то не ваша ли задача скрыть это от вашего пользователя? Возьмите эти ошибки, превратите в нормальные исключения — пусть ваш API будет хорошим. Почему все остальные должны мучаться?

Я ж сказал «например». Может быть все, что угодно же. А «взять ошибки и превратить в нормальные исключения» — это хорошо, но мало что меняет. Беда остается на своем месте — во время написания теста по TDD ты не знаешь, что у тебя буквально тут же возникнет совершенно левая задача по конвертации ошибок в исключения, без реализации которой ты не сможешь приступить к той задаче, к которой хотел. Готово дело — сроки поплыли… :)
Так TDD тут не при чём! Это может случиться в любом проекте с любой методологией.
Да вся наша профессия такая: у тебя буквально постоянно возникают совершенно левые задачи. Мы же инженеры, в этом и состоит наша работа, чтобы как-то с ними управиться и не слишком продолбать сроки. TDD тут не при чём.
добавьте Андрею в заслуги тоже опен-сорсную библиотеку https://github.com/codeborne/selenide, чтобы регалии звучали одинаково :)
Юхуу! Спасибо!

Что ж у тебя даже бейджа с coverage нету? Давай меряться! У меня 99%. Beat that! :-D

Да я как-то не заморачивался… Как-то не нужно было. В Jenkins же виден анализ покрытия.
Ну ладно, попробую прилепить.
наверно тут как и везде надо знать меру, нет смысла писать тесты на то что не может сломаться, тдд не обязательно должно распостряняться на весь проект, как и покрытие кода не должно быть 100% если это не несёт никакой пользы, поддерживать кучу тестов тоже напряжно, я вообще считаю что нужно писать в основном функциональные тесты, причём их писать можно также до написания самого кода, а юнит тесты только в местах где функциональные не дадут преимущества или тесты писать слишком сложно
Так это опасный путь. Никогда не знаешь заранее, что может сломаться. На самом деле сломаться может всё!
И вы забываете, что польза от тестов не только и не столько в том, что они обнаруживают ошибки. Они помогают разрабатывать код с хорошим дизайном, служат как документация и т.д.
НЛО прилетело и опубликовало эту надпись здесь
В условиях задачи было «поддерживать кучу тестов тоже напряжно», что подразумевает изменение поведения. Если эти сеттеры и геттеры начинают менять поведение, то на них и правда нужно писать тесты и это уже далеко не просто сеттеры и геттеры.
Если «поддерживать кучу тестов напряжно», то это значит, что они ломаются, иначе бы не напрягало. Если они ломаются, то значит вы изменили старое поведение на новое. А если вы изменили поведение, но не стали отражать этого в тестах, то в следующий раз сломаются уже не тесты (которых нет), а код на продакшене. Зачем это надо?
Отлично!

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

Но это говорит только о том, что эти тесты написаны неправильно. При изменении метода А должны ломаться только тесты, проверяющие этот метод — а их должно быть немного. Ну там, 2-5-10. ну может, 20. Поменять эти тесты — не такая уж большая работа.
Но это говорит только о том, что эти тесты написаны неправильно.

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

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

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

Вы бы пример кода привели что-ли? Какие сигнатуры методов у интерфейса подразумеваются? Такой пойдет?


public interface JsonSerializer<T>
{
    string ToJsonString(T item);
    T ToObject(string json);
}
public interface Formatter<T>
{
    string toString( T item );
    T fromString( string stream );
}
// Начинаем разработку как и полагается с написания модульного теста.
// Здесь набрасываем предположения о том, как будем тестировать
// конкретный Formatter<Person> с конкретными проверочными данными.
[TestClass]
public class DefaultFormatterTest
{        
    // Я предполагаю что в простейшем случае, набор тестовых
    // вызовов можно обернуть в команду.
    private Command formatterTestSuit;

    [TestInitialize]
    public void setUp()
    {
        // Это эталонная строка, из которой реализация 
        // Formatter<Person> должна будет извлечь 
        // экземпляр модели Person. И наоборот, из эталонного
        // экземпляра модели Person должна будет получаться 
        // такая строка при выполнении обратной операции.
        string text = "{\"FirstName\":\"Jack\",\"LastName\":\"Sparrow\",\"BirthDate\":\"1635/25/11\"}";

        // Это эталонная модель, из которой реализация
        // Formatter<Person> должна будет сгенерировать
        // строку, идентичную эталонной. И наоборот, из эталонной
        // строки должна будет получиться такая модель при 
        // выполнении обратного преобразования.
        Person model = 
            new Person{ 
                FirstName = "Jack", 
                LastName = "Sparrow", 
                BirthDate = new DateTime(1635, 11, 25)
            };

        // Для удобства, эталонные данные упаковываем в отдельный контейнер.
        FormatterSampleData<Person> sampleData = 
            new FormatterSampleData<Person>{
                Text = text,
                Model = model
            };

        // Инстанциируем экземпляр нашей, несуществующей еще фабрики
        // для производства строк из моделей, и моделей из строк.
        Formatter<Person> formatter = new DefaultFormatter<Person>();

        // И инициализируем наш несуществующий еще тестовый набор,
        // передавая ему в конструктор собственно экземпляр 
        // тестируемого Formatter<Person> и эталонные данные.
        this.formatterTestSuit = 
            new FormatterTestSuit<Person>(formatter, sampleData);
    }

    [TestMethod]
    public void Execute()
    {
        // Тестовый метод будет всего один. Он нужен лишь
        // для того, чтобы запустить комплект проверок,
        // упакованных внутрь команды.
        this.formatterTestSuit.Execute();
    }
}

// Переходим к реализации комплекта собственно модульных тестов,
// которые я решил упаковать в команду.
public class FormatterTestSuit<T>:Command
{
    private Formatter<T> formatter;
    private FormatterSampleData<T> data;

    public FormatterTestSuit(Formatter<T> formatter, FormatterSampleData<T> data)
    {
        if (formatter == null)
            throw new ArgumentNullException("formatter");

        if (data == null)
            throw new ArgumentNullException("data");

        this.formatter = formatter;
        this.data = data;
    }

    // Первый тестовый вызов проверяет корректность работы 
    // метода toString любой реализации Formatter<T>
    public void should_make_model()
    {
        T model = this.formatter.fromString(this.data.Text);

        Assert.IsNotNull(model);
        Assert.AreEqual(this.data.Model, model);
    }

    // Второй тестовый вызов проверяет правильность
    // строки, генерируемой методом toString.
    public void should_compose_string()
    {
        string text = this.formatter.toString(this.data.Model);

        Assert.AreEqual(this.data.Text, text);
    }

    // Оба тестовых метода, для удобства оборачиваем методом Execute.
    public void Execute()
    {
        this.should_make_model();
        this.should_compose_string();
    }
}

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

Боже, к чему такие сложности? Нужно всего лишь проверить, что после сериализации и десереализации объект получается эквивалентным. Причём рботать это должно независимо от формата, а не только лишь с json.

Где вы увидели ограничение форматов в виде "только лишь с json"?


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

А я вас как раз json-ом не ограничиваю. Json встречается в "запускателе" тестов. Сами тесты в отдельном классе, который в принципе понятия не имеет о том, чего ему могут "скормить" в качестве затравки. Хотите разбирать XML объявляйте новый "запускатель" тестового набора.


[TestClass]
public class DefaultXmlFormatterTest
{        
    private Command formatterTestSuit;

    [TestInitialize]
    public void setUp()
    {
        string text = "<person><FirstName>"Jack"</FirstName><LastName>"Sparrow"</LastName></person>";

        Person model = 
            new Person{ 
                FirstName = "Jack", 
                LastName = "Sparrow"
            };

        FormatterSampleData<Person> sampleData = 
            new FormatterSampleData<Person>{
                Text = text,
                Model = model
            };

        Formatter<Person> formatter = new DefaultXmlFormatter<Person>();

        this.formatterTestSuit = 
            new FormatterTestSuit<Person>(formatter, sampleData);
    }

    [TestMethod]
    public void Execute()
    {
        this.formatterTestSuit.Execute();
    }
}

Сравните с кодом на D:


import std.traits;
import std.stdio;
import std.outbuffer;
import std.conv;
import std.json;

interface Packer( Target , Source )
{
    Target pack( Source src );
    Source unpack( Target src );
}

mixin template PackerTest( alias Packer )
{
    /// Packing booleans
    unittest
    {
        auto packer = new Packer!bool;
        assert( packer.unpack( packer.pack( true ) ) == true );
        assert( packer.unpack( packer.pack( false ) ) == false );
    }

    /// Packing integers
    unittest
    {
        auto packer = new Packer!long;
        assert( packer.unpack( packer.pack( 0 ) ) == 0 );
        assert( packer.unpack( packer.pack( 123 ) ) == 123 );
        assert( packer.unpack( packer.pack( long.max ) ) == long.max );
    }
}

mixin PackerTest!StringPacker;
class StringPacker( Source ) : Packer!( string , Source )
{
    string pack( Source source )
    {
        return source.to!string;
    }
    Source unpack( string target )
    {
        return target.to!Source;
    }
}

mixin PackerTest!JsonPacker;
class JsonPacker( Source ) : Packer!( JSONValue , Source )
{
    JSONValue pack( Source source )
    {
        return source.JSONValue;
    }
    Source unpack( JSONValue target )
    {
        static if( is( Source : bool ) ) {
            switch( target.type )
            {
                case JSON_TYPE.TRUE : return true;
                case JSON_TYPE.FALSE : return false;
                default: throw new Exception( "Non boolean: " ~ target.type );
            }
        } else if( is( Source : long ) ) {
            return target.integer;
        }
    }
}

void main()
{
}

И?

Всё куда проще.

:-) Не буду с вами спорить.

НЛО прилетело и опубликовало эту надпись здесь

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

НЛО прилетело и опубликовало эту надпись здесь
Я не очень понял, в чём состоит ваше решение. Более того, я не понял, в чём проблема.
Как-то всё это выдумано. Нет такой проблемы в реальной жизни.

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


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

Как один из вариантов:


public interface Command
{
    void Execute();
}

public class DataContextImplementationTestBase:Command

    private DataContext context;

    public DataContextImplementationTestBase(DataContext context)
    {
        if(context == null)
            throw new ArgumentNullException("context");
        this.context = context;
    }

    public void DataContext_should_return_value()
    {
        IEnumerable<User> users = context.Get<IEnumerable<User>>();

        Assert.IsNotNull(users);
        Assert.AreEqual(0, users.Count());
    }

    public void DataContext_should_return_null()
    {
        Assert.IsNull(context.Get<IEnumerable<Agreements>>());
    }

    public void Execute()
    {
        this.DataContext_should_return_value();
        this.DataContext_should_return_null();
    }
}

[TestClass]
public class DefaultDataContextTest:DataContextImplementationTestBase
{
    public DefaultDataContextTest()
        :(new DefaultDataContext()){}

    [TestMethod]
    public void Run_tests()
    {
        this.Execute();
    }
}

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


Это как при обучении игре на гитаре сказать — "я уже сто(пусть даже тысячу) раз сыграл гамму, а все еще не могу сыграть Hotel California как Joe Walsh, не буду больше играть гаммы, это пустая трата времени".

Подскажите лучше какую-нибудь литературу по написанию тестов. Прочитал Кента Бэка, понял, что за методология такая. Но как научиться писать сами тесты? Есть сферический метод с параметрами и возвращаемым значением. Должен ли я проверять все граничные условия? Перебирать Декартово произведение аргументов? Как будет выглядеть идеальный тест?
Заголовок спойлера
А статья – какая-то переписка Энгельса с Каутским.
Идеальные тесты (как и все остальное идеальное), в реальном программировании, к счастью, не требуются, так что все эти теоретические изыски можно оставить. Проверили на каких-то входных значениях и ошибочных, если повезет, и уже и релизить пора. Все равно заранее все не предусмотреть, например, если на вход приходит какое-то сообщение из другой системы, то вообще непонятно что там за варианты могут быть, о чем-то и не догадаешься. Все самые интересные тесты будут написаны по результатам разбора логов ночного падения системы.

Неплохая книжка есть https://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052
Еще некоторые приемы описаны в https://www.amazon.com/xUnit-Test-Patterns-Refactoring-Code/dp/0131495054, но она довольно объемная и кажется, не без воды (читал давно).
Мне нравится книга «Clean Code» — она не совсем о тестах, но про тесты там хорошо расписано.
Ещё люди хвалят «xUnit Test Patterns: Refactoring Test Code».

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

Мне нравятся как написаны Принципы, паттерны и методики гибкой разработки Роберта Мартина, там правда код на C#, но в данном случае особенности синтаксических конструкций не очень существенны.


Еще можно читать перевод уже упомянутого здесь Clean code, того же Мартина, в русском варианте Чистый код. Там уже Java.


Там на самом деле много читать не придется, там надо много практиковать прочитанное. Программирование — это навыки, а не знания.

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

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

Интересно, а улучшение производительности по вашему в какую категорию попадает?

В "решение о рефакторинге без добавления новой функциональности"...

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

Бесполезный спор на тему "кому, что кажется правильным". Это не теорема, доказательств нет ни у одной из сторон. Каждый участник подразумевает разные исходные и разные результаты.


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

Остался за бортом вопрос о тестировании тестов.

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


Далее, как описывалось где-то в статье есть два варианта автоматизации сего действа:


  • статический анализ
  • мутационное тестирование
Так TDD для того и нужен, чтобы тесты не надо было тестировать!
Именно эта последовательность: красный тест — код — зелёный тест — и гарантирует, что тест правильный.
TDD не гарантирует ничего правильного, как ни странно. Она лишь выявляет некоторые(sic!) неправильности. Красный тест однозначно свидетельствует, что неверен либо код, либо тест, либо и то, и другое. Но зелёный тест не означает корректность и того, и другого, он может означать как корректность и теста, и кода (для данного кейса), так и некорректность и того, и другого.
Я согласен, слово «гарантирует» здесь не совсем корректно. Впрочем, полной гарантии не даёт вообще ничто на свете, так что споры о гарантиях мне кажутся бесполезными.

Ок, TDD не даёт полной гарантии, но даёт высокую степень уверенности.

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

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

Fesor Ну да, идея примерно та же. Заставить заранее продумать детали -> получить меньше багов.
Было бы интересно взглянуть, как примерно выглядят ваши критерии приёмки. Это какие-то требования в текстовом виде?

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

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


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

Есть еще такая штука как BDD. Там критерии приемки описываются в gherkin формате:


Scenario: User should be blocked if can't remember answers on security questions

Given I forgot my password
  And I try to restore it
 When I fail 3 times to answer security questions
 Then My account should be blocked
  And I should be notified by email

Как по мне детальнее не придумать. И самое крутое тут в том, что этот сценарий не привязан к деталям реализации — чисто бизнес правила и описание того, как это работает. Их может составлять/дополнять вся команда, а не только девелоперы/QA. Вы можете дать их почитать вашим стэкхолдерам/продукт оунеру/менеджеру и получить апрув мол "да, должно работать так". Вы можете даже таргет группе какой эти сценарии почитать дать что бы ранний фидбэк получить.


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


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


Ну и куча еще возможностей в контексте DDD, выделение контекстов, формирование единого языка и все такое.

Fesor Про BDD я, конечно, знаю, и отношусь к нему очень скептически.
Это красивая идея, но на практике она не работает. Никогда бизнес «стейкхолдеры» не пишут эти спеки вместе с разработчиками и QA. В итоге этот «геркин» привносит новые проблемы, а обещания не выполняет.

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

PS. Каким таким волшебным образом использование языка геркин сделает интеграционные тесты очень быстрыми?
Никогда бизнес «стейкхолдеры» не пишут эти спеки вместе с разработчиками и QA

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


В итоге этот «геркин» привносит новые проблемы, а обещания не выполняет.

обещание геркина — specification by example. Ничего более. Просто возможность описывать спецификацию проекта в виде примеров поведения системы, не более.


интеграционные и т.п. тесты никогда не заменят юнит-тестов.

нет, не заменят. Делать 100% покрытие через BDD стоит слишком дорого. И да, отдельные вещи стоит дополнительно покрывать юнит тестами. И да можно через TDD. И BDD ни в коем случае не замена TDD, он его дополняет.


Намного проще проектировать систему сверху вниз, а не снизу вверх.


Каким таким волшебным образом использование языка геркин сделает интеграционные тесты очень быстрыми?

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

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


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

Зарегистрируйтесь на Хабре, чтобы оставить комментарий