Размышления о TDD. Почему эта методология не получила широкого признания

https://medium.freecodecamp.org/8-observations-on-test-driven-development-a9b5144f868
  • Перевод
Привет, Хабр!

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

Поэтому мы решили не только вновь напомнить, что ищем такого человека, но и предложить перевод достаточно дискуссионной статьи, автор которой, Дуг Аркури (Doug Arcuri), делится собственными соображениями о том, почему TDD так и не стала мейнстримом. Давайте обсудим, прав ли он, и если нет — почему.



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

Легендарный программист Кент Бек – автор методологии TDD (разработка через тестирование) в ее современном понимании. Кент также вместе с Эрихом Гаммой участвовал в создании JUnit, широко используемого тестировочного фреймворка.

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

[KISS, Quality, YAGNI, ...] + [Testing, Specs, ...] == [TDD, ...]

Я глубоко уважаю эту работу, которая является для Кента делом всей жизни – не только за созданные им шедевры в области программирования, но и за то, что он без устали исследует суть доверия, смелости, отдачи, простоты и уязвимости. Все эти атрибуты оказались незаменимы для изобретения Экстремального Программирования (XP).

TDD – это принцип и дисциплина, которой придерживаются в сообществе XP. Этой дисциплине уже 19 лет.

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

TDD, исследования и профессионализм

Вот уже 19 лет дисциплина TDD – предмет споров в программистском сообществе.
Первый вопрос, который бы вам задал профессионал-аналитик – «каков процент разработчиков, которые сегодня пользуются TDD»? Если бы вы спросили об этом какого-нибудь друга Роберта Мартина (дядюшки Боба) и друга Кента Бека, то ответ был бы «100%».

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

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

Однако, никто не задает следующего вопроса: «ведь практиковать – означает «сознательно использовать» — но ведь о процентном соотношении оно судить не позволяет, так?» На мой субъективный взгляд, большинство программистов не занимались TDD даже в течение какого-либо символического периода.

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

Если мы не знаем, сколько же компаний практикует TDD, то возникает следующий вопрос: «Насколько TDD эффективна, если судить по ее измеримым достоинствам»?
Вероятно, вас обрадует, что за эти годы был проведен ряд исследований, подтвердивших эффективность TDD. Среди них – безусловно авторитетные отчеты от Microsoft, IBM, Университета Северной Каролины и Университета Хельсинки.



Выразительная диаграмма, взятая из отчета Хельсинского университета.

В определенной степени эти отчеты доказывают, что плотность ошибок удается снизить на 40-60%, для чего требуется работать усиленнее; время выполнения при этом возрастает на 15-35%. Эти числа уже начинают прослеживаться в книгах и новых промышленных методологиях, в частности, в сообществе DevOps.

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

1. TDD требует вербализировать подход

Практикуя TDD, мы начинаем сталкиваться с феноменом «обозначения цели». Проще говоря, такие краткие проекты, как подготовка провальных и успешных тестов – серьезный интеллектуальный вызов для разработчика. Разработчику приходится четко артикулировать: «считаю, что этот тест пройдет успешно» и «считаю, что этот тест будет провален» или «Я не уверен, позвольте поразмыслить после того как я попробую этот подход».

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

Сначала подумайте — а потом сделайте ваш следующий шаг (или шаги).

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

2. TDD прокачивает моторную память

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

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

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

3. TDD требует хотя бы немного продумывать свои действия наперед

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

Усаживаясь за работу и заканчивая очередной «присест» — также старайтесь сделать из этого ритуал. Сначала думайте и перечисляйте. Играйте с этим. Еще перечисляйте. Затем приступайте, делайте, думайте. Отмечайте. Повторите несколько раз. После чего еще раз подумайте и остановитесь.

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

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

// Список тестов
// "" -> не проходит
// "a" -> не проходит
// "aa" -> проходит
// "racecar" -> проходит
// "Racecar" -> проходит
// вывести валидацию
// отведать черничного эля

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

4. TDD зависит от коммуникации с коллегами

После того, как вышеприведенный список будет заполнен, некоторые шаги могут оказаться заблокированы, поскольку на них не вполне ясно описано, что делать. Разработчик не разобрался в списке тестов. Случается и обратное – составлен слишком сырой список, в котором очень много допущений по поводу пока не сформулированных требований. Если у вас получается нечто подобное – сразу остановитесь.

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

