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

1. Постановка задачи

В качестве примера будем использовать приложение Buy Some Milk.

Приложение Buy Some Milk

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

Вот типичный сложный пример  —  подмножество функций (свойств). Их нужно разбить на части. Такой процесс называют постановкой задачи.

Это важнейшая составляющая, требующая тщательного исполнения. Но бояться не стоит. Здесь, как и во всем остальном, нужна лишь некоторая практика.

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

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

Задачи

Каждая из задач должна удовлетворять принципам S.M.A.R.T.

  • Specific  —  соответствие специфике задачи.
  • Measurable  —  успешность решения задачи должна подтверждаться тестами.
  • Achievable  —  достижимость, нельзя сделать что-то сверх наших возможностей.
  • Relevant  — релевантность, действия должны соответствовать решаемой задаче, а токен входа пользователя не может быть прямой ссылкой на описание со списком продуктов.
  • Time-boxed  —  необходимо отслеживать длительность исполнения. Чрезмерная длительность может указывать на превышение размеров или на невыполнимость задачи.

Для примера Buy Some Milk нетрудно составить список удовлетворяющих таким принципам задач:

  • рендеринг элемента;
  • рендеринг нескольких элементов;
  • добавление элемента в список;
  • очистка поля ввода после добавления элемента;
  • пометка выполненного элемента.

2. Написание теста для отката в случае сбоя

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

Убедитесь, что механизм тестирования работает

Добавляя тесты к существующему коду, важно убедиться, что они связаны с кодом продукта. Например, система запуска ожидает __tests__/*.test.tsx, и когда вы добавите Todo.spec.tsx, он будет проигнорирован. Если тесты пройдены успешно, вы можете оказаться в заблуждении, продолжая править код до тех пор, пока не станет слишком поздно.

Дополнительно я (как и многие другие программисты) всегда применяю еще одну проверку. Это попытка нарушить работу действующего теста. Например, учитывая, что текущий calculate.test.ts проходит успешно, я изменяю некоторую логику в calculate.ts, чтобы проверить, проходит ли теперь тест.

Функция add

В calculate я изменяю add в:

export const add = (x, y) => x - y;

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

Написание тестов

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

Например, с Enzyme может быть такой код:

Использование Enzyme для тестирования

Но тесты react-testing-library фокусируются больше на поведении компонента, чем на внутреннем состоянии.

react-testing-library для тестирования поведения

Обратите внимание на API: getByText проверит, есть ли в DOM этот контент (точно так же, как это сделал бы реальный пользователь в браузере), как и toBeInTheDocument, фокус смещается на пользователя (а не на разработчика).

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

3. Создание теста с минимальными усилиями

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

Магические числа (magic numbers), встроенные функции, вложенные операторы if-else и прочее  —  все это подойдет на данном этапе. Но не останавливайтесь на достигнутом. Как только тесты станут рабочими, ищите возможности их улучшить.

4. Выполнение рефакторинга

Мартин Фаулер в своей книге “Рефакторинг: улучшение дизайна существующего кода” дает такие определения.

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

Рефакторинг (глагол)  —  реструктурирование кода программы без изменения наблюдаемого поведения. Повторяйте описанный выше шаг до выполнения всех поставленных задач, затем перейдите к следующему шагу.

Ключевыми особенностями здесь являются.

  1. Изменения не должны менять наблюдаемое поведение.
  2. Изменения должны быть небольшими и последовательными.

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

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

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

5. Отзывы коллег

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

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

Есть и другие преимущества такой экспертной оценки внутри команды:

  • обмен специфическими познаниями;
  • обмен мнениями по проблеме;
  • обсуждение используемых членами команды общих шаблонов проектирования.

Заключение

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

Читайте также:

Читайте нас в TelegramVK и Дзен


Перевод статьи Juntao Qiu: 5 Steps to Apply TDD More Effectively in React

Предыдущая статьяПочему большинство программистов выбирают Python
Следующая статьяЧто такое Flutter и зачем его изучать?