среда, 14 сентября 2011 г.

Why test first?

TDD != XP; TDD in XP;

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


Преимущества:

  1. Потом равносильно никогда (многие комманды срывались, когда тесты надо было писать после кода, так как фичи и change request'ы имеют свойства никогда не кончаться). Плюс под давлением повышается риск пробуждения свиньи в себе и программисты опять перестают писать тесты.  Итого, замкнутый круг - из-за стресса мы пишем меньше тестов, что приводит к росту ошибок, рост ошибок приводит к росту стресса. Еще плачевнее результат, когда написание юнит тестов доверяют другому отделу или отдельному программисту (ребята, мне вас жаль). Мало кто пишет код и думает о том, а как собственно говоря, тестировать все это хозяйство. Опять получаем замкнутый круг - чтобы написать тесты нужен рефакторинг, а чтобы сделать рефакторинг нужны тесты.
  2. It's more funny to code with test first. TDD позволяет привнести игровой элемент в работу программиста (ага, вот он неработающий тест, сейчас мы заставим его пройти). Ни для кого не секрет, что программисты это не рабочие у станка и мы не можем выдавать 150 деталей в час (фичи/в день). Вдобавок, эта методология позволяет избавиться от прокрастинации, так как TDD позволяет двигаться маленькими шажками (пресловутый red-green-refactor) и сдвинуться с мертвой точки.
  3. Testability Design по определению. Как говаривал Кент Бек, если трудно тестировать, значит  есть архитектурные проблемы. Test first заставит вас разделять зависимости, разгружать классы, убивать синглтоны и т.д. и т.п.   
  4. You are the first client of own code. Можете сразу ощутить, насколько удобно пользоваться собственным же творением)). Помогает избавиться от невразумительных API.
  5. Выпиливание непокрытых ветвей кода. Ведь если каждая новая ветка кода пишется после теста, то так называемый branch coverage = 100%. (По статистике самое большое число ошибок приходится на if операторы).
  6. Нечасто, но бывает, что test first позволяет обнаружить, что тест написан с ошибкой. Пример: приходит change request, пишем сначала тест, запускаем - получаем зеленую полоску( а должны были красную) => в тесте что - то не так( либо кто-то уже сделал данную функциональность без тестов, ай я яй :-) ).
  7. Позволяет большинству программистов еще раз переосмыслить ООП и понять, что ООП это не только совокупность определений, как говаривал наш преподаватель.
  8. Пишется минимальное количество кода, что уберегает разработчиков от overdesign. Что происходит с кодом, который пишется впрок, но не используется в проекте? Он никогда не удаляется.  Код на будущее, написанный даже не потому, что по ТЗ он понадобится, а потому, что может пригодится на случай атомный войны убивает проект, производит расфокусировку, замутняет контекст, мешая разобраться, а что собственно говоря здесь важно, а что нет, увеличивая нагрузку на мозг программиста. Главный императив программирования - борьба со сложностью. (cм. Макконнелла). TDD - это достаточно простой способ получить "чистый код, который работает".
  9. Если уж вы и собираетесь писать тесты (а вы же собираетесь? :-) )для вашего кода, то вы ничего не потеряете, поменяв местами написание тестов и написание кода.  
Недостатки :


  1. Высокий порог вхождения. Ну очень высокий - замедление работы колоссальное на первых порах (в 2 - 3 раза). 
  2. Необходимость стукнуться головой об стену))
  3. Из-за пункта 1 малое число практикующих TDD.
  4. В некоторый средах цикл red-green-refactor увеличивается из-за невразумительной IDE (привет, XCode!). 
  5. Неприменимость в процедурно-ориентированных языкак, низкоуровневом системном программированим (опыт имею нулевой минимальный, true юникс хакеры отзовитесь, поделитесь опытом в комментах, как вы тестируете свой код?).
  6. Может сложиться ощущение, что TDD это серебряная пуля.
Что почитать и посмотреть по теме:
  1. TDD By Example Кента Бека. 300 страниц легкочитаемого с юмором написанного текста про TDD, TDD и дизайн, паттерны и приемы TDD.
  2. Первой книги должно хватить, но ненасытные могут насладиться "Рефакторингом с использованием шаблонов" от Джошуа Кириевски. http://www.ozon.ru/context/detail/id/2909721/. Это, наверное, первая книга которая подняла такую проблему как overdesign и гибельность оного для проектов.
  3. Крайне классная презентация Ten Ways to improve your code http://www.infoq.com/presentations/10-Ways-to-Better-Code-Neal-Ford. Именно в ней появился тезис first client of your code.

