Сделайте ваши тесты снова надежными
Разработка программного обеспечения кажется такой же ненадежной, как и работа с треснувшим стеклом - в которой (оставайтесь со мной), чем больше функций у вас есть в вашем приложении, тем больше ошибок (или трещин), которые нужно заполнить тестами (например, много битого стекла). Тесты - это привратники, которые упреждающе выявляют ошибки до того, как код будет выпущен в производственную среду. Тогда почему я снова и снова обнаруживаю, что исправляю старые тесты каждый раз, когда делаю обновления кода? Вот мой ответ.
Детали реализации тестирования
Приходилось ли вам когда-нибудь исправлять свои тесты из-за того, что вы обновляли имя ключа состояния в своем приложении React? Как насчет того, чтобы удалить <div>
с id
, который искали ваши тесты? Считаете ли вы, что ваши тесты настолько хрупкие, что любые обновления кода могут привести к их сбою?
Не волнуйся, ты не единственный. Бесчисленные разработчики (в том числе и я) попадают в ловушку деталей тестирования реализации вместо реального взаимодействия с пользователем. Однако мы не единственные виновные стороны. Доступные библиотеки тестирования, такие как Enzyme, предоставляют API-интерфейсы, позволяющие напрямую изменять состояние приложения, что побуждает к тестированию деталей реализации. Например, используя примеры из setState API:
// Foo.js
class Foo extends React.Component {
constructor(props) {
super(props);
this.state = { name: 'foo' };
}
render() {
const { name } = this.state;
return (
<div className={name} />
);
}
}
// Foo.test.js
const wrapper = shallow(<Foo />);
expect(wrapper.find('.foo')).to.have.lengthOf(1);
expect(wrapper.find('.bar')).to.have.lengthOf(0);
wrapper.setState({ name: 'bar' });
expect(wrapper.find('.foo')).to.have.lengthOf(0);
expect(wrapper.find('.bar')).to.have.lengthOf(1);
Этот пример выглядит довольно прямолинейным, но представьте, что если вы измените имя состояния на firstName
вместо name
, ваш тест немедленно прервется, потому что состояние name
отсутствует в вашем новом объекте состояния приложения. Это скрытое следствие деталей реализации тестирования.
Почему библиотека тестирования React?
React Testing Library - это библиотека тестирования, разработанная мастером React Kent C. Dodds и многочисленными уважаемыми разработчиками со всего мира. На мой взгляд, RTL лучше других библиотек тестирования, потому что он
- Предотвращает детали реализации тестирования
- Имитирует реальное взаимодействие пользователя с компонентами
- Самостоятельные документы
- Предоставляет отличные инструменты, упрощающие написание тестов.
Теперь позвольте мне глубже погрузиться в эти преимущества и, надеюсь, убедить вас сесть на поезд RTL.
Использование библиотеки тестирования React
Как я уже упоминал выше, детали реализации тестирования делают тесты ненадежными и могут вызвать значительные накладные расходы на управление. Давайте посмотрим, как RTL решает эту проблему.
У нас есть простой счетчик, который может увеличивать или уменьшать отображаемое количество.
import React, { useState } from 'react'; const Counter = () => { const [count, setCount] = useState(0); const increment = () => setCount(count => count + 1); const decrement = () => setCount(count => count - 1); return ( <div> <div>Count: {count}</div> <button onClick={decrement}>Decrement</button> <button onClick={increment}>Increment</button> </div> ); }; export default Counter;
С RTL ваш тест может выглядеть так
import * as React from 'react'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import Counter from './counter'; test('should update count when button clicked', () => { const { container } = render(<Counter />); const increment = screen.getByRole('button', {name: /increment/i}); const decrement = screen.getByRole('button', {name: /decrement/i}); const message = screen.getByText(/count/i); expect(message).toHaveTextContent('Count: 0'); userEvent.click(increment); expect(message).toHaveTextContent('Count: 1'); userEvent.click(decrement); expect(message).toHaveTextContent('Count: 0'); });
Из этого примера мы видим, что мы не манипулируем напрямую каким-либо состоянием приложения; Скорее, мы имитируем то, как пользователь будет взаимодействовать с приложением. Делая это, мы можем свободно обновлять компонент <Counter />
и даже добавлять новые функции, не беспокоясь о сбоях тестов. Эти тесты останутся действительными до тех пор, пока мы не изменим поведение тестируемых функций. Кроме того, тесты, подобные приведенным выше, настолько ясны и понятны, что могут легко служить документацией для ваших компонентов.
💡 Когда я просматриваю код, я всегда просматриваю тесты перед реальным кодом, чтобы понять ожидаемое поведение. Подобные тесты определяют четкие спецификации того, что должен делать компонент.
userEvent
Вы можете заметить, что мы используем userEvent
API от @testing-library/user-event
. Это не обычная MouseEvent('click')
функция. Наши старые способы создания MouseEvents для взаимодействия с элементом также были формой тестирования деталей реализации. Отправка события click вручную не проверяет, является ли элемент интерактивным или интерактивным, что может скрыть смертельную ошибку в вашем рабочем коде. userEvent
точно имитирует реальное взаимодействие с пользователем и предупредит вас, если элемент недоступен для клика. Если этого мало, существует целый список библиотек в экосистеме RTL.
Инструменты разработчика
Начинаете видеть его ценности?
screen.debug() & screen.logTestingPlaygroundURL()
Иногда бывает сложно найти правильный элемент, который вы хотите протестировать, и это определенно сбивает меня с толку, когда запрос, в котором я так уверен, возвращает null
или undefined
. screen
API от @testing-library/dom
имеет две функции, которые устранят двусмысленность вокруг этого. Добавив screen.debug()
в свой тест, когда тест будет запущен, вы сможете увидеть дерево DOM для визуализированного компонента, например:
Это позволит вам точно определить правильный запрос для утверждения в ваших тестах. Если это все еще недостаточно очевидно, вы можете использовать screen.logTestingPlaygroundURL()
, чтобы понять это за вас. Эта команда вернет сгенерированную ссылку, по которой вы можете перейти в своем браузере:
Это подводит нас к следующему и последнему пункту продажи библиотеки тестирования React.
Тестовая площадка
Несмотря на то, что нам, разработчикам, нравится программировать, иногда приятно отдохнуть от утомительной работы и позволить другим инструментам сделать за нас часть тяжелой работы. Именно здесь на сцену выходит площадка для тестирования. Площадка для тестирования - это интерактивный сайт, на котором вы можете вставить дерево DOM и найти точный запрос для элемента, который вам нужен. Вы можете либо вставить результаты из screen.debug()
, либо перейти по ссылке вывода из screen.logTestingPlaygroundURL()
, чтобы начать работу, и выбрать элемент, который вы хотите запросить, скопировать и вставить результат прямо в свои тесты. Пользовательский интерфейс похож на проверку элемента с помощью инструмента разработчика, поэтому нет необходимости в обучении, чтобы начать использовать сайт в полном объеме. Чтобы сделать его еще более удобным, есть также расширение для браузера, которое позволяет вам взаимодействовать с любым сайтом!
Заключение
Написание тестов, вероятно, не так увлекательно, как разработка новых функций, а необходимость переписывать тесты каждый раз, когда мы обновляем наш код, делает это еще менее приятным. Нам нужно прекратить тестирование деталей реализации и начать зеркальное отображение взаимодействия с пользователем.
Библиотека тестирования React затрудняет тестирование деталей реализации и проливает свет на то, как правильно писать тесты. Мне, например, очень нравится его использование, и я надеюсь, что вы тоже. 😄
Больше контента на plainenglish.io