Pull to refresh

Comments 25

вы пишите провальный тест без написания кода.

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


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

Как же тут не беспокоиться, если ранее мы не смогли написать красных тестов, оставив не все тестовые сценарии покрытыми?

> Что делать, когда ты не можешь написать ни одного красного теста, так как код сейчас работает верно, но тестовые сценарии покрыты определённо не все?

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

Выделяю, то, что вы не заметили:


Но что делать со вторым, третьим, десятым? Что делать, когда ты не можешь написать ни одного красного теста, так как код сейчас работает верно, но тестовые сценарии покрыты определённо не все?

Вы можете писать тесты на каждое входное значение

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


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

Ну, давайте и я выделю для вас то, что вы не заметили:


Как же тут не беспокоиться, если ранее мы не смогли написать красных тестов, оставив не все тестовые сценарии покрытыми?

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

Мне казалось очевидным, что ответ на второй вопрос: "после ответа на первый, второй вопрос не имеет смысла".


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

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

Каким образом лишь частичное написание тестов даёт уверенность в понимании требований?


решает проблему низкого покрытия кода тестами

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


Есть ещё "бесплатные плюшки" у TDD

О которых вы нам не расскажете?

даёт уверенность в понимании требований?

"уверенность" понятия субъективное. Суть в том что бы разработчик начал формулировать требования ДО написания кода. Если уверенности в понимании требований нет — пишите больше тестов, есть уверенность в понимании — возможно тестов уже достаточно.


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


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


решает проблему низкого покрытия?

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


О которых вы нам не расскажете?

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


p.s. я не очень понимаю вашу позицию. Вы хотите затеять очередной холивар нужен ли TDD? А зачем? подход не претендует на универсальность а его плюсы, минусы и прочее разжеваны в 5-ти часовом холиваре DDH, Кента Бэка и Фаулера (Is TDD Dead). Есть еще TDD vs DbC с дядей Бобом и Джимом Копленом.


Ну либо накиньте что-то конкретное.

Суть в том что бы разработчик начал формулировать требования ДО написания кода.

Не просто формулировать требования. А формулировать их формально. В чём преимущество перед пост-формализацией?


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

Что именно является способом борьбы со страхом внесения изменений? Формулирование требований до написания кода или всё же полнота покрытия тестами?


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

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


тесты в TDD это та самая "бесплатная" вещь.

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


Вы хотите затеять очередной холивар нужен ли TDD?

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

В чём преимущество перед пост-формализацией?

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


Формулирование требований до написания кода или всё же полнота покрытия тестами?

В комплексе. Но больше не столько "физическая" полнота покрытия, сколько уверенность, в том что требования покрыты тестами, и если тесты после изменений не падают, то значит требования всё ещё выполняются. А 100% кода тестами покрыто или 1% — не суть.


Но как это может обеспечить достаточное покрытие хотя бы классов эквивалентности?

Если требования покрывают классы эквивалентности в терминах бизнес-требований, то и тесты их покроют в этих терминах.


Какая ж она бесплатная, если вы на их написание требуется время и силы? И тратится независимо от того пишутся ли тесты до или после.

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


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

Практика эту гипотезу обычно опровергает. Одним из главных плюсов TDD и ко как раз считается, что они форсят создание легкотестируемого кода (вернее простых для тестирования API), если разработчики не мазохисты :) Вам, наверное, известно, какая мука покрывать тестами большинство легаси кода, особенно юнит-тестами.

В чём преимущество перед пост-формализацией?

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


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


Что именно является способом борьбы со страхом внесения изменений?

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


Но как это может обеспечить достаточное покрытие хотя бы классов эквивалентности?

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


Какая ж она бесплатная, если вы на их написание требуется время и силы?

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


Тут стоит сделать ремарку — если вы УЖЕ можете делать более изолированные модули, вы УЖЕ умеете писать тесты неплохо, вы понимаете как определять их качество и т.д. то ценности от TDD для вас возможно нет.


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

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


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

