Экстремальное программирование в стиле тестеров-крутотенечек!

Original author: Мах Kh.
  • Translation
Вы, мои дорогие, когда-либо представляли себе процесс тестирования, идущий задом наперед? Если да, то вы уже знакомы с TDD!

Немножко истории

Сейчас мы с вами будем говорить об одной из методик экстремального программирования и тестирования. Но сначала — Тойота! Да-да, именно та самая Тойота, компания, которая делает весьма неплохие, как по мне, автомобили. Какое они имеют отношение к тестированию, тем более экстремальному? Да самое непосредственное! И нет, я не имею ввиду тесты софта на скорости в 180 км/час. Это было бы глупо. Весело, но глупо.

TDD (Test-Driven Development), или, если по-нашему, — разработка, основанная на тестировании — это достаточно интересная методика, но, как по мне, TDD звучит круче, а посему я буду именно так называть её здесь. Мы же круты, верно?

Так о чем я? А, Тойота! Именно в этой компании Тайичи Охно и разработал TDD. Эта методика изначально была ориентирована на производство автомобилей. Тайичи был слегка помешан на безотходном производстве и, в дополнение ко всему, был достаточно умен для воплощения мечты скрупулезного японца в жизнь. Так появилась TPS, любящая мама TDD. Его гениальная идея заключалась в том, чтобы производить только необходимое количество автозапчастей, необходимых для первых стадий сборки. Также к этим запчастям предъявлялись исключительно высокие требования касательно качества. Процесс общения между различными этапами производства происходил благодаря Канбанам или, проще говоря, большим доскам с необходимыми надписями и заметками.

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

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

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

Тяжело в учении…

Хотя в учении, то есть в теории, все звучит предельно просто. TDD кажется предельно простым, на первый взгляд. Хотя бы по тому, что у него всего два основных правила. Какие могут быть правила страны чудес, где верх это низ, а низ это верх? Давайте взглянем:
  • Забудьте о написании нового кода. Перестаньте. Отойдите от клавиатуры на безопасное расстояние и боритесь с собой изо всех сил. Хотя бы пока автоматизация не подведет вас.
  • Не повторяйтесь. Устраните все, что дублирует друг-друга.

Звучит несложно, верно? Но эти правила проще внедрить, нежели им следовать. Эти правила просто-таки стремятся придумать усложнения процесса ровно настолько, насколько это вообще возможно. И немножко больше.

Если следовать правилу номер раз (а мы будем ему следовать, ведь все мы безусловно крутые TDD-шники с горящими клавиатурами и прочими примочками, добавляющими плюсы к нашей небесной крутости), то тесты следует писать до того, как будет написан код, который нужно тестировать. Мне кажется, что каждый из нас в той или иной мере завидовал Юре Гагарину и хотел бы отправиться покорять звезды хотя бы раз в своей жизни. А посему, для нашего небольшого примера мы воспользуемся Java для измерения параметров нашего потенциального покорителя вселенной. Почему? Потому что можем!

image

Пусть вас не удивляет тот факт, что в примере всего несколько строк тестирующего кода и ни одной строки кода продуктивного. Это просто пример, созданный для простоты восприятия. Так что-же все таки в нем творится?
  • Создание объекта. Наш космолет был успешно создан. И в нормальном состоянии он не перемещается. Соответственно, его скорость равна 0 миль в секунду. Это не самое точное определение звездолета, но, во-первых, я не разработчик космических аппаратов, а во-вторых, для наших целей, как это на Руси принято, и так сойдет. Нужно разбираться с каждой проблемой по очереди или, как это называют наши коллеги TDD-шники, заниматься органическим дизайном.
  • Дизайн API. Юнит-тестам повезет больше всего. Они будут первыми, кто опробует на себе крупицы кода. И, соответственно, они будут первыми предоставлять нам необходимые данные. Соль в том, что данные мы получим еще до написания настоящего кода. Круто, правда?
  • Декаплинг. На данный момент класс космического корабля декаплируется (отделяется) из определенных моделей движения. Все что ему пока известно это интерфейс 'Driving Mode' в котором мы можем увидеть стандартный шаблон TDD. Строка, вмещающая в себе принципы дизайна, запрограммирована для интерфейса, а не для реализации. Огромным плюсом является то, что теперь нам подвластен юнит, который тестируется стабом (симуляция поведения компонента софта). Я бы также постарался не слишком полагаться на конкретные классы. Они обычно добавляют как минимум одну неточность факторов. А больше чем одна неточность это не то, с чем легко справиться в TDD.
  • Побочные эффекты. Что будет означать изменение модели движения? Только то, что она теперь будет равна высшей скорости конкретной модели, как указанно в примере.

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

