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

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

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

Это стиль автора такой

Совет на самом деле должен быть один: если вы хотите самостоятельно разобраться, что такое TDD, то изучайте первоисточники (Кент Бек, Роберт Мартин) и пробуйте эту технику сами, а не полагайтесь на испорченные телефоны в лице анонимных коучей, Егора Бугаенко и автора этой статьи. Изложение тенденциозное и однобокое, начинается с фактических ошибок и тащит эти ошибки через весь текст.


Чисто парочка самых эпичных фейлов для примера:


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

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


  1. В фазе Red: "какое поведение я хочу получить от моего кода?", или "в каком случае код поведёт себя неправильно?". Мы фокусируемся только на вопросах входа и выхода данных в функцию, но не грузим мозг качеством кода, алгоритмами, структурой...
  2. В фазе Green: "какие изменения в коде позволят пройти все тесты?". Мы снова не грузим мозг вопросами чистоты или качества кода, но зато высвободившийся мыслительный ресурс направляем на то, чтобы получить работоспособную для данного этапа версию.
  3. В фазе Refactor: "можно ли улучшить читаемость и понятность этого кода, не меняя его поведения?". Имея на руках работающий код и работающие тесты, мы можем попробовать улучшить их читаемость. BTW, тесты тоже можно и нужно рефакторить, но только сам код при этом должен оставаться неизменным.

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


Изначально зелёные тесты неизбежны

Типичнейшее заблуждение новичка: раз я написал тест, и он сразу зелёный — значит, всё хорошо! Зелёный цвет хороший, красный цвет плохой. Я написал пачку тестов, они все покрашены в правильный цвет — значит, мой тестируемый код работает правильно. А тот, кто требует делать их изначально красными, тот требует соблюдать бессмысленные ритуалы и делать лишнюю работу.


Но смысл здесь очень даже присутствует. Есть много причин, по которой тест может быть зелёным. Например, в нём забыли поставить ассерты. Я видел такое неоднократно. В тесте просто повыполнялся какой-то код и не упал с исключением — вот и славно. Несложно понять, что такой "тест" довольно слабо будет фиксировать поведение кода. Или проверка некорректная: по логике ждём падения в одном месте, а на деле оно происходит совсем в другом (помню случай, когда нашёл у коллеги несколько таких тестов, которые вроде бы покрывали разные кейсы, но на деле дружно ловили совсем другое исключение, одно и то же для всех тестов). Или проверка проходит успешно, но только потому, что не успевает выполниться соседний поток.


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


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


Ну ок, бывает и так, что код уже существует на момент написания тестов. Что тут делать? Временно "сломайте" код, что бы и нет? Хороший способ. Либо временно инвертируйте тестовый ассерт. Тоже годится. В момент написания это обычно сделать гораздо дешевле, чем когда-то потом.


Я мог бы ещё много чего критического написать, но уже достаточно. Главное, что хочется пожелать читателям сего опуса: читайте первоисточники, хоть на английском, хоть в переводе (уже много лет есть). Думайте своей головой. Обязательно практикуйтесь и анализируйте свои впечатления. Это навык, который требует большого объёма практики, но и в ответ даёт многое!

Например, в нём забыли поставить ассерты. Я видел такое неоднократно.

Видимо автор такого теста слишком много думал над ритуалами, но мало над тем, что он делает.


В тесте просто повыполнялся какой-то код и не упал с исключением — вот и славно.

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


Временно "сломайте" код, что бы и нет? Хороший способ.

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


Либо временно инвертируйте тестовый ассерт.

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

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

Ох уж эта самоуверенность молодости теоретика!


Инвертируя ассерт, я проверяю две вещи:


  1. В процессе выполнения теста мы доходим до ассерта
  2. Результат работы ассерта влияет на результат всего теста

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


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

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

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

Читаю этот комментарий со слезой умиления. Разбираешь человеку ситуацию по деталям, пережёвываешь, кладёшь в рот — а он в ответ: "так просто ты должен научиться писать тесты правильно! это же очевидно!". Конечно, очевидно! Я ведь не просто так провёл всю сложную работу, сначала выясняя проблему в коде, а потом подбирая точные и понятные слова, чтобы изложить её здесь.


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


Требования к понятности, читаемости тестов приводят к необходимости прятать детали реализации под капот, и не всегда бывает возможно быстро до них добраться. Да это и не надо в большинстве случаев. Понятность и модифицируемость самих тест-кейсов обычно бывают важнее. Но в некоторых (заранее неизвестных!) случаях это выходит боком, если дополнительно не проверить. Порой и многопоточность оказывается деталью реализации тестов или тестируемой системы. Гораздо проще для таких помнить и применять один простой rule of thumb ("убедись, что тест падает на ассерте"), чем полностью анализировать каждый раз весь flow кода от места вызова до ассерта.


тестирование тестов тоже можно автоматизировать через инструменты мутационного тестирования

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


Может, я что-то не так делал? Просветите, пожалуйста!


Хотя, если не сможете просветить, я всё понимаю. Кидаться какашками в TDD куда проще.


Я, конечно, пороха не нюхал

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

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


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


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


