суббота, 13 мая 2017 г.

TDD

Продолжаем разбираться в концепции Егора Бугаенко об Элегантном и по-настоящему Объектном Программировании в Java. 

В частности, Егор предлагает переоценить актуальность концепции TDD (Test-Driven-Development) и, как обычно его выводы парадоксальны. Заниматься тестированием до того, как код забажен(bug) не особо денежно.
нам не нужно тестировать код, пока он не сломается

 
Егор приводит веский довод, что для разработчика правильнее реагировать на выявленные (в том числе клиентом) конкретные баги, а изначально заниматься "излишним" проектированием "абсолютно чистой-чистой" системы накладно. 

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

За последние 300 тыс. строк Егор ни разу не использовал TDD, как написано в книжке: "сначала тест, потом код". Как (ленивый циник) практик, он придерживается концепции:

Код, Развертывание, Ошибка, Тест, Исправление
(Code, Deploy, Break, Test, Fix)

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

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

Он пишет код (много кода, весь код), без тестов, совсем. Проверяет, что у него все работает. После этого отправляет код в работу и спокойно ждет сообщений о поломке кода на практике. И, вот, уже  после отбора наиболее критичных багов наступает время TDD.
Я не касаюсь существующего кода, пока мне не удастся сломать его и доказать существование проблемы тестом.
Здесь Егор, как и прежде, верен своей концепции ограничения своей активности только необходимо-достаточными усилиями для решения проблемы, и не более того. Он говорит о балансе, когда тестируются только принципиально важные компоненты из-за которых возникают ошибки, а добиваться 100% покрытия кода тестами, и тем более вперед основного кода, это слишком-слишком дорого.
Технически невозможно проверить все или исправить все ошибки. Мы должны исправить только то, что видимо и недопустимо для бизнеса. Если бизнесу все равно, или наши пользователи / тестеры не видят наших ошибок - мы не должны тратить ресурсы проекта на их исправление.
Что же, логично, "книжный" TDD каркас слишком дорого обходится программистам, когда целиком применяется к проекту, и, в то же время, очень мощный, когда используется для выявления принципиально хрупкого кода.

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


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

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


Критика и отзывы:

Изложенная концепция - это не TDD, возможно более подходящее название Bug Driven Testing

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

Никто в здравом уме не будет платить за ПО без мощного внутреннего тестирования. Не стоит полагаться на клиентов для улучшения своего кода.

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

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

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

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

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

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

Роберт Мартин показывает суть TDD в своей замечательной книге «Agile Software Development: принципы, шаблоны и практика». Посмотрите пример с боулингом.
 
Почему заказчик должен платить за исправление ошибки? Если происходит ошибка, это вина программистов. Они должны исправить баг «бесплатно», так как клиент уже заплатил за разработку. 
Денежные отношения подразумевают, что существуют определенные правила, т.е. программное обеспечение должно работать так, как ожидается, чтобы оно было клиентом оплачено. Соответственно, штрафы, судебные иски, ущерб репутации и других нежелательных последствия, могут серьезно перевесить ресурсы, которые мы сэкономим на «не исправлении» ошибки.
 
Хорошая статья! Я согласен с тем, что написание тестов до кода очень сложно, и, скорее всего, это пустая трата времени. И в большинстве проектов единственными тестами, которые имеют ценность, являются те, которые получены из бизнес-требований. Тем не менее, я думаю, что все зависит от отрасли, в которой вы находитесь. Если ошибка может нанести серьезный ущерб или создать угрозу для жизни (как в Therac-25), то думаю вы заинтересованы, чтобы код, который вы развернули, был тщательно протестирован.

Мне нравится идея, что тесты - это наглядная документация. Таким образом, никакие нажатия клавиш не будут потрачены впустую, поскольку даже мне, как разработчику, нужно сначала изучить, как я хочу использовать определенный класс.
 
Предложение Егора пример концепции «Минимально жизнеспособного продукта». Это важно. Написать наименьшее количество кода, чтобы перевести его на следующий этап - тестирование в команде QA, или даже простой проверке самого себя.
 
Я думаю, что этот подход работает, но, возможно, в меньших масштабах. Я также не стал бы писать тесты, до кода, потому что опыт говорит, что это больше работы, чем требуется для реального тестирования. И, что имеет смысл вводить тесты только после того, как важная часть функциональности реализована. Но вместе с тем, я бы чувствовал себя крайне неловко ожидая, что у кого-то могут возникнуть проблемы с моим ПО без тестирования, потому что это снижает уверенность клиентов в моей способности выпускать качественный код. В работе, я придерживаюсь одного и того же принципа: Разбить, протестировать, исправить, развернуть.

Полностью с согласен с концепцией Егора. Во всяком случае, я хотел бы подчеркнуть разницу между TDD (разработка на основе тестов) и TDD (Test Driven Design). Первая работает хорошо. Обычно для юнит-тестов, где нет дизайна. Второй - неправильная философия и совершенно катастрофическая попытка попытаться дать правила дизайну. Ваш подход правильный. Сначала вы делаете дизайн (потому что вы единственный, кто может это сделать), затем вы покрываете код тестами, когда и где это необходимо. 
 
