Люди не любят писать тесты. Большинству из нас это кажется пустой тратой времени. Особенно фронтенд-разработчики. Зачем беспокоиться, если я могу просто пойти и проверить это в браузере? А TDD еще хуже, почему я пишу тесты перед кодом, намеренно ограничивая себя? Я бы сделал это в три раза быстрее, чем вы говорите.

Что ж, ты прав. Но чувствуете ли вы уверенность в своем коде?

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

Теперь вы можете чувствовать себя некомфортно. Это нормально. «Я не знаю, с чего начать», - скажете вы. Это тоже нормально.

Начни с малого

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

Эти компоненты - хорошее место для начала.

Нам нужна детская площадка, давайте создадим проект. Чтобы упростить задачу, воспользуемся Create React App.

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

$ cd %your_workspace_dir%
$ npx create-react-app test-components
$ cd test-components
$ yarn start

Если вы все сделали правильно, вы перейдете на страницу http: // localhost: 3000 / и увидите следующее:

Отлично, проект у нас запущен. Что нам нужно, чтобы начать писать тесты?

Средство выполнения тестов, библиотека утверждений, библиотека имитации. Тех троих нас прикрыл Jest. Он поставляется с приложением create-react-app, поэтому никаких действий не требуется. Затем нам нужно отобразить реагирующие компоненты в наших тестах и ​​взаимодействовать с ними. Энзим сделает это за нас.

yarn add enzyme enzyme-adapter-react-16

Нам также нужно его настроить. Добавить setupTests.js файл в src/

import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });

Выполнено!

Покажи мне уже код

Хорошо, давайте реализуем компонент. Это будет управляемый ввод, поэтому он должен получать обработчики value и onChange. Плюс кучу дополнительных функций, таких как состояния error и disabled. Я знаю, что вы делали это раньше, но на этот раз мы пойдем по пути TDD.

Прежде всего, создайте папку components/Input внутри src/. Здесь будет жить компонент. Теперь давайте добавим файл index.jsx со следующим содержимым.

Он возвращает null, потому что мы еще не готовы его реализовать, нам просто нужен компонент, который мы можем импортировать.

И давайте избавимся от CRA по умолчанию в App.js. Мы также можем использовать там наш недавно созданный компонент.

Красный, зеленый, рефакторинг

Мы хотим, чтобы наш компонент Input фактически отображал элемент ввода. Это будет первое, что нужно проверить.

красный

Создайте файл index.test.js внутри папки компонента.

Запустите yarn test, чтобы увидеть, как это не удается

Что мы сделали? Мы создали набор тестов Входной компонент с тестовым примером визуализирует ввод, который неглубоко отображает наш Input компонент и проверяет, есть ли в нем элемент HTML input.

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

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

Зеленый

Но не хочу, чтобы он потерпел неудачу, правда? Input необходимо исправить.

В терминале вы должны теперь увидеть прохождение теста

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

Рефакторинг

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

Повторить

Теперь к следующей особенности. Например, мы могли бы добавить опору value и ожидать, что Input покажет это значение. Итак, поток будет выглядеть так: сначала мы создаем тест (красный, помните?):

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

Вполне ожидаемо, что тестовый пример не удастся.

Сейчас мы это исправим. Применение минимального необходимого изменения. (Зеленый)

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

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

Завершение компонента

onChange prop является обязательным условием для управляемого ввода. Мне нравится, когда onChange получает строку вместо события. Итак, давайте добавим еще один тест

Это может потребовать некоторых пояснений. jest.fn() - это простой макет функции. Он отслеживает звонки, чтобы вы могли подтвердить их позже. Мы передаем фиктивный обработчик onChange нашему Input компоненту. Затем мы моделируем событие изменения входных данных, передавая { target: {value: newValue} } (имитируя структуру нативного события) функции фермента simulate. Ожидается, что onChange вызывается ровно один раз с заданным значением в качестве аргумента.

Он снова красный! Сделаем это зеленым.

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

Теперь тест должен пройти.

Осталось еще две функции: состояния disabled и error. Когда компонент находится в состоянии error, у него должны быть красные границы и текст. Так что нужно просто добавить класс CSS. Опять же, мы начинаем с неудачного теста:

А потом исправляем:

Что ж, это просто имя класса CSS, и мы не тестировали точную реализацию, например цвет границы или текста. Вы определенно можете это сделать, но в общем случае лучше тестировать логику и стили отдельно. Стили требуют ручной проверки, вам просто нужно пойти и проверить, как выглядит компонент (это одна из вещей, которые мы, люди, можем делать лучше, чем компьютеры). Есть инструменты, которые могут помочь вам в этом, например Сборник рассказов.

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

Тест:

И обновленный компонент:

Обратите внимание, что здесь мы провели рефакторинг. Мы обновили способ установки имени класса, а также обновили обработчик onChange. И мы уверены, что не нарушили существующую функциональность, потому что тесты еще зеленые. В этом сила TDD.

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

Полный пример кода (вместе с некоторыми фиктивными функциями для использования компонента) можно найти здесь.

Вот и все

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

Спасибо за прочтение!

UPD: обновлена ​​часть рефакторинга, спасибо Kjetil Klaussen за то, что заметил мои ошибки.

Эта история публикуется в Noteworthy, куда ежедневно приходят более 10 000 читателей, чтобы узнать о людях и идеях, формирующих наши любимые продукты.

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