У вас сарказметр барахлит. По поводу тестирования могу порекомендовать: Фрактальное тестирование.

Видимо автор такого теста слишком много думал над ритуалами, но мало над тем, что он делает.

vs


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

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

В первом случае речь шла про "забыл", во втором про "отсутствие асерта". Я же процитировал конкретные реплики, на которые отвечал. Отсутствие асерта — нормальная ситуация. Но забыть его — это всё равно, что забыть голову.

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

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

Вы "проверяете" тест не на ошибках (непреднамеренных дефектах), а на преднамеренных дефектах. Это два почти не пересекающихся множества дефектов.

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

Вот именно, в коде — имплементации ожидаемого поведения, а не в белиберде.

тесты тоже можно и нужно рефакторить, но только сам код при этом должен оставаться неизменным
Если я правильно понимаю TDD, то переход RED->GREEN нужен в том числе и для проверки самих тестов. Если у меня тесты зелёные, затем я их «отрефакторил», а они всё ещё зелёные, появилась ненулевая вероятность, что они будут зелёные даже со сломанным основным кодом.
(подымем тему… пришёл по ссылке из сходной статьи)

> Так что, если в TDD и есть какие-либо правила, которые надо выполнять «ритуально», так это одно: никогда не доверяй тесту, если не видел, как он падает.

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

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

Вот для этой задачи есть несколько методов решения, я знаю с ходу два:

1. Явные инверсии тестов: варьировать их явно помеченным образом так, что с конкретной вариацией тест должен падать. Например, если тест проверяет 2*2==4, подставить на вход 3*2 по определённому условию. Это будет работать там, где допускаются вариации тестов (не везде такое разрешают, потому что многие считают слишком сложно такое читать/поддерживать).
2. Автоматизированные случайные мутации кода как тестируемого, так и тестов. Каждая мутация, что не сломала тест, показывает непокрытый путь исполнения и/или избыточность кода.
Говоря про ломание кода, нельзя не упомянуть про парадокс воронов. Суть его в том, что задаётся вопрос: «Все ли вороны чёрные?». И для ответа на него берутся нечёрные предметы. Например — красные яблоки. И каждое такое яблоко как бы подтверждает тезис о том, что «все вороны чёрные», ведь яблоки не чёрные и при этом не вороны. Что-то тут не так с логикой, не правда ли?

Так что не так с логикой-то? И как правильно доказывать что «все вороны чёрные»?

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

Про Байеса в конце там глупости, конечно, написаны. Любая конечная выборка из бесконечного числа объектов вообще никак не увеличивает вероятность истинности гипотезы.

Хорошо, тогда как доказать, что все «вороны чёрные»? Или даже, что «завтра солнце встанет на востоке, а сядет на западе»?
Заголовок спойлера
А разгадка в том, что число объектов конечно, и ограничено продолжительностью жизни поделённой на скоростью рук. Мы всегда говорим о своей собственной убеждённости, которая зависит от того что доставали из мешка мы сами и люди, которым мы доверяем.
Нет, Дмитрий лишен его пафоса генетически)) Развернуть самолет с порнозвездой, оплатив ей время, потому что код не прошел ревью — так пишет только один человек. Он скорее плохо чувствует тональность своих текстов, поэтому часто чувствуется грубость, где ее не было бы в устой речи.
проблема не в этом (точнее, «по бОльшей части не в этом»), а в том, что пишет он херню. И продолжает это делать даже после того, как ему распелалили его ошибки (хотя и далеко не все)! Рукалицо.

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


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


Ломание теста или кода нужно для того, чтобы проверить утверждение на тавтологичность. Мы заявили, что все вороны черные. Мы не можем написать тест, который полностью это за нас сделает. Но что если в мире вообще нет ни одного нечерного предмета? Ну такое вот у нас загадочное определение черного цвета. Ломаем тест — требуем, чтобы вороны были красными. На черную ворону тест выдает ошибку. Ок, чиним обратно тест, ломаем код. Теперь на красную ворону тест выдает ошибку. По крайней мере из кода может прийти что-то такое, что тест может различать как правильное или неправильное, а не всегда говорит "ок, йа отработал".


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

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


При отсутствии кода тест даже не скомпилируется. Такое себе "усиление".

Это предельный вариант TDD — сначала мы тестируем, что код есть. Тест падает. Пишем заголовок для кода, но не тело. Часть теста проходит, а дальше все равно падает. Пишем захардкоженную константу. И так далее.


http://wiki.c2.com/?TransformationPriorityPremise — иногда я в таком стиле развлекаюсь с кодом.

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

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


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

Слепое следование ритуалам как раз и приводит к тому самому "тяп-ляп и в продакшен". Включение же мозга автоматически исключает бессмысленные действия.

Но мне нравится запускать тесты и видеть как что-то происходит. И кроссворды разгадывать тоже нравится.

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

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


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

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

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

К ТДД коучу, надо полагать?

К психологу

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

Бывает, что думаешь, что знаешь цвет теста, а на самом деле ошибаешься. Пишешь new User, перед тем как создать класс User а оказывается где-то в зависимости он уже есть и умная IDE его и портировал незаметно.

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


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


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

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

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

