Полное руководство по модульному тестированию в приложениях React с помощью Jest и React-Testing

Введение

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

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

Компоненты React можно тестировать несколькими способами, которые можно условно разделить на две группы:

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

Хотя тестирование приложений React можно выполнять несколькими способами, в этом руководстве мы создадим приложение React и рассмотрим полное руководство о том, как мы можем выполнять модульные тесты в приложении React, используя Шутка и Реактивная библиотека тестирования чтобы вы могли отточить свои навыки тестирования и научиться создавать ручное приложение React.

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

Что такое тестирование?

Прежде всего, давайте посмотрим на вещи в перспективе. Тестирование — очень широкий термин, который может относиться к ручному тестированию, модульному тестированию, регрессионному тестированию, интеграционному тестированию, нагрузочному тестированию и т. д.

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

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

In Разработка через тестирование (TDD), вам рекомендуется написать тест и ожидаемое значение, прежде чем писать логику для метода. Естественно, поначалу не получится. Однако после этого вы просто заставляете его работать, и когда он проходит тест, вы начинаете рефакторинг, чтобы сделать его короче, чище, быстрее и т. д. Пока результат остается прежним, вы знаете, что ничего не сломали. при рефакторинге!

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

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

Подходы к тестированию

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

Ручное тестирование

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

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

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

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

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

Автоматизированное тестирование

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

Существует множество методов написания автоматизированных тестов:

  • Написание программ для автоматизации браузера,
  • Вызов функций непосредственно из исходного кода,
  • Сравнение скриншотов отрендеренного приложения…

Каждая техника имеет свой набор преимуществ, но все они имеют одну общую черту – они экономят ваше время и обеспечивают более высокое качество кода по сравнению с ручным тестированием!

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

Типы тестирования

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

Существует три типа тестов интерфейсных приложений:

  • Модульные тесты: В модульных тестах тестируются отдельные модули или компоненты программного обеспечения. Индивидуальная единица — это отдельная функция, метод, процедура, модуль, компонент или объект. Модульный тест изолирует и проверяет часть кода, чтобы убедиться, что каждая единица программного кода работает должным образом.
    Отдельные модули или функции тестируются в модульном тестировании, чтобы убедиться, что они работают должным образом, и все компоненты также тестируются индивидуально. Модульное тестирование может включать, например, определение того, правильно ли работает функция, оператор или цикл в программе.

  • Моментальные тесты: этот тип теста гарантирует, что пользовательский интерфейс (UI) веб-приложения не изменится неожиданно. Он фиксирует код компонента в определенный момент времени, что позволяет нам сравнивать компонент в одном состоянии с любым другим возможным состоянием, которое он может принять.
    Типичный сценарий тестирования моментального снимка включает отрисовку компонента пользовательского интерфейса, создание моментального снимка и сравнение этого снимка с эталонным файлом моментального снимка, хранящимся в тесте. Если два снимка различаются, тест завершится ошибкой, поскольку изменение было либо неожиданным, либо эталонный снимок необходимо обновить, чтобы отразить новый компонент пользовательского интерфейса.

  • Сквозные тесты: Сквозные тесты — самый простой для понимания тип тестов. Сквозные тесты во внешних приложениях автоматизируют браузер, чтобы убедиться, что приложение работает правильно с точки зрения пользователя.
    Сквозные тесты экономят много времени. Вы можете запустить сквозной тест столько раз, сколько захотите, после того, как вы его написали. Подумайте, сколько времени потенциально может сэкономить набор из сотен этих тестов по сравнению с написанием тестов для каждого отдельного модуля.
    При всех преимуществах сквозных тестов есть несколько проблем. Во-первых, сквозные тесты отнимают много времени. Еще одна проблема со сквозными тестами заключается в том, что их сложно отлаживать.

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

Если вы хотите понять основы сквозного тестирования с помощью Cypress, прочтите нашу «Сквозное тестирование в JavaScript с помощью Cypress»!

Преимущества и недостатки тестирования

Хотя тестирование важно и должно проводиться, как обычно, оно имеет как преимущества, так и недостатки.

Преимущества

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

Недостатки бонуса без депозита

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

Обзор модульного тестирования

До сих пор мы рассматривали тестирование в целом. Настало время погрузиться во все, что относится к модульному тестированию и тому, как писать модульные тесты в приложениях React!

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

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

Плюсы модульных тестов