16 комментариев:

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

    ОтветитьУдалить
  2. Наверно стоит все-так добавить что я не согласен с Беком только когда речь идет не об внешних API модулей. Тут он абсолютно прав.

    ОтветитьУдалить
  3. очень сформулированы мысли. Буду рад, если таких постов будет больше :)

    ОтветитьУдалить
  4. Почему не имеет? Обычно это значит, что тестируемый класс имеет много зависимостей или не была выделена дополнительная абстракция, которую можно стабить - допустим, источник данных не был выделен в абстракцию(классический паттерн DAO). Из-за этого тесты для компонента зависят от базы - на лицо запах, логика получения данных размазывается в коде. Рассмотрим другую ситуацию - для класса, который тестируем, необходимо проставить кучу зависимостей в виде других объектов - это сигнал, что компонент перегружен и выполняет много ему несвойственной работы, т.е. нарушается принцип Single Responsobility. Имхо сложность тестирования и запахи кода имеют свойства кучковаться.

    ОтветитьУдалить
  5. Success примеры не являются доказательством, об этом я и говорю. Это только мнение, и опираться на него как то страшно)) Люди имеют свойство ошибаться.

    ОтветитьУдалить
  6. Паттерны проектирования тоже не имеют математического обоснования)). Паттерн считается паттерном, когда его независимо изобрели три человека. Это тоже своего рода просто эмпирические приемы.

    ОтветитьУдалить
  7. Порог вхождения все перечеркивает. Знаю кучу проектов, где тестирование не применяется и все хорошо. Зачастую заказчик не готов платить в 1,5 раза больше, если уже видит стабильный продукт, чтобы он стал еще "стабильнее и гибче". Джуниорам не доверишь кодитьв таком стиле, ибо тесты будут писать ради тестов(есть конечно исключения, но в целом это так).
    А по посту -отличный пост, квинтессенция тех мыслей, которые обсуждались в последнее время, спасибо.

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

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

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

    Alex, что думаешь о том, что я написал?

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

    ОтветитьУдалить
  9. насёчт заказчиков, которые не хотят платить за TDD:
    это очень частый довод, который приходиться слышать.
    Но ведь заказчика никто не справшивает - "надо ли нам в проекте использовать в данном месте паттерн стратегия или мы обойдёмся просто swith-case кострукцией на 5 страниц?"
    спрашивать у заказчика "надо ли нам использовать TDD?" - это примерно тоже самое.

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

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

    ОтветитьУдалить
  10. Надо со школы брать людей и обучать TDD - в институте уже плохому научат на младших курсах)))

    ОтветитьУдалить
  11. не, ну в принципе можно и со школой какой-нибудь найти контакт и в ней набрать небольшую группу... эксперимент получиться намного более правильным, да)

    ОтветитьУдалить
  12. Рекоммендую СМАЛ - там светлые головы учатся

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

    Вывод - TDD редкая опциональная фича для хороших инженеров и проектов.

    ОтветитьУдалить
  14. В средние века люди не мылись, не пользовались развитой медициной и высокими технологиями - но ничего, жили же! И обходились без всех благ цивилизаций!

    Вывод - все достижения человечества, что мы сейчас имеем, опциональны.

    Все - желчь вышла)))

    ОтветитьУдалить
  15. Виталий спрашивал о применимости TDD в процедурном коде. Готовясь к SamaraPy я решил проверить, каково это писать процедурный код на Python через тесты — не менее продуктивно, но есть свои заморочки из-за глобальной области видимости и области видимости переменных.

    Но на этом я не остановился и решил почитать про Embedded-системы, в которых используется C, и нарыл замечательную книгу — http://www.le.ac.uk/eg/mjp9/pes1ohp_a4.pdf
    Просмотрев отдельные интересные места (а они действительно интересны, т.к. практического опыта работы на таком низком уровне никогда не было), пришел к возможно поспешному выводу, что TDD там и не нужно особенно. Гораздо больше профита от покрытия тестами спецификаций и даташитов, а также «узлов», в которых происходит общение со сторонними библиотеками, кодом или системами. К примеру, если программа пишется под несколько моделей контроллеров, имеет смысл написать программу-тестировщик, которая проверит, что основные процедуры корректно выполняются на железке.
    Но писать тесты перед кодом как-то страшновато для очень низкоуровневых штуковин. По той причине, что фактически тесты будут эмулировать реальную среду выполнения кода, и сама по себе эмулированная среда выполнения очень сложна и может не учитывать многие факторы, которые реально имеют значения для реального устройства. Если тестировать сугубо программную низкоуровневую логику, то кажется, что код тестов будет фактически дублировать основной код, что кажется нерациональным. С высокоуровневой логикой, которая оперирует уже состояниями и процессами, а не единичными процедурами, изменяющими состояния, все работает точно также.
    Мы пишем тесты прежде всего потому, что имеем дело с «черными ящиками», неизвестное поведение которых порождает неоднозначности кода, который пишем. Здесь же таких неоднозначностей, кажется, меньше, либо они имеют совершенно другие масштабы. Возможно эти неоднозначности могут быть решены на уровне статического анализа кода, не обязательно в ходе отладки. А другие, которые приводят к отказам бортового ПО в космосе, возможно воспроизвести только в специальной среде и может быть в таких условиях, которые свести к testcase'ам может быть не реально, не имея подобного опыта ранее.

    Да и банально Embedded приложение меняется гораздо медленнее, чем любое корпоративное. Непонятно, вообще можно ли там применять Agile в разработке.

    А какие у вас рождаются домыслы, читая про Embedded C? Может устроим на эту тему XP-party?))

    ОтветитьУдалить