Ну вот я написал тест, который никогда не падал:


assert( Math.pow( 2, 2 ) === 4 )

Вы ему не доверяете?

Это не тест, это некий ассершн.
Вот если бы это было что-то вроде...


def test_uber_method_returns_zero_when_no_users_online(self):
    self.assertEqual(2*2, 4)

Это тест. И вы не ответили на вопрос.


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

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


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

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

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

Там, где светло, искать ключи, конечно, проще, чем там, где потерял. Правильно ли я понимаю, что примера правдоподобного кода теста, где ритуал бы чем-то помог, от вас ждать не стоит?

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

Там не коллбэки, это в espresso так ассерты выглядят. А проблема заключалась в том, что селекторы onView и onData имеют разное применение, официально рекомендовался именно onData с очень размытым объяснением почему именно он. Но для данного конкретного теста требовался именно onView.

Проблема скорее в кривом фреймворке тестирования, где асерты неявно приводят к сайдэффектам.

Асерты нельзя вызывать в колбэках.

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

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

Чтобы это понять достаточно включить голову.

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


Ну или учиться у того, у кого голова включена, а не у того, кто учит красить тесты в разные цвета.

… а вы почему-то считаете, что это взаимоисключающие вещи.


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

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

Ну если по этой дорожке пойти дальше, можно оказаться в пункте

Хороший тест не делает ничего сложного, в отличие от тестируемого кода.


Это значит, что у вас не хватает теста на то, что колбек вызывается.

Тогда вам придётся писать минимум в 2 раза больше тестов: один для проверки, что колбек вызывается при заданных GIWEN+WHEN, а второй проверяющий собственно THEN. Смысла в этом нет, если можно можно просто написать нормальный тест.

Тогда вам придётся писать минимум в 2 раза больше тестов

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

Обязательно. В общем случае вы не знаете от каких действий внутри теста зависит будет колбэк вызван или нет.

Обязательно.

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


А вот если коллбек не под моим контролем, тогда может быть два теста — это как раз хорошо, чтобы каждый из них был попроще?

Конечно, не доверяю. В каком контексте выполняется этот тест? Это IDE, блокнот, консоль браузера? Я точно получу информацию о фейле, когда тот произойдёт? Или, может, у меня упадёт билд, когда я его запущу? Или упадёт настройка и запуск тестируемого приложения?


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


def it_works():
    assert False

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

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

Я вот точно не доверяю. В основном потому, что я понятия не имею, что он тестирует и зачем.

Статья из цикла «слышал звон, да не знаю где он».
Аргументы противников статьи тоже пока что сводятся к слепому почитанию авторитетов и выполнению заведомо ритуальных действий. По степени ненахождения звона это твердое 1:1.
Кроме того, в ситуации, когда контракт вам известен заранее, вы можете сразу написать все тесты, а потом уже весь код, который им соответствует. Формально, это будет не TDD, вы не будете менять код после добавления каждого теста.


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

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

Неверное понимание TDD. Оно (она?) не про то, чтобы покрыть код тестами для всех сценариев,. Нет ни цели такой, ни свойства у него. Оно про то, что прежде чем менять, логику кода, нужно написать тест, который падает на отсутствующем или старом коде Покрытие некоторых сценариев в итоге — бесплатный бонус.


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

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


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

Вы повесили ярлык «ритуал» и пытаетесь всё показывать в отрицательном контексте.

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

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

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


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

Это неверное умозаключение. В статье я объяснил почему.


Лично мне оно жизнь спасало не раз.

Обратите внимание, что я нигде не апеллирую к личному опыту.

я нигде не апеллирую к личному опыту

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

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

тдд — методология написания тестов

Вообще-то нет. Даже в названии видно "Test-driven development". Или в Википедии: "Test-driven development (TDD) is a software development process".

На заборе тоже много чего написано. По сути же это совмещение тестирования с разработкой. При этом тестирование первично.

На заборе тоже много чего написано. По сути же это совмещение тестирования с разработкой.

Это не "по сути", это как вы это прочитали. Способов определить, чье прочтение "более верное", я не вижу.

Как вы определяете основную задачу TDD?

Оно (она?) не про то, чтобы покрыть код тестами для всех сценариев,.

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

как вы рефакторите без полного покрытия тестами.

Ну как-как, берем и рефакторим. Ну да, это не самый надежный в мире процесс, но иногда это необходимо.

По разному. Самое простое — средства IDE, покрытые, надеюсь, тестами :)

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

Каким образом он может быть применим?

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

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

Так и бесполезность TDD не связана с его полезностью :)

Я это и имел ввиду. Следите за руками. :) В парадоксе сначала говорят про всех черных. А потом вводят утверждение для всех остальных через отрицание (не-черных). Далее говорят, что не-черный это, допустим, красный. Т.е. красный здесь это отрицательный пример.

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

На тему проблем классической логики с отрицанием рекомендую глянуть это видео: https://www.youtube.com/watch?v=9kPxFtRefac

Как всегда хорошая по форме презентация. Контент комментировать не буду.

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


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


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

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

Публикации

Истории