[Перевод] Мой ответ тем, кто полагает, что значение TDD преувеличено

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

Я не стал заострять на этом внимание, разговор добрался до Agile. Клиент, в целом, был открыт идее испытания новых методологий и улучшения своих рабочих процессов. Но — лишь до тех пор, пока я не упомянул о разработке через тестирование (TDD, Test-Driven Development). Единственным ответом на это была следующая фраза: «Значение TDD преувеличено».

hxfqiserarqxybmtnqynd-srtyk.jpeg

Мне не только больно было это слышать, но это заставило меня понять то, что TDD — это ещё одна из тех Agile-методологий, которые могут выглядеть чем-то вроде «городских легенд». Это — то, что заставило меня написать данный материал, в котором мне хотелось бы обратиться к тем, кто сомневается в ценности TDD.

Что такое TDD?


Позвольте мне начать с определения TDD. Это стратегия разработки программного обеспечения, в ходе реализации которой при создании программы полностью руководствуются написанием тестов. Собственно говоря, это вполне понятно и из названия данной методологии. Эта идея предложена Кентом Беком в его книге «Разработка через тестирование», которая была написана в 2003 году. Применение TDD предусматривает выполнение следующих трёх шагов:

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


Этот процесс ещё известен как цикл «красный, зелёный, рефакторинг» («Red, Green, Refactoring»).

Вот — простой пример применения TDD. Здесь мы хотим написать на Python метод, который предназначен для вычисления переменной C по теореме Пифагора.

Вот код теста (файл test.py):

#!/usr/bin/env python

"""
test.py
"""

import unittest

from main import *

class TestDrivenDevelopment(unittest.TestCase):

        def test_pythagorean_theorem(self):
                a, b = 3, 4
                self.assertEqual(calculate_side_c(a, b), 5)

if __name__ == '__main__':
    unittest.main()


Вот код метода (main.py), в котором пока ничего не происходит:

#!/usr/bin/env python

"""
main.py
"""

def calculate_side_c(side_a, side_b):
    return True


Теперь попробуем запустить тест.

44d2b86bc1c38bfeb30a02cafce0ae1f.png


Тест дал сбой («Красная» часть цикла TDD)

Перепишем код, приведя содержимое main.py к следующему виду:

#!/usr/bin/env python

"""
main.py
"""

def calculate_side_c(side_a, side_b):
    c_squared = (side_a * side_a) + (side_b * side_b)
    c = c_squared ** 0.5
    return c 


Снова запустим тест.

4759ab8324469708c517f8d5334877a9.png


Тест прошёл удачно («Зелёная» часть цикла TDD)

Теперь подвергнем код main.py рефакторингу:

#!/usr/bin/env python

"""
main.py
"""

def calculate_side_c(side_a, side_b):
    return ((side_a ** 2) + (side_b ** 2)) ** 0.5


a651c88fe3588c0f994c4eb0662c1686.png


Тест прошёл удачно и для кода, подвергнутого рефакторингу (Часть «Рефакторинг» цикла TDD)

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

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

  1. У нас есть QA-отдел. Писать тесты — это их работа.
  2. Создание тестов, в которых вполне могут понадобиться моки и стабы, способно отнять много сил и времени.
  3. У TDD нет никаких преимуществ перед обычной разработкой.
  4. TDD — это медленно.


Разберём эти идеи.

У нас есть QA-отдел. Писать тесты — это их работа


Этот ответ на мой вопрос о том, почему некто не использует TDD, всегда меня немного смешит. Я — большой сторонник принципа «You build it, you run it», когда тот, кто написал код, несёт за него ответственность. Поэтому меня коробит, когда я вижу, что разработчики ведут себя так, будто их единственная задача — это писать функциональный код.

Я глубоко убеждён в том, что разработчики несут ответственность за то, чтобы их код обладал бы следующими качествами:

  • Правильность — соответствие нуждам бизнеса.
  • Понятность.
  • Тестируемость.
  • Расширяемость.
  • Простота.


Передача задачи по написанию тестов QA-отделу, полагаю, не относится к списку должностных обязанностей разработчика. У специалистов QA-отдела есть и более важные дела. Они не должны тратить своё рабочее время, в основном, на тестирование кода методом «чёрного ящика».

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


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

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