Очень интересный и противоречивый подход. Мне нравятся некоторые его части, у меня есть сомнения по поводу некоторых других частей. Подходит ли  подход Егора ко всем видам тестов? Потому что есть несколько видов тестов. У каждого есть своя роль и назначение.
  • Unit тестирование отдельного класса (его обязанностей). Используется разработчиками для понимания класса и безопасного рефакторинга. Но оно бесполезно для конечного пользователя. Unit тесты ничего не говорят о состоянии системы. У вас может быть 100% тестовое покрытие, каждый класс работает отлично. Но система в целом - пустая и бесполезная.
  • Системные тесты. Тестирование всей системы с использованием внешних API (вызов REST, HTTP и т.д.). Они тестируют варианты использования системы (с точки зрения пользователя). Они медленнее, чем юнит-тесты. Когда они терпят неудачу, обычно трудно понять (из сообщения об ошибке), что неправильно, потому что многие вещи могут пойти не так (нет базы данных, потерянного соединения, веб-страница не загружена и т.д.).
 
Мои сомнения относительно подхода:
  • Если вы не пишете модульные тесты вместе с производственным кодом (первым или последним), то их может быть очень сложно добавить позже. Я предполагаю, что ваш стиль кодирования (небольшие классы, действительно строгие правила, обзоры кода) позволяет добавлять юнит-тесты позже. Но для большей части кода я видел, что невозможно (непрактично) добавлять юнит-тесты без серьезного рефакторинга.
  • Без системного тестирования, трудно не сломать систему. Откуда разработчик знает, как должна работать система, когда он что-то меняет? Ручное тестирование каждый раз нецелесообразно.
  • Крайне мало тестового кода по сравнению с рабочим. Если вы относитесь к проекту серьезно,  тестового кода должно быть гораздо больше, чем производственного кода.
 
Я использую TDD всякий раз, когда это возможно, и он отлично работает. Бывает иногда трудно придерживаться подхода TDD, особенно при работе с устаревшим кодом. Но знание того, что все функциональные возможности кода покрыты тестированием, хорошо помогает, когда я хочу что-то реорганизовать. Тесты скажут мне, если что-то будет сломано.
 
Когда я учу людей делать TDD, я очень подчеркиваю важность проектной сессии для понимания идеи. И никогда не должно быть предлогом «отключить свой мозг» для Red-Green-Refactor.
Если я правильно понял ваш подход, вы пишете тесты
только для зарегистрированных проблем, прежде чем исправить их. Таким образом, у вас никогда не будет тестов для кода, который просто работает (вернее: кажется, что работает). С вашим подходом гораздо больше ручной проверки (или скрещивания пальцев), не так ли? 
Подход Егора будет полезен для предотвращения сверхинтеграции, никогда не следует забывать, что «Очевидная реализация» также является допустимым методом для получения «зеленого».

Тесты являются частью документации, поэтому откладывание или пренебрежение их написанием не вариант
 
Думаю, что TDD «по книге» слишком сложно, почти невозможно. Но, я думаю, вы никогда не должны писать код без тестов (какие-либо тесты, но, по крайней мере, что-то); Я думаю, что с каждым новым классом новый тестовый класс должен появляться всегда. И с каждым новым методом, или с большими изменениями в методе, должен появиться новый тест.
 
Вам не кажется, что этот подход слишком хаотичен? Если вы будете постоянно работать над кодом, по вашему плану может получиться хорошо (быстро и лениво), но что делать, если вы сделаете перерыв на несколько месяцев? Бьюсь об заклад, вернувшись, тебе понадобится гораздо больше времени, чтобы разобраться и реорганизовать все, возможно, даже выбросить весь старый код.

Я придерживаюсь другого подхода. Сначала пишу интерфейсы и классы (но не код), затем пишу тесты. Конечно, не для каждого класса, даже дядя Боб говорит, что вы не должны этого делать. Фокус в том, что я первый пользователь, и у меня будут жалобы). Я не люблю каждый раз щелкать по страницам. Особенно, если проект вообще не имеет графического интерфейса. Я пишу тест один раз, а затем я пишу код, меняю код и каждый раз, когда я могу использовать ярлык, чтобы выполнить тестовый запуск за считанные секунды. Это позволяет мне не отвлекаться на другие окна. Я могу сделать все это в IDE или в текстовом редакторе. И есть двойная прибыль. Я экономлю время на разработке, а затем у меня есть тест. Я думаю, да, писать тест очень дорого, когда что-то уже работает. Но если такие тесты помогут вам в разработке, их ценность удваивается. Во всяком случае, нет общего подхода, который подойдет каждому. Все хорошо, что работает именно для вас.


Комментариев нет:

Отправить комментарий