С методологией TDD (Test-Driven Development) связано немало противоречивых мнений. Но не будем вступать в дискуссии о востребованности TDD. Сейчас я постараюсь показать пользу этого инструмента в повседневной разработке кода, рассматривая его лишь с точки зрения практической ценности.
1. Постановка задачи
В качестве примера будем использовать приложение 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
, чтобы проверить, проходит ли теперь тест.
В calculate
я изменяю add
в:
export const add = (x, y) => x - y;
И если код не работает, я знаю, что тест и реализация взаимосвязаны, можно далее совершенствовать тест расширением (прежде не забудьте изменить его обратно).
Написание тестов
Помните, тестировать всегда нужно с точки зрения пользователя. Пару лет назад в сообществе React
была довольно популярна библиотека Enzyme
. Ее самым большим недостатком было то, что она чаще тестировала внутреннее состояние, а не поведение.
Например, с Enzyme
может быть такой код:
Но тесты react-testing-library
фокусируются больше на поведении компонента, чем на внутреннем состоянии.
Обратите внимание на API: getByText
проверит, есть ли в DOM этот контент (точно так же, как это сделал бы реальный пользователь в браузере), как и toBeInTheDocument
, фокус смещается на пользователя (а не на разработчика).
При создании теста описывайте по возможности поведение. И проверяйте бизнес-возможность, а не внутреннее состояние.
3. Создание теста с минимальными усилиями
Приоритетом на этом этапе должен быть просто работающий тест, вместо стремления к превосходному и элегантному коду. Как говорится: “сделанное лучше идеального”.
Магические числа (magic numbers), встроенные функции, вложенные операторы if-else
и прочее — все это подойдет на данном этапе. Но не останавливайтесь на достигнутом. Как только тесты станут рабочими, ищите возможности их улучшить.
4. Выполнение рефакторинга
Мартин Фаулер в своей книге “Рефакторинг: улучшение дизайна существующего кода” дает такие определения.
Рефакторинг (существительное) — внесение изменений во внутреннюю структуру программы для того, чтобы сделать ее более понятной и удобной для модификации, не изменяя наблюдаемое поведение.
Рефакторинг (глагол) — реструктурирование кода программы без изменения наблюдаемого поведения. Повторяйте описанный выше шаг до выполнения всех поставленных задач, затем перейдите к следующему шагу.
Ключевыми особенностями здесь являются.
- Изменения не должны менять наблюдаемое поведение.
- Изменения должны быть небольшими и последовательными.
Во время рефакторинга нужно следить за статусом теста. После завершения рефакторинга (переименование, извлечение переменной в константу или извлечение фрагмента в функцию) он всегда должен иметь статус passing
(успешное завершение). Кроме того, сохраняйте каждое небольшое изменение. По завершении рефакторинга и тестирования я обычно делаю локальную фиксацию (commit). А если предстоит сделать несколько фиксаций, их всегда можно объединить перед отправкой на удаленный сервер.
В общем, нужно выполнить рефакторинг кода, устранив погрешности быстрой черновой реализации.
После удачного завершения рефакторинга, можно применить шаги 2-4 ко всем остальным задачам из списка.
5. Отзывы коллег
Поначалу я не был большим поклонником рецензирования. Но однажды понял, что даже комментарии младших разработчиков могут заслуживать внимания.
Я предпочитаю пошаговый анализ (5-10 минут), чтобы обсудить предысторию, проблемы и использованные решения. И при этом часто получаю несколько полезных предложений, позволяющих очистить код, сделать его более понятным.
Есть и другие преимущества такой экспертной оценки внутри команды:
- обмен специфическими познаниями;
- обмен мнениями по проблеме;
- обсуждение используемых членами команды общих шаблонов проектирования.
Заключение
В этой статье не рассматривалась целесообразность применения TDD. Она заинтересует в первую очередь тех, кто намеревается использовать эту методику. Выполняя рассмотренные шаги, можно увидеть, как она действует. Эти 5 простых и действенных шагов я регулярно выполняю практически со всеми своими задачами. Считаю, что это приносит большую пользу и мне, и моим коллегам в разных проектах.
Читайте также:
- Проектирование архитектуры ПО React: лучшие практики
- Что такое разработка через тестирование и как эффективно ее использовать
- Рекомендации по обработке и регистрации ошибок в React
Читайте нас в Telegram, VK и Дзен
Перевод статьи Juntao Qiu: 5 Steps to Apply TDD More Effectively in React