Модульные тесты выполняются быстро и могут быть запущены за несколько секунд (либо индивидуально для новой функции, либо глобально для всех тестов), предоставляя разработчикам немедленную обратную связь о том, сломана ли функция или нет. Они также помогают предоставлять документацию, потому что, если новый разработчик присоединится к проекту, ему нужно будет знать, как ведут себя различные блоки кодовой базы; это можно узнать, посмотрев на результаты модульных тестов.

Минусы модульных тестов

Хотя модульные тесты имеют свои хорошие стороны, у них также есть свои проблемы. Проблема в том, что код рефакторинга, когда дело доходит до изменения дизайна, так как это, как правило, сложнее с модульными тестами. Скажем, например, у вас есть сложная функция с ее модульными тестами, и вы хотите разделить эту функцию на несколько модульных функций. Модульный тест для этой функции, скорее всего, не удастся, и вам придется объявить его устаревшим и написать два модульных теста для разделенных функций. Вот почему модульное тестирование неявно поощряет их предварительное разделение и индивидуальное тестирование, что приводит к более тестируемым модульным компонентам кода. Тем не менее, в некоторых случаях вы не можете предвидеть возможные изменения в будущем, а количество времени, необходимое для обновления модульных тестов, делает серьезные процессы рефакторинга менее привлекательными.

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

Модульное тестирование приложения React — демонстрационный проект

Давайте посмотрим на реальный пример модульного тестирования приложения React!

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

Настройка проекта

Ассоциация create-react-app Команда, созданная командой React, — лучший способ начать создание реального и крупномасштабного приложения React, поскольку она готова к использованию и легко работает с Библиотека шутливого тестирования. Если вы откроете package.json файл, вы обнаружите, что у нас есть поддержка по умолчанию для Шутка и Библиотека тестирования React в setupTests.js файл. Это избавляет нас от необходимости вручную устанавливать Jest в наш проект, если нам это нужно!

Если вы еще не использовали его - запустите его с помощью npx, который установит его для последующего использования:

$ npx create-react-app react-unit-tests

Если у вас уже установлен этот инструмент, создайте приложение React и назовите его react-unit-tests:

$ create-react-app react-unit-tests

Примечание: npx использует последнюю версию create-react-app, в то время как глобально установленная версия может не работать. Обычно рекомендуется запускать инструмент через npx чтобы обеспечить наличие последних версий, если только вы намеренно не хотите использовать другую версию.

Далее входим в директорию проекта и запускаем сервер разработки:

$ cd react-unit-tests && npm start
// OR
$ cd react-unit-tests && yarn start

Это выведет наше вновь созданное приложение в браузере по адресу localhost:3000.

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

Создание компонента счетчика

В src каталог нашего проекта, создайте новый файл с именем Counter.js. В Counter.js, мы определим все части компонента. Он будет содержать различные функции счетчика, в том числе increment(), decrement(), restart()и switchSign(), который инвертирует значение счетчика с отрицательного на положительное при нажатии. Эти функции созданы для управления начальным значением счетчика (которое передается как свойство):


import React, { useState } from "react";

function Counter({ initialCount }) {
  const [count, setCount] = useState(initialCount);

  const increment = () => {
    setCount((prev) => prev + 1);
  };

  const decrement = () => {
    setCount((prev) => prev - 1);
  };

  const restart = () => {
    setCount(0);
  };

  const switchSign = () => {
    setCount((prev) => prev * -1);
  };

  return (
    <div>
      <h1>
        Count: <h3>{count}</h3>
      </h1>
      <div>
        <button onClick={increment}>Increment</button>
        <button onClick={decrement}>Decrement</button>
        <button onClick={restart}>Restart</button>
        <button onClick={switchSign}>Switch sign</button>
      </div>
    </div>
  );
}

export default Counter;

Затем обновите App.js:


import "./App.css";
import Counter from "./Counter";

function App() {
  return (
    <div className="App">
      <Counter />
    </div>
  );
}

export default App;

Теперь мы можем просмотреть приложение счетчика в браузере:

Полное руководство по модульному тестированию в приложениях React с помощью Jest и React-Testing PlatoBlockchain Data Intelligence. Вертикальный поиск. Ай.

Создание тестов для компонентов

Давайте создадим тестовый файл с именем Counter.test.js для представления теста для компонента Counter. Не забудьте также удалить App.test.js так что это не создает нежелательных результатов, пока мы запускаем тесты.