Если видите, что списке тестов есть пробелы – встаньте и громко об этом скажите.

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

5. TDD требует итерационную архитектуру

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

Разумеется, выстраивать архитектуру на основе тестов нерационально. Сам дядюшка Боб согласился с другими экспертами в том, что это никуда не годится. Требуется более обширная карта, но не слишком далеко отстоящая от тестовых списков, которые вы разрабатывали «в полевых условиях».

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

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

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

6. TDD выявляет хрупкость модульных тестов и вырожденную реализацию

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

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

// Не несовершенный палиндром
@Test
fun `Given "", then it does not validate`() {
    "".validate().shouldBeFalse()
}

@Test
fun `Given "a", then it does not validate`() {
    "a".validate().shouldBeFalse()
}

@Test
fun `Given "aa", then it validates`() {
    "aa".validate().shouldBeTrue()
}

@Test
fun `Given "abba", then it validates`() {
    "abba".validate().shouldBeTrue()
}

@Test
fun `Given "racecar", then it validates`() {
    "racecar".validate().shouldBeTrue()
}

@Test
fun `Given "Racecar", then it validates`() {
    "Racecar".validate().shouldBeTrue()
}

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

// Слишком обобщенная реализация, сделанная на основе предоставленных тестов
fun String.validate() = if (isEmpty() || length == 1) false else toLowerCase() == toLowerCase().reversed()
// Это наилучшая реализация, решающая все тесты
fun String.validate() = length > 1
length > 1 

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

Вопрос в том – когда разработчик должен прекратить писать тесты? Ответ кажется простым: когда этого достаточно с точки зрения бизнес-логики, а не по мнению автора кода. Это может уязвить нашу конструкторскую страсть, а простота может кого-то выбешивать. Эти чувства компенсируются удовлетворением при виде собственного чистого кода и пониманием, что впоследствии код можно будет уверенно рефакторить. Весь код получится очень аккуратным.

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

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

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

7. TDD демонстрирует обратный цикл выполнения тестовых утверждений

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

// Вывести числа от 9 до 15. [OK]
// Для чисел, кратных 3, вывести Fizz вместо числа.
// ...

Прошли на несколько шагов вперед. Теперь наш тест проваливается.

@Test
fun `Given numbers, replace those divisible by 3 with "Fizz"`() {
    val machine = FizzBuzz()
    assertEquals(machine.print(), "?")
}

class FizzBuzz {
    fun print(): String {
        var output = ""
        for (i in 9..15) {
            output += if (i % 3 == 0) {
                "Fizz "
            } else "${i} "
        }
        return output.trim()
    }
}
Expected <Fizz 10 11 Fizz 13 14 Fizz>, actual <?>.

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

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

8. TDD демонстрирует условие очередности преобразований

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

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

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



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

Это и есть Условие очередности преобразований. По-видимому, есть некий порядок риска рефакторинга, на который мы готовы выйти по прохождении теста. Обычно лучше всего выбирать вариант преобразования, показанный в самом верху списка (простейший) – в таком случае вероятность попадания в тупик остается минимальной.

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

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

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

Закрепилась ли TDD на практике?

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

Причина 1: Недостаточный контакт с реальной культурой тестирования

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

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

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

Причина 2: Дефицит образовательных ресурсов

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

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

Причина 3: Темой пренебрегают в университетах

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

Причина 4: Требуется сильная увлеченность тестами и стремление заниматься ими

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

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

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

Заключение

Формулировка XP от Кента – это простое сочетание чутья, мышления и опыта. Три этих уровня – как ступени к достижению того уровня качества, который определяется как порог. Именно эта модель отлично объясняет проблему с TDD.

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



Из книги XP Explained. Изначально эта схема иллюстрировала качество проектирования, поэтому представьте, что пороговый уровень еще выше.

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

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

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

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

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

@Test
fun `Given software, when we build, then we expect tests`() {
    build(software) shoudHave tests
}

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

Издательский дом «Питер»

226,00

Компания

