«Почему большинство разработчиков боятся постоянно вносить изменения в свой код? Боятся, что сломают! Почему они боятся сломать его? Потому что у них нет тестов ».
- Роберт К. Мартин, Чистый кодер: Кодекс поведения для профессиональных программистов
Хороший софт хорошо протестирован. Вот некоторые основы использования React и Jest, которые помогут начать работу с модульным тестом.
Что такое модульное тестирование?
Модульное тестирование - это когда мы тестируем отдельные участки кода. В React это может быть компонент или функция.
Почему мы проводим модульное тестирование?
Модульное тестирование - это самый маленький и простой тест, который мы можем написать. Это позволяет выявлять ошибки на раннем этапе, что означает, что в более поздних тестах (интеграция, регрессия и т. Д.) Требуется гораздо меньше усилий. Мы хотим сосредоточиться на модульных тестах, так как они могут быть написаны на стадии разработки и сохранят эффективность на должном уровне.
Источник: http://www.testingreferences.com/here_be_pyramids.php
Что нам следует тестировать?
Мы хотим протестировать как счастливый, так и несчастливый путь для компонента. Удачный путь предполагает, что все, что делает пользователь, соответствует ожиданиям. Несчастливый путь предполагает, что пользователь пытается сделать что-то неожиданное, например, если пользователь пытается ввести числа в текстовое поле.
Тесты должны проверять то, что видит пользователь, проверять поведение приложения, а не детали реализации. Поведенческое тестирование похоже на проверку того, действительно ли всплывающее окно появляется после нажатия этой кнопки. Детали реализации были бы чем-то вроде проверки состояния этого компонента после нажатия кнопки. Тестирование того, что пользователь действительно видит / использует, гарантирует, что мы лучше охватим его опыт. Автоматические тесты должны подтвердить, что код приложения работает для производственных пользователей. (Https://kentcdodds.com/blog/testing-implementation-details)
После того, как мы напишем тест и довольны результатом, его не нужно будет переписывать. Единственный случай, когда это нужно будет изменить, - это если функциональность изменилась. Нефункциональные требования не должны препятствовать прохождению тестов.
Когда он перестанет быть модульным тестом?
Тест не является модульным, если:
- Он обращается к базе данных
- Он общается по сети
- Это касается файловой системы
- Его нельзя запускать одновременно с другими модульными тестами.
- Вы должны делать особые вещи в своей среде (например, редактировать файлы конфигурации), чтобы запустить ее.
(Https://www.artima.com/weblogs/viewpost.jsp?thread=126923)
Это не исчерпывающий список, но несколько хороших примеров того, что НЕ должен охватывать модульный тест. Модульные тесты должны быть автономными и не полагаться друг на друга. Если тест основан на состоянии предыдущего теста или другого компонента, это НЕ модуль.
Покрытие кода
Хотя было бы неплохо иметь 100% покрытие кода, это не цель. Покрытие кода не принимает во внимание, что на самом деле тестируют тесты, и их качество. Покрытие кода дает нам оценку того, насколько хорошо протестирован наш код. Однако мы должны сосредоточиться на функциональности.
Хорошее обсуждение других показателей и покрытия кода: https://stackoverflow.com/questions/90002/what-is-a-reasonable-code-coverage-for-unit-tests-and-why
Тестовые пары
Использование тестовых двойников позволяет нам изолировать желаемое поведение, сосредоточиться на жизненно важных частях теста и смоделировать все остальное. Jest - хороший фреймворк, который мы можем использовать (https://jestjs.io/) для этого.
Например, мы можем высмеивать несущественные методы так:
- Когда нас не волнует результат, мы можем использовать jest.fn ()
const { getByTestId } = render( <MUIPicker testid="endDate" isDisabled={false} onBlur={jest.fn} /> );
- Когда нас действительно волнует результат работы метода, мы можем использовать mockImplementation ().
API.post = jest.fn().mockImplementation(() => { return Promise.reject({ response: { data } }); });
Если мы имитируем что-то, нам также необходимо сбрасывать макеты после каждого теста и предварительно инициализировать их заново. Для этого можно использовать beforeEach () и afterEach ().
beforeEach(() => { jest.fn().mockRestore(); }); afterEach(() => { jest.fn().mockClear(); });
Дополнительная информация: https://jestjs.io/docs/en/mock-function-api
Зацикливание
Если у вас есть похожие тесты, которые могут требовать только разных входных данных, чтобы следовать принципу СУХОЙ (Не повторяйтесь) чистого кода, мы можем использовать циклы.
it.each` errorObject | id | message ${"success"} | ${"elementID1"} | ${"successful import"} ${"warning"} | ${"elementID2"} | ${"import with warnings"} ${"failure"} | ${"elementID3"} | ${"failed import"} `(`Should show correct ($errorObject) on error`, ({ id, message }) => { fireEvent.click(testRender.getByTestID(id)); expect(testRender.getByTestID("message")).toEqual(message); } );
Асинхронный
Не все тесты должны быть синхронными. Используя async, мы можем дождаться желаемых результатов для таких вещей, как вызовы API или изменения состояния.
test('the data is peanut butter', async () => { const data = await fetchData(); expect(data).toBe('peanut butter'); });
Дополнительная информация: https://jestjs.io/docs/en/asynchronous.html