Каким образом лишь частичное написание тестов даёт уверенность в понимании требований?

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


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

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


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

Про частичное написание тестов я ничего не говорил.

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


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

Вы очень непонятно написали.


Тестовые сценарии у QA, мы работаем с требованиями и не покрываем их, а переводим на формальный язык тестов

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


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

Опять голословные лозунги в стиле Геббельса. Вы можете обосновать каким образом написание тестов до кода на всё это влияет?

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

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


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

Пишем первый тест, что div(4,2) === 2 — красный
Пишем наивную имплементацию return a/b — зелёный;
Пишем второй тест, что div(4,0) бросает исключение — зелёный


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


Вы можете обосновать каким образом написание тестов до кода на всё это влияет?

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

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

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


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

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

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


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


но тестовые сценарии покрыты определённо не все?

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

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

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

Вы вот правильные вещи пишете, но к TDD они имеют мало отношения :-) Зелёный тест — это по факту написание теста после реализации, что не является TDD по определению.


Важно что бы вы не добавлялии в реализацию вещей для которых требования в тестах не закреплены.

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

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

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

Вы предлагаете написать дополнительный код, чтобы сломать тест, а потом его удалить, чтобы починить? И Что вам даст эта карго-практика?


Давайте на более конкретном примере..


Первый тест красный:


assert( toGrid( 22.5 ) === 20 ) )

Реализация и тест зеленеет:


function toGrid( x : number ) {
    return Math.floor( x / 10 ) * 10
}

Второй тест сразу зелёный:


assert( toGrid( -22.5 ) === -30 ) )

Ваши действия?

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

То есть вы сразу пишете все тесты, а потом всю реализацию? Мужественно. Но вы уверены, что такой подход можно назвать TDD?


CI не даёт просто написать тест

Вы не думали, что такую CI стоит сразу выкинуть?


обернул бы, например, в abs всё, написал бы тест, он сломался, закоммитил, а потом удалил abs и получил бы зелёный

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


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

Ну, что-то он тестирует. А то ли он тестирует? Тестирует ли он что-то помимо отсутствия того дефекта, что вы вручную добавляли? Например, тавтологический тест:


assert( toGrid( -22.5 ) === toGrid( -22.5 ) ) )

И добавленный карго-дефект:


function toGrid( x : number ) {
    if( x < 0 ) return Number.NaN
    return Math.floor( x / 10 ) * 10
}

Перестанет ли тест быть тавтологическим, если мы последовательно добавим и удалим сей дефект?

> Вы не думали, что такую CI стоит сразу выкинуть?

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

> Но вы уверены, что такой подход можно назвать TDD?

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

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

Всё зависит от процесса, но написание заведомо зелёного теста к TDD отношения не имеет, по-моему — обычное покрытие тестами готового кода.

> Перестанет ли тест быть тавтологическим, если мы последовательно добавим и удалим сей дефект?

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

Вы правильно заметили на тему "мужественности". Но все же от "все тесты" это все очень далеко. Я как-то общался с человеком которому не нравилось TDD потому что он месяц тесты писал и только потом писал реализацию. Сами понимаете как это не эффективно.


На тему же "мужественности" — для простых задач можно сразу накидать несколько тест кейсов (триангуляция, в терминах Кента Бэка) и потом реализовывать код. И чаще всего вы врядли захотите двигаться совсем уж маленькими шагами. А вот если вы написали несколько тестов и не можете написать код — всегда можно убрать пару тестов и двигаться более маленькими шажками. Тут все очень субъективно. По сути основной момент — насколько вы уверены. Не уверены — сокращайте длину итераций.


Вы уверены, что стоит тратить рабочее время на подбор такого намеренно дефективного алгоритма?

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


да, довольно часто это все избыточно (опять же смотрим про уверенность), но если уверенности нет — имеет смысл.


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


Ну, что-то он тестирует. А то ли он тестирует?

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

почему то поведение которого вроде бы небыло (иначе зачем тест) — работает.

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

Sign up to leave a comment.

Articles