Поделиться публикацией
Комментарии 61
    0
    «В TDD подобное необходимо из-за повторения действий» — что-то тут не так. При правильной организации труда повторения действий программиста должны быть минимальны — пусть повторением компьютер занимается. (и вроде с правильным инструментом так и получается, разве нет?)
      0
      Тут же имелось ввиду — red, green, refactor. Т.е. процесс, а не повторение одних и тех же действий.
      +1
      Что-то слишком много воды и очень абстрактных размышлений без конкретики.
      Может именно поэтому TDD не заходит людям?
        0
        А может он не заходит, потому что не обеспечивает необходимую производительность труда сравнительно с классическим подходом, в котором юнит-тесты пишут после кода?

        Лично я пишу до написания кода только функциональные позитивные тесты, чтобы упростить тестирование разрабатываемого куска кода. Юнит-тесты — только после кода и только на сложные участки. Ведь даже 100% покрытие не спасет от ошибок, связанных с интеграцией компонентов.
          0
          > Ведь даже 100% покрытие не спасет от ошибок, связанных с интеграцией компонентов.

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

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

          0
          Хотелось бы спросить автора и его дядюшку, почему они уверены, что их тесты корректны? И кто тогда будет писать тесты, тестирующие тесты? А тесты, тестирующие тесты, тестирующие тесты? Ну и так далее.
          Как тестировать приватные методы? А методы с побочными эффектами? Я пытался узнать у тех, кто не испытывает неприязни к ТДД, и получил ответ, что никак. Какой тогда в этом смысл? Большая часть функционала обычно бывает скрыта, и тестировать публичные методы, предполагая, что приватные выдадут корректный результат, как-то несерьезно. Получается, что покрытие тестами будет достигать в лучшем случае процентов тридцать (чаще намного меньше).
          Думаю, что я могу ответить на вопрос, почему эта методология не получила широкого признания. Потому что она бессмысленна. Люди не хотят тратить свое время на то, что заведомо не сработает или даст мизерный выхлоп.
            +2
            Хотелось бы спросить автора и его дядюшку, почему они уверены, что их тесты корректны?

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


            И кто тогда будет писать тесты, тестирующие тесты? А тесты, тестирующие тесты, тестирующие тесты? Ну и так далее.

            Кто тестирует тех кто тестирует вручную?


            Как тестировать приватные методы?

            Вызовом публичных?


            А методы с побочными эффектами?

            Проверкой на наличие побочных эффектов?


            Я пытался узнать у тех, кто не испытывает неприязни к ТДД, и получил ответ, что никак.

            Про тесты написаны кучи книжек — попробуйте почитать хотя бы одну.


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

            Если у большая часть функционала скрыта, это значит что у вас god object или подобный антипаттерн. Надо рефакторить — разделять уровни абстракции и их тестировать отдельно.


            Получается, что покрытие тестами будет достигать в лучшем случае процентов тридцать (чаще намного меньше).

            А как вы тестируете вручную — не покрываете приватные методы?


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

            Люди не хотят тратить время на то, чтобы хотя бы погуглить

              0
              Как тестировать приватные методы?
              Вызовом публичных?

              У вас может быть 3 приватных последовательно вызывающих друг друга метода. В каждом есть, допустим, 3 ветки логики. Если тестировать через публичный интерфейс, то нужно проверить 3х3х3 = 27 кейсов. Помножьте на число граничных условий и будет совсем ахтунг.


              Если у большая часть функционала скрыта, это значит что у вас ~~god object ~~

              Инкапсуляция.


              Надо рефакторить — разделять уровни абстракции и их тестировать отдельно.

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

                0
                У вас может быть 3 приватных последовательно вызывающих друг друга метода. В каждом есть, допустим, 3 ветки логики. Если тестировать через публичный интерфейс, то нужно проверить 3х3х3 = 27 кейсов. Помножьте на число граничных условий и будет совсем ахтунг.

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


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


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

                Это не делается механически. Если что-то хочется протестировать отдельно, надо подумать почему — представляет оно отдельную концепцию или нет. Условно, если класс Клиент содержит код проверки счета, надо рассмотреть возможность создать класс Счет, перенести код проверки туда и протестировать отдельно.


                Т.е. выделение дополнительного слоя абстракции со своей инкапсуляцией.

                  0
                  Если ветка в приватном методе вызывается независимо от веток в вызывающем методе

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


                  если вы действительно настаиваете на тестировании всех кобинаций

                  Нет, я настаиваю на компонентном тестировании (Подробнее об этом тут: https://habr.com/post/351430/). Каждый из этих приватных методов (а точнее их видимость придётся повысить) тестируется в предположении, что вызываемые им методы уже протестированы. Это даст 3+3+3 = 9 кейсов.


                  Если что-то хочется протестировать отдельно, надо подумать почему — представляет оно отдельную концепцию или нет.

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

                    +1
                    Вы сейчас говорите про тестирование белого ящика

                    Я сейчас говорю о TDD. У меня складывается впечатление, то вы сейчас пытаетесь критиковать процесс, про который не знаете даже теоретически


                    Каждый из этих приватных методов (а точнее их видимость придётся повысить) тестируется в предположении, что вызываемые им методы уже протестированы. Это даст 3+3+3 = 9 кейсов.

                    Расскажите, как TDD сделает из этого 27 кейзов.


                    Каждый из этих приватных методов (а точнее их видимость придётся повысить)

                    Ээээ? Вы ранее тут что-то говорили при нарушение инкапсуляции?


                    нужно определить ширины слов

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

                      0
                      У меня складывается впечатление, то вы сейчас пытаетесь критиковать процесс, про который не знаете даже теоретически

                      Похоже вы вкладываете в понятие ТДД существенно больше, чем все остальные. Не поделитесь с нами вашим видением что же такое ТДД на самом деле?


                      Расскажите, как TDD сделает из этого 27 кейзов.

                      ТДД-то ту при чём? Это следствие предлагаемого вами косвенного тестирования.


                      Вы ранее тут что-то говорили при нарушение инкапсуляции?

                      А чем инкапсуляция от сокрытия отличается знаете?


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

                      Что вы там тестировать в слове собрались? Константы?

                        +1
                        Похоже вы вкладываете в понятие ТДД существенно больше, чем все остальные.

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


                        ТДД-то ту при чём?

                        https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html


                        A few years later this fine granularity was codified into three rules: the so-called Three Laws of TDD.
                        1. You must write a failing test before you write any production code.
                        2. You must not write more of a test than is sufficient to fail, or fail to compile.
                        3. You must not write more production code than is sufficient to make the currently failing test pass.

                        Как только вы покроете ваши 9 случаев у вас не получится написать красный тест.


                        А чем инкапсуляция от сокрытия отличается знаете?

                        The term encapsulation is often used interchangeably with information hiding. Not all agree on the distinctions between the two though; one may think of information hiding as being the principle and encapsulation being the technique. A software module hides information by encapsulating the information into a module or other construct which presents an interface.


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


                        Что вы там тестировать в слове собрались? Константы?

                        Я думал, что слова бывают разные и чтобы определить ширину, нужно какое-то вычисление, нет?

                          0
                          Как только вы покроете ваши 9 случаев у вас не получится написать красный тест.

                          Этот момент может настать и после 5 тестов, и после 3 и даже после первого же цикла. Значит ли это, что остальные тесты писать не надо? Будете ли вы уверены в последующем рефакторинге, если эти 5/3/1 тест всё время будут зелёными?


                          использование компонента не через его интерфейс

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


                          если вы делаете что-то приватное публичным

                          Собственно, кроме public и private других модификаторов доступа не знаете?


                          может приводить к хрупким тестам

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


                          Я думал, что слова бывают разные и чтобы определить ширину, нужно какое-то вычисление, нет?

                          Вызов внешнего апи, там нечего тестировать.

                            0
                            Этот момент может настать и после 5 тестов, и после 3 и даже после первого же цикла.

                            Продемонстрируйте мне, пожалуйста, тест который приводит к написанию кода из двух уровней с тремя вариантами каждый при соблюдении правила 2 TDD. "You must not write more of a test than is sufficient to fail, or fail to compile." и 3 "You must not write more production code than is sufficient to make the currently failing test pass.".


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

                            Не нашел в статье определения юнит теста, по которому они должны быть хрупкими. Нашел в статье, что solitary юнит тесты с использованием моков должны иногда давать не те же результаты, что и тестирование с продакшн кодом. Про Solitary vs Sociable и Mocks aren't stubs можно почитать у Фаулера.


                            Вызов внешнего апи, там нечего тестировать.

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

                              +1

                              Еще хотелось бы отметить элегантноть выбранного фреймворка и стиля тестов в статье:


                              // Нахрена везде повторять этот "мол"?
                              $mol_test({
                               // В названии теста не отражено требование которое он проверяет
                              'print greeting to defined target'() {
                                      const app = new $my_hello_message
                                      app.target = ()=> 'Jin'
                                      // опять mol. зачем, мол, повторять, мол, везде, мол?
                                      $mol_assert_equal( app.sub().join( '' ) , 'Hello, Jin' )
                                  } ,
                              
                              })

                              Сравним с mocha.


                              import { hello } from './hello-world';
                              import { expect } from 'chai';
                              import 'mocha';
                              
                              describe('Hello function', () => {
                              
                                it('should return hello world', () => {
                                  const result = hello();
                                  expect(result).to.equal('Hello world!');
                                });
                              
                              });

                              Читается почти как английский текст

                                0
                                Нахрена везде повторять этот "мол"?

                                Это пространства имён. Если вам режет глаз, то вы точно так же как и с импортами можете переименовать:


                                const hello = $my_hello
                                const equal = $mol_assert_equal
                                const test = $mol_test
                                
                                test({
                                    'returns greetings'() {
                                        equal( hello() , 'Hello world!' )
                                    } ,
                                })

                                Вообще, забавно, что вы сравниваете совершенно разные тесты совершенно разных вещей.


                                Читается почти как английский текст

                                Вы таки гуманитарий?


                                В названии теста не отражено требование которое он проверяет

                                Там же английским по белому написано: печатает правильное приветствие указанному адресату. Или вам без повторения "should" (опять should. зачем, should, повторять, should, везде, should?) не понятно?

                                  0
                                  Это пространства имён. Если вам режет глаз, то вы точно так же как и с импортами можете переименовать:

                                  Зачем же вы живете с этим mol не переименовывая?


                                  Вы таки гуманитарий?

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


                                  даже test('returns greeting') читается чуть легче чем mol_test('return greeting') за счет третьего лица и удаления префикса.


                                  в 'print greeting to defined target' вообще неоднозначность — я сначала прочитал это что надо распечатать что-то в target.


                                  Там же английским по белому написано: печатает правильное приветствие указанному адресату.

                                  Неа, там написано "напечатать", а не "печатает", а во-вторых не однозначно, к чему относится "to" — к print или greeting.


                                  "печатать приветствие к определенной цели".


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


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


                                  Кстати, что такое sub — там про сендвичи, или это какая-то концепция понятная вебдевам с первого взгляда?

                                    0
                                    Зачем же вы живете с этим mol не переименовывая?

                                    Это удобно и позволяет не запутаться. Одна и та же сущность во всех контекстах называется одинаково.


                                    иметь офигеть какие преимущества

                                    Это тот самый случай.


                                    там написано "напечатать", а не "печатает"

                                    Ага, опечатался.


                                    не однозначно, к чему относится "to" — к print или greeting

                                    Я хз, как это однозначно сформулировать на английском. Поэтому читайте лучше код — он однозначный.


                                    никто не стал даже и пытаться?

                                    Отвечу за всех. Никто не стал. Остальные наслаждаются.


                                    Кстати, что такое sub — там про сендвичи, или это какая-то концепция понятная вебдевам с первого взгляда?

                                    Сокращение от submissive.

                                      0
                                      Это удобно и позволяет не запутаться. Одна и та же сущность во всех контекстах называется одинаково.

                                      По мне, так это недостаток — отсутствие учета контекста. Ну да ладно. Многочисленные пользователи фреймворка смотрят на меня свысока ;)


                                      Ага, опечатался.

                                      Возможно, если бы была привычка писать should было бы сложнее


                                      Я хз, как это однозначно сформулировать на английском.

                                      Я бы написал что-то типа
                                      "prints a given recipient name in a greeting"


                                      Поэтому читайте лучше код — он однозначный.

                                      Я бы предпочел читать код того, кто дает понятные имена


                                      Сокращение от submissive.

                                      Это общепринятое сокращение? Честно говоря, мне от расшифровки яснее не стало. Почему добавление в submissive это print?

                                        0
                                        Возможно, если бы была привычка писать should было бы сложнее

                                        С чего бы?


                                        "prints a given recipient name in a greeting"

                                        Хороший вариант, да.


                                        Это общепринятое сокращение?

                                        Да.


                                        Почему добавление в submissive это print?

                                        Потому, что это не print, а конфигурирование компонент. Одно свойство задали, другое проверили.

                                          0
                                          С чего бы?

                                          С того, что описаться в буквах s, h, o, u, l и d сложнее чем в одной s.


                                          Это общепринятое сокращение?

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


                                          Я поискал "submissive sub programming" и в первой строке было вот что


                                          For in BDSM the submissive (or “sub”) willingly grants the dominant (or “dom”) power over them, and they do so out of trust and respect.

                                          Теперь мне стало понятно, в какой именно среде это общепринятые термины и все стало ясно. Спасибо!

                                            0

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

                                              0
                                              Ну хозяев с рабами то у всех есть, а вот какая целевая аудитория должна понимать сразу что sub это submissive? Ссылку можете дать на кого-то кто не вы и не BDSM (если это разные категории) и использует это сокращение в программировании? Я вот только знаю тег sub.
                                                +1
                                                Это одна категория.
                                                  0

                                                  Я думаю, это должны быть разные категории, так как есть любители BDSM кроме вас. Максимум вы как категория должны входить в BDSM.

                                0
                                Продемонстрируйте мне, пожалуйста, тест

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


                                Пишем тест — он красный:


                                assert( pixel_pos( 1.1 ) === 1 )

                                Пишем код — тест зеленеет.


                                function pixel_pos( x ) {
                                    return Math.floor( x )
                                }

                                Больше красных тестов написать не удалось — функция полностью удовлетворяет всем требованиям.


                                Побенчмаркали и выяснили, что Math.trunc работает быстрее, чем Math.floor. Отрефакторили код:


                                function pixel_pos( x ) {
                                    return Math.trunc( x )
                                }

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


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


                                Sociable

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


                                можно почитать у Фаулера.

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


                                Тогда приведите, пожалуйста, код

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

                                  +1
                                  Пишем код — тест зеленеет.

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


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

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


                                  UPD: вернее, само обсуждение начинается отсюда


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

                                  Уверенность в себе это хорошо. Но я не всегда доверяю чужой уверенности в себе :)

                                    0
                                    Простейший код — это возвращение константы в данном случае.

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

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

                                      Какие аргументы у сторонников TDD за эти итерации? Или вы опять пытаетесь очень уверенно критиковать то, в чем не разобрались?


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

                                      Подумал. А тут не тот же самый компромисс между трудоемкостью покрытия и точностью результата как и в вашей фразе ранее?


                                      "Каждый из этих приватных методов (а точнее их видимость придётся повысить) тестируется в предположении, что вызываемые им методы уже протестированы. Это даст 3+3+3 = 9 кейсов."


                                      Т.е. у вас дополнительный вызов метода уровня 2 покрывается ровно одним кейзом уровня 1. Разница только в том, что в этой новой задаче этот дополнительный вызов находится в отдельном методе, а метод уровня два библиотечный.

                                        0
                                        А тут не тот же самый компромисс между трудоемкостью покрытия и точностью результата как и в вашей фразе ранее?

                                        Слепое следование ТДД даёт гарантированно меньшее покрытие при большей трудоёмкости.


                                        метод уровня два библиотечный

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

                                          0
                                          Слепое следование ТДД даёт гарантированно меньшее покрытие при большей трудоёмкости.

                                          Я не понял о чём вы

                                          Вы не поняли и при этом продолжили рассуждения!


                                          Я вот о чем — вы вашем примере снизили количество кейзов, как с 27 до 9 за счет того, что тестируете кейзы вложенных выовов только в одном кейзе вызывающей функции — так? Условно, соответственно когда тестируюете другой кейз, вызывающей функции вы не гарантируете от соверщенно такой же подмены вызываемой функции на другую, совпадающую в одном тестируемом кейзе но не совпадающую в других. Совершенно так же как замена round на trunc.


                                          чтобы не мокать каждый оператор и стандартную функцию,

                                          Таким образом, никаких модульных тестов не существует: как только мы используем что-то не мокая оно обращается в компонентный тест! (вы повторили цепочку рассуждений из другого обсуждения, но с другими выводами :D )

                                            0
                                            тестируете кейзы вложенных выовов только в одном кейзе вызывающей функции — так?

                                            Нет, вложенные функции покрыты своими тестами, поэтому тестировать их косвенно нет необходимости. Так же как с "библиотечными" функциями.


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

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

                                              0
                                              Нет, вложенные функции покрыты своими тестами, поэтому тестировать их косвенно нет необходимости. Так же как с "библиотечными" функциями.

                                              Тогда почему вас насторожило что round не покрыт косвенными тестами настолько что он может быть отличен от trunc?


                                              Именно так, модульные тесты — фикция и профанация.

                                              Теперь надо применить эту же логику последовательно к компонентным тестам и end-to-end тестам: если X используется в соединении с Y для тестирования, то это уже тестирование не X а еще и Y.


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


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


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

                                                0
                                                Тогда почему вас насторожило что round не покрыт косвенными тестами настолько что он может быть отличен от trunc?

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


                                                Исходя из этого нет никаких компонентных тестов, а есть только вселенские тесты.

                                                Вы статью-то мою перечитайте, там чёткая классификация.

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

                                                  Это все одна и та же тема. У нас есть вызов функции f1 из функции f2. Если тест функции f2 не содержит все кейзы функции f1 допустимые на тех данных которые может ей предоставить f2 значит мы можем заменить ее на функцию f3, которая отличается от f1 в кейзах, которые не тестируются тестами f2.


                                                  Это вы же сами продемонстрировали в случае замены round на trunc.


                                                  Теперь у нас есть функция f1 у которой есть три кейза, которая вызывает f2 у которой есть три кейза. Обе функции приватные. Если я тестирую 3+3=6 кейзов то я в каких-то кейзах f1 тестирую не все возможности f2 и там они могут быть заменены на функцию f3 точно таким же образом.


                                                  Вы статью-то мою перечитайте, там чёткая классификация.

                                                  Перечитал


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

                                                  Ок модули определяются через независимое тестирование.


                                                  Тестирование модулей так же известно как "юнит-тестирование".

                                                  Ok. Это независимое тестирование и есть юнит тестирование.


                                                  Также из обсуждения мы знаем что модульных тестов не бывает "модульные тесты — фикция и профанация" => модулей тоже не бывает, так как они определены через тестирование.


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

                                                  Как компонент может включать в себя то, чего не бывает?

                                                    0

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

                                                      0

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

                                                        0
                                                        А как я траверсю AST?
                                                          0

                                                          Неизвестно. Я к аудитории обращаюсь.


                                                          всех вариантов входных параметров, а тестирование всех классов эквивалентности и граничных условий.

                                                          Тестирование существует не само для себя, а для выявления ошибок или некорректных "граничных условий".

                                                        0
                                                        Потому что проблема не вы вызовах функций, а в том, что алгоритмы различны.

                                                        Это странно или коряво сформулированно. Алгоритмы различны. Частный случай — вызов не тех функций. Никакого противопоставления нет.


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

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


                                                        https://habr.com/post/279535/


                                                        • Исчерпывающее тестирование (Exhaustive Testing — ET) — это крайний случай. В пределах этой техники вы должны проверить все возможные комбинации входных значений, и в принципе, это должно найти все проблемы. На практике применение этого метода не представляется возможным, из-за огромного количества входных значений.


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

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


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

                                                        Не хотите — не отвечайте.

                                                          0
                                                          Полное покрытие в практическом смысле — это не тестирование всех вариантов входных параметров, а тестирование всех классов эквивалентности и граничных условий.
                                                            0

                                                            Отлично! У нас есть определение.


                                                            Теперь давайте рассмотрим подробнее корневое сообщение из этой ветки:


                                                            У вас может быть 3 приватных последовательно вызывающих друг друга метода. В каждом есть, допустим, 3 ветки логики. Если тестировать через публичный интерфейс, то нужно проверить 3х3х3 = 27 кейсов. Помножьте на число граничных условий и будет совсем ахтунг.

                                                            далее


                                                            Каждый из этих приватных методов (а точнее их видимость придётся повысить) тестируется в предположении, что вызываемые им методы уже протестированы. Это даст 3+3+3 = 9 кейсов.

                                                            Т.е. для функции верхнего уровня есть 27 классов жквивалентности, мы уменьшили их до 9 за счет того, что вызываемые методы "уже протестированы".


                                                            То есть я так понимаю, что функция f1 вызывает функцию f2, которая вызывает f3. Допустим у f1 есть три ветки логики, 1, 2, 3 соответственно. Обозначим комбинации так 1.1 — кейз когда отрабатывает 1 кейз f1 и 1 кейз f2 и так далее.


                                                            Если вы можете убрать из тестирования кейзы f2 в каком-то кейзе f1, потому, что они "уже протестированны" то значит тест не свалится в том случае, если мы заменим f2 на какую-то другую функцию f4, у которой кейзы совпадают с убранными и несовпадают с оставшимися.


                                                            То есть если мы оставили кейз 1.1 то если мы заменим в вызове функцию f2 на f3, для которой кейз 1.1 проходит, а 1.2 и 1.3 не проходят, то мы получим то же самое.


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


                                                            Если вы видите ошибку в моей логике, укажите на нее.

                                                              0

                                                              Ошибка тут:


                                                              Т.е. для функции верхнего уровня есть 27 классов жквивалентности,

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

                                                                0

                                                                Давайте примем что это так. Отсюда два вопроса:


                                                                1. Какой именно тест предотвратит замену f2 на f4 (напомню, что f4 ведет себя так же как f2 на всех кейзах, кроме тех которые опущены при тестировании f1 на основании того, что f2 уже протестирован)
                                                                2. Если заменить, f1 на pixel_pos а f2 на round, получаем, что у pixel_pos одна ветка => один класс эквивалентности, а round уже протестирован => нам нужен ровно один кейз для pixel_pos

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

                                                                  0
                                                                  Какой именно тест предотвратит замену f2 на f4

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


                                                                  нам нужен ровно один кейз для pixel_pos

                                                                  Ноль, если линковка и типизация статические.

                                                                    0
                                                                    Это уже вопрос сверки контрактов.

                                                                    При чем тут какая-то "сверка контрактов" — любой тест проверяет, что тестируемая система соблюдает свой контракт. В f1 использовали f2 а не f4 чтобы f1 выполнил свой контракт, а не почему-то еще.


                                                                    Ноль, если линковка и типизация статические.

                                                                    Имеется ввиду test case

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

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


                                                                        Итого, вы из 27 приписанных кому-то тестов сделали 9, но теперь их не 9 а больше, но какие непонятно, но таким же способом полученный 1 тест для pixel_pos недостаточен и даже 2 теста по TDD это мало.


                                                                        Я все правильно изложил?


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


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


                                                                        Будете ли вы отвечать на вопрос вот тут?

                                                              0

                                                              Отдельный вопрос — насколько практично "полное покрытие" в реально существующих проектах. Например, какое покрытие у mol. Я не вижу бейджа хотя бы с code
                                                              coverage по запускавшимся строчкам, было бы интересно, если бы вы поделились статистикой.

                                    +1
                                    > Например, на уровне методов, их содержимое инкапсулировано, а сигнатура является публичным интерфейсом.

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

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

                                    Если система видна тестам и не видна кому-то еще это нормально. Тоже с моей точки зрения. Ответственность выражается в прохождении тестов.

                                      0

                                      С моей точки зрения это значит, что есть уровень абстракции, который явно не выражен.


                                      см также Front Door First


                                      Еще мне интересно как вы "Каждый из этих приватных методов (а точнее их видимость придётся повысить)" выполняете так, чтобы они не становились видны кому-то еще.

                        +2
                        Мне кажется, что если покрытие тестами будет достигать в лучшем случае 30%, то это означает, что ваш код, как бы это по мягче сказать… плохой и требует рефакторинга.
                        Если невозможно через внешние интерфейсы пройти все ветки кода, то что-то в это есть не правильное.

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

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

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

                              Абстрактная цифра 30% покрытия говорит от том, что не покрыто по крайней мере 70% :)

                            +1
                            Как тестировать приватные методы?

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

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

                            Тут уж вам решать сколько тестов писать и нужно ли прогонять тесты по всем веткам. Хороший вариант — параметризованные тесты.

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

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

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

                            TDD не серебрянная пуля, его не нужно использовать всегда.

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

                          Самое читаемое