image

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

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

Что же мне не нравится в приведенном примере? Скорость, зависящая от состояния. Также есть усложнение, ведь скорость есть сразу в двух местах. Попробуем зафакторить тут все теми средствами, которые нам предлагает 'Driving Mode'.

image

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

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

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

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

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

Различные уровни дизайна

Сколько нужно будет up-front дизайна? Кто знает, это лишь один из тех вопросов, на которые нет ответа. Все зависит только от проекта. Мы же будем дальше придерживаться софта для нашего кораблика. Предположим, что из целей безопасности одна из техник поведения в нестандартных ситуациях была написана двумя разными командами. Обе команды занимались написанием программ, которые должны работать параллельно. У программ будут заданы определенные параметры, и если они в чем-то не соглашаются, тогда, скажем, запуск отменяется автоматически. Эта методика разделения показывает, что ап-фронт в данном случае — это две разные софтинушки, которым необходимо будет соглашаться друг с другом, а этого эффекта сложно добиться и от одной программы.

А какие тесты мне писать, если я не знаю требований?

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

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

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

Зачем вообще все эти ваши заморские TDD?

Что же такое TDD? Я бы предпочел думать об оном как о технике дизайна, в первую очередь. То есть, конечно, разработчики юнит-тестов служат жизненно необходимой цели верификации. Но вы должны согласиться, что верифицируют они правильность кода. Таким образом, каждый мало-мальски уважающий себя проект вынудит их к acceptance и requirements тестам. Юнит-тесты — это просто сильная основа, которая необходима для правильного функционирования всего в целом. Именно так TDD позволяет фокусироваться на важном, что приводит к всеобщему успеху, радости и множеству денежки. Рай, что ни скажи.

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

Охватывая код

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

Приятно то, что TDD даст вам 100% данных (в теории) благодаря тому, что код Вы пока не писали, следуя правилу номер раз, ровно до тех пор, пока машины вас не подвели.

Охватывание кода — это то, что вам нужно для получения большего объема сведений и помощи в починке битых окон.

Битые окна

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

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

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

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

Уход за софтом

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

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

Рекомендации по TDD

TDD — нечто крайне строгое, а со всего, что строго, легко соскользнуть. А посему существует и список вещей, которых стоит передерживаться, если вы хотите быть тру-хардкорным TDD-шником.
  1. Код юнита и код, который тестируется, должны быть равнозначными.
  2. Юнит-тесты должны быть такими же маленькими и независимыми, как, скажем, Монако.
  3. Пользуйтесь постоянными названиями. Теми, к которым вы привыкли. Таким образом, значительно упрощается навигация. Называйте юнит-тесты соответственно юнитам, которые тестируются.

Чему же мы научились?

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

Что на самом деле TDD делает, так это меняет местами процессы тестирования и разработки. При этом находя золотую середину для качественного дизайна и отличной осведомленности в процессе. Но не стоит забывать (НИКОГДА) о том, что TDD требует дисциплины и целеустремленности.

Но, как и в любой жизненной ситуации, выбор исключительно за Вами, друзья. Я всего лишь хотел бы пожелать вам удачи во всех ваших начинаниях, и да не обидит вас заказчик, да не выбьется поставленный костыль, да не слетит весь процесс в тартарары из-за мелочи малоприятной! Всем лучей добра!
  • +2
  • 10.3k
  • 5
Share post

Comments 5

    0
    В Японии 1940x металл был дорог, а человеческий труд − дёшев, как в Китае 2000х. Так что да, не панацея. :)
      0
      О боже, какая же у него каша в голове!
        +1
        На звезде, воде и в бороде
        TDD, TDD, TDD!
          0
          Слог шикарен. Спасибо автору за удовольствие от прочтения.

          А по теме TDD — в современном программировании планка занижается всё больше и больше. Учёные остались в 70-х, хакеры в 90-х, многостаночники в нулевых. Теперь эпоха эонов-архитекторов и морлоков-кодеров. TDD позволяет сделать в срок проект силами даже не студентов, а выпускников ПТУ. И это, с точки зрения бизнеса, прекрасно. Исполнители дешёвы и легкозаменяемы.

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

          Only users with full accounts can post comments. Log in, please.