ИЗ АРХИВА ЖУРНАЛА «ПРАГПАБ», ИЮЛЬ 2016 ГОДА

Разработка через тестирование: необходимость, технические препятствия и решения

Венкат Субраманиам

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

In this article:
* Benefits 
    * Quick Feedback
    * Make Applications Truly Testable
    * Better Design
* Technical Impediments and Solutions
    * Visualizing the Design
    * Visualizing the Implementation

Разработка приложений с использованием автоматизированных тестов, практика, известная как Разработка через тестирование (TDD), обсуждалась на многих конференциях и в большом количестве книг, включая Разработка через тестирование: На примере Кента Бека и Развитие объектно-ориентированного программного обеспечения, руководствуясь тестами Стива Фримена и Нэта Прайса. Тема вызвала споры и активно обсуждалась.

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

Преимущества

TDD имеет несколько основных преимуществ:

  • Быстрая обратная связь о том, что код продолжает работать
  • Сделайте приложения по-настоящему тестируемыми
  • Лучший дизайн

Давайте обсудим каждое из этих преимуществ.

Быстрая обратная связь

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

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

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

🌟Без автоматического тестирования невозможно быстро отреагировать на изменения.

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

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

Разве после написания кода недостаточно написать тесты? Почему нам следует писать тесты до или одновременно с написанием кода?

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

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

Сделайте приложения по-настоящему тестируемыми

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

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

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

Проект поддерживает автоматическое тестирование с нуля. Функция «Обработка платежа» абстрагирует обработку платежа, поэтому остальной части приложения не приходится с этим иметь дело. Любое изменение в функции «Обработка заказа» можно быстро проверить, протестировав функцию «Обработка платежа» посредством модульного тестирования. Хотя модульное тестирование вселяет в нас уверенность, мы не можем успокаиваться на том, что все хорошо. Обработка платежа должна правильно взаимодействовать со сторонней платежной службой. Это тоже нужно протестировать. Но как мы можем автоматизировать тестирование этого кода? Списание средств с реальных кредитных карт ради тестирования не будет экономически эффективным давайте' не забывать об этих неприятных комиссиях за транзакции. К счастью, любой платежный сервис, с которым стоит иметь дело, предоставляет поддельный сервис в целях тестирования. Когда запрос, отправленный в платежную службу, содержит тестовый ключ и хорошо известные номера кредитных карт, служба незаметно направляет вызовы в поддельную службу, а не в производственную службу. Это обеспечивает довольно быстрое автоматизированное тестирование.

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

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

Лучший дизайн

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

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

Как TDD влияет на лучший дизайн?

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

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

🌟Плохо спроектированный код делает автоматическое тестирование невероятно сложным.

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

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

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

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

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

Технические препятствия и решения

Препятствия на пути внедрения TDD и получения от него выгод делятся на две категории: препятствия управления и технические препятствия.

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

Некоторые из ключевых технических препятствий для TDD:

  • Не могу представить дизайн
  • Не могупредставить реализацию

Давайте обсудим это по одному.

Визуализация дизайна

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

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

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

Большой дизайн — это плохо. Отсутствие дизайн-мышления тоже плохо. Мы не можем создать хороший дизайн, перемещаясь по коду, не отступая назад и не думая, куда нам следует двигаться. Разделите разработку функции на две фазы: стратегическое и тактическое проектирование.

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

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

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

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

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

Визуализация реализации

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

🌟 Программирование – это замечательный процесс непрерывных открытий.

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

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

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

Пиковое обучение — это ключевой урок, который я подчеркиваю в книге Тестирование JavaScript Приложения: быстрый, надежный, поддерживаемый код. Если вам' неясно, как будет выглядеть та или иная функция или класс, создайте очень маленький, изолированный рабочий образец — всплеск —, чтобы получить представление. Как только вы научитесь на пиковой активности, оставьте этот образец в стороне, вернитесь к текущему проекту, а затем начните разрабатывать код, который будет написан, с использованием тестов. Детали, которые вы узнаете в ходе пиковой активности, помогут вам продумать одну из возможных реализаций. Исходя из этого, когда вы начинаете писать тесты, а затем минимальный код, тесты начинают уточнять дизайн имеющегося кода.

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

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

Заключение

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

О Венкате Субраманиаме

Доктор. Венкат Субраманиам — отмеченный наградами автор, основатель Agile Developer, Inc. и преподаватель в Университете Хьюстона. Он обучил и наставлял тысячи разработчиков программного обеспечения в США, Канаде, Европе и Азии, а также регулярно приглашается докладчиком на нескольких международных конференциях. Венкат помогает своим клиентам эффективно применять и добиваться успеха с помощью гибких практик в своих проектах по разработке программного обеспечения. Венкат является (со)автором нескольких книг, включая удостоенную награды Jolt Productivity 2007 года книгу Практика гибкого разработчика.

Примечание редактора: основного заголовка «Преимущества» не было в исходной статье. Он был добавлен для облегчения создания оглавления.