Примечание: Общепринятой практикой является именование тестовых файлов с суффиксом .test.js, отражающее имя файла/компонента, который вы тестируете. Это обеспечивает непрерывность между тестовыми файлами, изменения вносятся только в файлы, относящиеся к коду, который вы обновляете, при отправке изменений (меньшее количество конфликтов слияния) и доступны для чтения.

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

In Counter.test.js, мы сначала импортируем Counter компонента, затем запустите тест с describe() function для описания всех различных функций, которые могут выполняться внутри компонента.

Ассоциация describe() Функция используется для группировки определенных наборов тестов, которые могут выполняться на компоненте с использованием различных it() и test() методы. Это своего рода логическая оболочка, в которой вы, ну, описываете, что делает серия тестов, с каждым it() являющийся функциональным тестом для устройства.

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

Тестирование начального значения счетчика

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

Первое, что мы собираемся протестировать, это начальное значение счета и как компонент обрабатывает реквизит, который его устанавливает. С it() метод, мы проверяем, действительно ли приложение-счетчик отображает точное начальное значение счетчика, которое было передано в качестве реквизита, т.е. 0 в этом случае и передать функцию обратного вызова, описывающую все действия, которые будут происходить внутри теста:


import { render, screen } from "@testing-library/react";
import Counter from "./Counter";

describe(Counter, () => {
  it("counter displays correct initial count", () => {
    render(<Counter initialCount={0} />);
    expect(screen.getByTestId("count").textContent).toEqual(0);
  });
});

Здесь мы использовали screen экземпляр из библиотеки React Testing для рендеринга компонента в целях тестирования. Полезно визуализировать фиктивную версию тестируемого компонента. И так как

элемент, содержащий count значение должно динамически изменяться, мы используем screen.getByTestId() функция, чтобы прослушать его и получить его значение с помощью textContent имущество.

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

Примечание: Ассоциация screen Функция возвращает соответствующий DOM-узел для любого запроса или выдает ошибку, если элемент не найден.

Затем в Counter.js компонент, мы будем слушать

элемент во время тестирования, установив data-testid атрибут элемента со значением count:


import React, { useState } from "react";

function Counter({ initialCount }) {
  const [count, setCount] = useState(initialCount);
  const increment = () => {
    setCount((prev) => prev + 1);
  };
  const decrement = () => {
    setCount((prev) => prev - 1);
  };
  const restart = () => {
    setCount(0);
  };
  const switchSign = () => {
    setCount((prev) => prev * -1);
  };

  return (
    <div>
      <h1>
      	
        Count: <h3 data-testid="count">{count}</h3>
      </h1>
      <div>
        <button onClick={increment}>Increment</button>
        <button onClick={decrement}>Decrement</button>
        <button onClick={restart}>Restart</button>
        <button onClick={switchSign}>Switch sign</button>
      </div>
    </div>
  );
}

export default Counter;

Чтобы проверить, является ли начальный count значение равно 0, мы используем expect() метод, чтобы описать, что ожидается от теста, который мы установили! В нашем случае мы ожидаем, что начальное значение счетчика будет 0 поэтому мы используем toEqual() метод, который используется для определения совпадения значений двух объектов. Вместо определения личности объекта toEqual() matcher рекурсивно проверяет все поля на равенство.

Примечание: Тест специально ориентирован на информацию, которую вы предоставляете; в нашем примере, то есть Counter компонент, получивший initialCount опора Это говорит о том, что даже если другой файл — скажем, давайте App.js-отсутствует реквизит в Counter компонент, тест все равно будет пройден, потому что он сосредоточен исключительно на Counter.js и не умеет пользоваться Counter составная часть. Кроме того, поскольку тесты не зависят друг от друга, рендеринг одного и того же компонента с разными реквизитами в других тестах также не повлияет.

Теперь мы можем запустить набор тестов:

$ yarn test

Тест должен провалиться:

FAIL  src/Counter.test.js
  Counter
    × counter displays correct initial count (75 ms)

  ● Counter › counter displays correct initial count

    expect(received).toEqual(expected) // deep equality

    Expected: 0
    Received: "0"

       5 |   it("counter displays correct initial count", () => {
       6 |     render();
    >  7 |     expect(screen.getByTestId("count").textContent).toEqual(0);
         |                                                     ^
       8 |   });
       9 | });
      10 |

      at Object. (src/Counter.test.js:7:53)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        1.929 s, estimated 2 s