Знакомство с SOLID можно начать отсюда. Здесь же мне хотелось бы остановиться на том, что в аббревиатуре SOLID представлено буквой S, то есть — на принципе единственной ответственности (The Single Responsibility Principle). Это — идея, в соответствии с которой методы и классы должны решать лишь одну задачу. Они должны функционировать так, чтобы их деятельность не влияла бы на остальные части системы.

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

Поговорим о пирамиде тестирования.

433f060001679c9206a03158020927ff.png


Пирамида тестирования (изображение взято отсюда)

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

Тесты пользовательского интерфейса (UI Tests) и сервисные тесты (Service Tests) требуют достаточно больших временных затрат на их проведение. Поэтому основной объём тестов должен быть представлен модульными тестами (Unit Tests), так как на выполнение даже больших количеств подобных тестов уходят миллисекунды. Это, конечно, помогает улучшить и цикл обратной связи, что является одной из основных целей DevOps.

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

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

  • Модульный ли это тест, или нужно рассматривать его как тест более высокого уровня, как сервисный тест?
  • Хорошо ли спроектирован код, каждый ли класс и метод соответствуют принципу единственной ответственности (не отличается ли код сильной связанностью)?


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

У TDD нет никаких преимуществ перед обычной разработкой


По иронии судьбы, о том, что у TDD нет преимуществ перед обычной разработкой, говорят, в основном, те программисты, которые даже не пробовали работать в стиле TDD. А ведь пока что-то не попробуешь, нельзя понять — хорошо это или плохо. Даже группа Coldplay знает о том, что «if you never try you«ll never know».

У TDD есть две главных сильных стороны.

Первое преимущество TDD выглядит достаточно очевидным. Если перед написанием рабочего кода некто всегда пишет тесты для этого кода, то, по определению, в распоряжении программиста всегда оказывается самотестирующийся код. Вот что пишет об этом Мартин Фаулер: «У вас имеется самотестирующийся код в том случае, если вы можете запустить последовательность автоматизированных тестов на кодовой базе и, при успешном завершении тестов, можете быть уверены в том, код лишён существенных дефектов».

Другими словами — применение TDD означает, что код работает именно так, как нужно программисту.

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

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

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

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

TDD — это медленно


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

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

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

В этой работе описана система из четырёх показателей оценки эффективности DevOps:

  1. Частота развёртывания.
  2. Время выполнения изменений.
  3. Время восстановления сервиса.
  4. Частота провалов при изменениях.


Меня тут особенно интересуют показатели «Частота развёртывания» и «Частота провалов при изменениях».

При использовании TDD, как я уже говорил, исходя из предположения о том, что система была построена исключительно с применением TDD, команда разработчиков, сразу же после внесения изменения в систему, будет знать о том, не нарушило ли это изменение работоспособность в какой-нибудь из частей системы. Применение TDD, кроме того, повышает скорость нахождения ошибок. То, что тесты рассчитаны на проверку весьма небольших фрагментов кода, позволяет очень быстро выяснять то место проекта, в котором произошёл сбой. Это приводит к тому, что ошибки реже обнаруживаются в уже развёрнутой обновлённой версии системы, что снижает показатель «Частота провалов при изменениях».

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

В книге «The Phoenix Project» рассматривается четыре типа работ. Один из них — это «Неожиданная работа». Подобные работы приходится выполнять тогда, когда разработчикам нужно отвлечься от того, чем они заняты, и заняться чем-то другим. Обычно — исправлением некоей ошибки. Если проект построен из самотестирующегося кода — это минимизирует объём ошибок. Это, в свою очередь, приводит к минимизации объёма «неожиданной работы». А чем меньше в жизни разработчика подобных «неожиданностей» — тем лучше он себя чувствует и тем продуктивнее и качественнее работает.

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

Итоги


Как и любой другой инструмент, как и любой другой подход к разработке, методология TDD может поначалу показаться неудобной. А до того, как программисты как следует эту методологию освоят, она может показаться им немного медленной. Что тут сказать? Процитирую Джеза Хамбла: «Если нечто вызывает неприятные ощущения — занимайтесь этим чаще и вы с этим справитесь».

А теперь предлагаю вам попрактиковаться в методологии TDD и заниматься этим до тех пор, пока TDD перестанет вызывать у вас неприятные ощущения :)

Уважаемые читатели! Применяете ли вы методологию TDD при работе над своими проектами?

1ba550d25e8846ce8805de564da6aa63.png

© Habrahabr.ru