Ran all test suites related to changed files.

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


import { render, screen } from "@testing-library/react";
import Counter from "./Counter";

describe(Counter, () => {
  it("counter displays correct initial count", () => {
    render(<Counter initialCount={0} />);
     
    expect(Number(screen.getByTestId("count").textContent)).toEqual(0);
  });
});

Теперь наш код пройдет первый тест:

$ yarn test

PASS  src/Counter.test.js
  Counter
    √ counter displays correct initial count (81 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        2.271 s
Ran all test suites related to changed files.

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

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

Тестирование кнопки увеличения

Чтобы проверить, что increment кнопка работает как надо, то есть увеличивает count значение на единицу при каждом нажатии, нам нужно сначала получить доступ к increment кнопку, затем мы определяем новый it() метод для того же.

Так как значение кнопки не динамическое, то есть всегда будет иметь значение Increment внутри него мы используем getByRole() метод вместо getByTestId() для запроса DOM.

При использовании getByRole() метод, роль описывает элемент HTML.

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

Следующее, что нужно сделать, это смоделировать событие щелчка с помощью fireEvent() метод, который позволяет запускать события, имитирующие действия пользователя во время тестирования.

Во-первых, мы пишем тест, чтобы увидеть, увеличивается ли значение счетчика на 1 по сравнению с его начальным значением 0:


import { fireEvent, render, screen } from "@testing-library/react";
import Counter from "./Counter";

describe(Counter, () => {
  it("counter displays correct initial count", () => {
    render(<Counter initialCount={0} />);
    expect(Number(screen.getByTestId("count").textContent)).toEqual(0);
  });

  it("count should increment by 1 if increment button is clicked", () => {
    render(<Counter initialCount={0} />);
    fireEvent.click(screen.getByRole("button", { name: "Increment" }));
    let countValue = Number(screen.getByTestId("count").textContent);
    expect(countValue).toEqual(1);
  });
});

Это приводит к:

$ yarn test

PASS  src/Counter.test.js
  Counter
    √ counter displays correct initial count (79 ms)
    √ count should increment by 1 if increment button is clicked (66 ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        2.405 s
Ran all test suites related to changed files.

Затем мы также можем написать тест, чтобы проверить, count значение было равно 0 до того, как кнопка была нажата путем определения двух expect() методы — один перед запуском события клика, а другой — после запуска события клика:


it("count should increment by 1 if increment button is clicked", () => {
    render(<Counter initialCount={0} />);
    let countValue1 = Number(screen.getByTestId("count").textContent);
    expect(countValue1).toEqual(0);
    fireEvent.click(screen.getByRole("button", { name: "Increment" }));
    let countValue2 = Number(screen.getByTestId("count").textContent);
    expect(countValue2).toEqual(1);
});

Тесты все же прошли:

$ yarn test

PASS  src/Counter.test.js
  Counter
    √ counter displays correct initial count (82 ms)
    √ count should increment by 1 if increment button is clicked (60 ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        2.388 s
Ran all test suites related to changed files.

Проверка кнопки уменьшения

Таким же образом мы написали тест для Increment кнопку, мы определяем тест для Decrement кнопка такая:


it("count should decrement by 1 if decrement button is clicked", () => {
  render(<Counter initialCount={0} />);
  fireEvent.click(screen.getByRole("button", { name: "Decrement" }));
  let countValue = Number(screen.getByTestId("count").textContent);
  expect(countValue).toEqual(-1);
});

Это приводит к:

$ yarn test

PASS  src/Counter.test.js
  Counter
    √ counter displays correct initial count (79 ms)
    √ count should increment by 1 if increment button is clicked (73 ms)
    √ count should decrement by 1 if decrement button is clicked (21 ms)

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        2.346 s
Ran all test suites related to changed files.

Проверка кнопки перезагрузки

Как и в случае Increment и Decrement кнопки, мы определяем тест для Restart кнопка такая:


it("count should reset to 0 if restart button is clicked", () => {
  render(<Counter initialCount={50} />);
  fireEvent.click(screen.getByRole("button", { name: "Restart" }));
  let countValue = Number(screen.getByTestId("count").textContent);
  expect(countValue).toEqual(0);
});

В целях тестирования начальное значение было установлено равным 50 (произвольное значение), и при запуске теста все четыре теста проходят успешно:

$ yarn test

PASS  src/Counter.test.js
  Counter
    √ counter displays correct initial count (81 ms)
    √ count should increment by 1 if increment button is clicked (57 ms)
    √ count should decrement by 1 if decrement button is clicked (21 ms)
    √ count should reset to 0 if restart button is clicked (16 ms)

Test Suites: 1 passed, 1 total
Tests:       4 passed, 4 total
Snapshots:   0 total
Time:        2.583 s
Ran all test suites related to changed files.

Тестирование кнопки переключения знака

Также напишем тест на инвертирование знака на count значение, установив значение count до 50 в тестовом файле. Затем посмотрите, какой знак отображается до и после нажатия кнопки:


it("count invert signs if switch signs button is clicked", () => {
  render(<Counter initialCount={50} />);
  let countValue1 = Number(screen.getByTestId("count").textContent);
  expect(countValue1).toEqual(50);
  fireEvent.click(screen.getByRole("button", { name: "Switch signs" }));
  let countValue2 = Number(screen.getByTestId("count").textContent);
  expect(countValue2).toEqual(-50);
});

Это приводит к:

$ yarn test

PASS  src/Counter.test.js
  Counter
    √ counter displays correct initial count (91 ms)
    √ count should increment by 1 if increment button is clicked (72 ms)
    √ count should decrement by 1 if increment button is clicked (21 ms)
    √ count should reset to 0 if restart button is clicked (19 ms)
    √ count invert signs if switch signs button is clicked (14 ms)

Test Suites: 1 passed, 1 total
Tests:       5 passed, 5 total
Snapshots:   0 total
Time:        3.104 s
Ran all test suites related to changed files.

Ууух! Все тесты для нашего приложения-счетчика прошли успешно.

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

В общем, хорошей отправной точкой для тестирования является:

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

Лучшие практики модульного тестирования

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

  • Избегайте ненужных тестов: Хорошие тесты не сопровождаются ненужными ожиданиями или тест-кейсами.
    Мы можем найти лучшее понимание, взглянув на тесты ниже:

test('the success modal is visible', () => {});
test('the success modal has a success message', () => {});

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

  • Избегайте раскрытия внутренней логики: Если ваш тест выполняет действие, которое не выполняет ваш пользователь (например, тестирование внутреннего метода, который не доступен пользователю), вы, скорее всего, тестируете детали реализации. Вы можете в конечном итоге открыть приватную функцию исключительно для тестирования вашего компонента. Это запах кода, которого следует избегать. Вместо этого реструктурируйте свою кодовую базу, чтобы приватную функцию можно было протестировать, не раскрывая ее публично.

  • Избегайте тестирования деталей реализации: Если наше приложение увеличивает x и y - ли x увеличивается первым или, скорее всего, не имеет значения, пока результат тот же. Вы должны всегда иметь возможность рефакторить, изменять и иным образом обновлять детали реализации. без нарушения тестов, в противном случае тесты ускорили бы накопление технического долга за счет увеличения стоимости рефакторинга и оптимизации.

  • Поместите бизнес-логику в чистые функции, а не в компоненты пользовательского интерфейса..

Заключение

Это руководство в первую очередь посвящено модульному тестированию. Однако было важно, чтобы мы сначала поняли и оценили все, что включает в себя тестирование, включая то, что оно означает, подходы к тестированию, типы тестирования, его преимущества и недостатки.

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

Мы также научились запрашивать DOM при тестировании приложений React с помощью getByTestId() метод. Он кажется очень полезным для определения контейнеров и запросов к элементам с динамическим текстом, но он не должен быть вашим запросом по умолчанию. Вместо того, чтобы использовать getByTestId() метод прямо сейчас, сначала попробуйте один из них:

  • getByRole() - он запрашивает элемент, а также гарантирует, что он доступен с правильной ролью и текстом
  • getByLabelText() — это отличный запрос для взаимодействия с элементами формы, он также проверяет, правильно ли наши метки связаны с нашими входными данными через атрибуты for и id
  • getByText() – Если ни один из двух предыдущих запросов недоступен, getByText() метод будет полезен при доступе к элементам на основе текста, который виден пользователю
  • getByPlaceholderText(): этот запрос предпочтительнее тестового идентификатора, когда все, что вам нужно для запроса элемента, — это заполнитель.

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

Отметка времени:

Больше от Стекабьюс