Jest Tutorial для начинающих: начало работы с Jest для тестирования JavaScript

Spread the love

Что значит тестирование? Как проверить код JavaScript с помощью Jest? Изучите основы тестирования JavaScript с этим руководством Jest для начинающих!

Оригинальная статья: Valentino GagliardiJest Tutorial for Beginners: Getting Started With Jest for JavaScript Testing

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

В техническом жаргоне тестирование означает проверку того, что наш код отвечает некоторым ожиданиям. Например: функция с именем «transformer» должна возвращать ожидаемый результат при некоторых входных данных.

Существует много видов тестирования, но в основном короткие тесты делятся на три основные категории:

  • модульное или как его еще называют юнит тестирование
  • интеграционное тестирование
  • UI тестирование

В этом руководстве Jest мы рассмотрим только модульное тестирование, но в конце статьи вы найдете ресурсы для других типов тестов.

Jest Tutorial: что такое Jest?

Jest – это JavaScript-тестер (test runner), то есть библиотека JavaScript для создания, запуска и структурирования тестов. Jest распространяется в виде пакета NPM, вы можете установить его в любом проекте JavaScript. Jest – один из самых популярных тестеров в наши дни, который по умолчанию используется для Create React App.

Перво-наперво: откуда мне знать, что тестировать?

Когда дело доходит до тестирования, даже простой блок кода может парализовать новичков. Самый распространенный вопрос – «Как мне узнать, что надо тестировать?». Если вы пишете веб-приложение, хорошей отправной точкой будет тестирование каждой страницы приложения и каждого взаимодействия с пользователем. Но веб-приложения также состоят из блоков кода, таких как функции и модули, которые также должны быть протестированы. В большинстве случаев есть два сценария:

  • вы наследуете устаревший код, который поставляется без тестов
  • Вы должны реализовать новую функциональность с нуля.

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

  1. импортируем функцию для тестирования
  2. предоставляем входные параметры функции
  3. определяем что ожидается на выходе
  4. проверить соответствует ли это то что возвращает функция

На самом деле, это все. Тестирование больше не будет страшным, если вы будете думать так: входные параметрыожидаемый результатоценка результата. Через минуту мы также увидим удобный инструмент для почти точной проверки того, что тестировать. А теперь руки на Jest!

Jest Tutorial: настройка проекта

Как и в каждом проекте JavaScript, вам потребуется среда NPM (убедитесь, что в вашей системе установлен Node). Создайте новую папку и инициализируйте проект:

mkdir getting-started-with-jest && cd $_
npm init -y

Затем установите Jest:

npm i jest --save-dev

Давайте также настроим скрипт NPM для запуска наших тестов из командной строки. Откройте package.json и настройте скрипт с именем «test» для запуска Jest:

  "scripts": {
    "test": "jest"
  },

Теперь все готово!

Jest Tutorial: спецификации и разработка на основе тестирования

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

В этом уроке представим что мы получили довольно простую спецификацию от нашего менеджера проекта. Очень важному клиенту нужна функция JavaScript, которая должна фильтровать массив объектов.

Для каждого объекта мы должны проверить свойство с именем «url», и если значение свойства соответствует предоставленному поисковому термину, мы должны включить соответствующий объект в результирующий массив. Будучи опытным разработчиком JavaScript-кода, вы хотите следовать test-driven development, дисциплине, которая требует написания теста с ошибкой перед началом написания кода.

По умолчанию Jest ожидает найти тестовые файлы в папке с именем __tests__ в папке вашего проекта. Создайте новую папку, затем:

cd getting-started-with-jest
mkdir __tests__

Затем создайте новый файл с именем filterByTerm.spec.js внутри __tests__. Вы можете спросить, почему расширение включает «.spec.». Это соглашение, заимствованное из Ruby для пометки файла как specification для данной функциональности.

А теперь давайте пройдем тестирование!

Jest Tutorial: тестовая структура и первый провальный тест

Время создать свой первый тест Jest. Откройте filterByTerm.spec.js и создайте тестовый блок:

describe("Filter function", () => {
  // test stuff
});

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

Далее мы встретимся с другой функцией под названием test, которая является фактическим тестовым блоком:

describe("Filter function", () => {
  test("it should filter by a search term (link)", () => {
    // actual test
  });
});

На данный момент мы готовы написать тест. Помните, что тестирование – это вопрос входных данных, функций и ожидаемых результатов. Сначала давайте определим простые входные данные, массив объектов:

describe("Filter function", () => {
  test("it should filter by a search term (link)", () => {
    const input = [
      { id: 1, url: "https://www.url1.dev" },
      { id: 2, url: "https://www.url2.dev" },
      { id: 3, url: "https://www.link3.dev" }
    ];
  });
});

Далее мы собираемся определить ожидаемый результат. Согласно заданию тестируемая функция должна исключать объекты, свойство url которых не соответствует заданному поисковому запросу. Мы можем ожидать, например, массив с одним объектом, с заданной ссылкой в качестве поискового запроса:

describe("Filter function", () => {
  test("it should filter by a search term (link)", () => {
    const input = [
      { id: 1, url: "https://www.url1.dev" },
      { id: 2, url: "https://www.url2.dev" },
      { id: 3, url: "https://www.link3.dev" }
    ];

    const output = [{ id: 3, url: "https://www.link3.dev" }];
  });
});

И теперь мы готовы написать реальный тест. Мы будем использовать функцию expect и Jest matcher для проверки того, что наша фиктивная (пока) функция возвращает ожидаемый результат при вызове. Вот тест:

expect(filterByTerm(input, "link")).toEqual(output);

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

filterByTerm(inputArr, "link");

В тесте Jest нужно обернуть вызов функции в expect, которые в сочетании с matcher (функция Jest для проверки выходных данных) выполняет фактические тесты. Вот полный код:

describe("Filter function", () => {
  test("it should filter by a search term (link)", () => {
    const input = [
      { id: 1, url: "https://www.url1.dev" },
      { id: 2, url: "https://www.url2.dev" },
      { id: 3, url: "https://www.link3.dev" }
    ];

    const output = [{ id: 3, url: "https://www.link3.dev" }];

    expect(filterByTerm(input, "link")).toEqual(output);

  });
});

(Чтобы узнать больше о Jest matchers, ознакомьтесь с документацией).

Запустим наш тест:

npm test

Вы увидите что тест не проходит:

 FAIL  __tests__/filterByTerm.spec.js
  Filter function
    ✕ it should filter by a search term (2ms)

  ● Filter function › it should filter by a search term (link)

    ReferenceError: filterByTerm is not defined

       9 |     const output = [{ id: 3, url: "https://www.link3.dev" }];
      10 | 
    > 11 |     expect(filterByTerm(input, "link")).toEqual(output);
         |     ^
      12 |   });
      13 | });
      14 |

“ReferenceError: filterByTerm is not defined. Это хорошая вещь на самом деле. Давайте исправим это в следующем разделе!

Jest Tutorial: исправление теста (и повторение его)

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

Для выполнения теста мы будем использовать встроенную функцию JavaScript под названием filter, которая способна отфильтровывать элементы из массива. Вот минимальная реализация filterByTerm:

function filterByTerm(inputArr, searchTerm) {
  return inputArr.filter(function(arrayElement) {
    return arrayElement.url.match(searchTerm);
  });
}

Вот как это работает: для каждого элемента входного массива мы проверяем свойство «url», сопоставляя его с регулярным выражением методом match. Вот полный код:

function filterByTerm(inputArr, searchTerm) {
  return inputArr.filter(function(arrayElement) {
    return arrayElement.url.match(searchTerm);
  });
}

describe("Filter function", () => {
  test("it should filter by a search term (link)", () => {
    const input = [
      { id: 1, url: "https://www.url1.dev" },
      { id: 2, url: "https://www.url2.dev" },
      { id: 3, url: "https://www.link3.dev" }
    ];

    const output = [{ id: 3, url: "https://www.link3.dev" }];

    expect(filterByTerm(input, "link")).toEqual(output);
  });
});

Теперь запустите тест снова:

npm test

и увидеть, как он проходит!

 PASS  __tests__/filterByTerm.spec.js
  Filter function
    ✓ it should filter by a search term (link) (4ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.836s, estimated 1s

Отличная работа. Но закончили ли мы тестирование? Еще нет. Что нужно, так это чтобы наша функция перестала работать. Добавим тестирование с поисковым термином в верхнем регистре:

function filterByTerm(inputArr, searchTerm) {
  return inputArr.filter(function(arrayElement) {
    return arrayElement.url.match(searchTerm);
  });
}

describe("Filter function", () => {
  test("it should filter by a search term (link)", () => {
    const input = [
      { id: 1, url: "https://www.url1.dev" },
      { id: 2, url: "https://www.url2.dev" },
      { id: 3, url: "https://www.link3.dev" }
    ];

    const output = [{ id: 3, url: "https://www.link3.dev" }];

    expect(filterByTerm(input, "link")).toEqual(output);

    expect(filterByTerm(input, "LINK")).toEqual(output); // New test

  });
});

Запустите тест … и он снова не пройдет. Время исправить это снова!

Jest Tutorial: исправление теста в верхнем регистре

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

filterByTerm(inputArr, "link");
filterByTerm(inputArr, "LINK");

Для проверки этого условия мы ввели новый тест:

expect(filterByTerm(input, "LINK")).toEqual(output); // New test

Чтобы сделать это, мы можем настроить регулярное выражение для match:

//
    return arrayElement.url.match(searchTerm);
//

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

function filterByTerm(inputArr, searchTerm) {
  const regex = new RegExp(searchTerm, "i");
  return inputArr.filter(function(arrayElement) {
    return arrayElement.url.match(regex);
  });
}

И вот полный тест:

describe("Filter function", () => {
  test("it should filter by a search term (link)", () => {
    const input = [
      { id: 1, url: "https://www.url1.dev" },
      { id: 2, url: "https://www.url2.dev" },
      { id: 3, url: "https://www.link3.dev" }
    ];

    const output = [{ id: 3, url: "https://www.link3.dev" }];

    expect(filterByTerm(input, "link")).toEqual(output);

    expect(filterByTerm(input, "LINK")).toEqual(output);
  });
});

function filterByTerm(inputArr, searchTerm) {
  const regex = new RegExp(searchTerm, "i");
  return inputArr.filter(function(arrayElement) {
    return arrayElement.url.match(regex);
  });
}

Запустите его снова и убедитесь что оно проходит. Отличная работа! В качестве упражнения для вас напишите два новых теста и проверьте следующие условия:

  1. тест по запросу “uRl”
  2. проверить пустой поисковый запрос. Как функция должна справиться с этим?

Как бы вы структурировали эти новые тесты?

В следующем разделе мы увидим еще одну важную тему в тестировании: покрытие кода.

Jest Tutorial: покрытие кода

Что такое покрытие кода? Прежде чем говорить об этом, давайте сделаем небольшую корректировку нашего кода. Создайте в корневом каталоге вашего проекта новую папку с именем src и создайте файл с именем filterByTerm.js, в который мы поместим и экспортируем нашу функцию:

mkdir src && cd _$
touch filterByTerm.js

Вот содержимое файла filterByTerm.js:

function filterByTerm(inputArr, searchTerm) {
  if (!searchTerm) throw Error("searchTerm cannot be empty");
  const regex = new RegExp(searchTerm, "i");
  return inputArr.filter(function(arrayElement) {
    return arrayElement.url.match(regex);
  });
}

module.exports = filterByTerm;

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

function filterByTerm(inputArr, searchTerm) {
  if (!searchTerm) throw Error("searchTerm cannot be empty");
  if (!inputArr.length) throw Error("inputArr cannot be empty"); // new line
  const regex = new RegExp(searchTerm, "i");
  return inputArr.filter(function(arrayElement) {
    return arrayElement.url.match(regex);
  });
}

module.exports = filterByTerm;

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

Этот инструмент называется покрытием кода, и он является мощным инструментом в нашем наборе инструментов. Jest имеет встроенное покрытие кода, и вы можете активировать его двумя способами:

  1. через командную строку, передав флаг “–coverage
  2. настроив Jest в package.json

Перед запуском теста с покрытием обязательно импортируйте filterByTerm в tests/filterByTerm.spec.js:

const filterByTerm = require("../src/filterByTerm");
// ...

Сохраните файл и запустите тест с покрытием:

npm test -- --coverage

Вот что вы получаете:

 PASS  __tests__/filterByTerm.spec.js
  Filter function
    ✓ it should filter by a search term (link) (3ms)
    ✓ it should filter by a search term (uRl) (1ms)
    ✓ it should throw when searchTerm is empty string (2ms)

-----------------|----------|----------|----------|----------|-------------------|
File             |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
-----------------|----------|----------|----------|----------|-------------------|
All files        |     87.5 |       75 |      100 |      100 |                   |
 filterByTerm.js |     87.5 |       75 |      100 |      100 |                 3 |
-----------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total

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

Если вы хотите, чтобы покрытие кода всегда было активным, настройте Jest в package.json следующим образом:

  "scripts": {
    "test": "jest"
  },
  "jest": {
    "collectCoverage": true
  },

Вы также можете передать флаг тестовому скрипту:

  "scripts": {
    "test": "jest --coverage"
  },

Если вы хотите это визуализировать, есть также способ получить отчет в формате HTML для покрытия кода, просто настройте Jest следующим образом:

  "scripts": {
    "test": "jest"
  },
  "jest": {
    "collectCoverage": true,
    "coverageReporters": ["html"]
  },

Теперь каждый раз, когда вы запускаете тест npm, вы можете получить доступ к новой папке с именем coverage в папке вашего проекта: getting-started-with-jest/coverage/. Внутри этой папки вы найдете несколько файлов, где /coverage/index.html – это полная сводка HTML покрытия вашего кода:

Jest HTML code coverage report summary

Если вы нажмете на имя функции, вы также увидите точную непроверенную строку кода:

Jest HTML code coverage report single file

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

Jest Tutorial: как протестировать React?

React – очень популярная библиотека JavaScript для создания динамических пользовательских интерфейсов. Jest отлично работает для тестирования приложений React (Jest и React от инженеров Facebook). Jest также является test runner по умолчанию в Create React App.

Если вы хотите узнать, как тестировать компоненты React, ознакомьтесь с разделом Testing React Components: The Mostly Definitive Guide. Руководство охватывает компоненты модульного тестирования, компоненты класса, функциональные компоненты с хуками и новый Act API.

Заключение

Тестирование – это большая и увлекательная тема. Существует множество типов тестов и множество библиотек для тестирования. В этом руководстве Jest вы узнали, как настроить Jest для отчетов о покрытии, как организовать и написать простой модульный тест и как тестировать код JavaScript.

Чтобы узнать больше о UI тестировании, я настоятельно рекомендую взглянуть на JavaScript End to End Testing with Cypress.

Даже если это не связано с JavaScript, я также предлагаю прочитать Test-Driven Development with Python  от Гарри Персиваля. Он полон советов и подсказок по всем вопросам тестирования и подробно описывает все виды тестов.

Если вы готовы сделать скачок и узнать об автоматизированном тестировании и непрерывной интеграции, то Automated Testing and Continuous Integration in JavaScript для вас.

Вы можете найти код для этого урока на Github: getting-started-with-jest, а также решение для упражнений.

Спасибо за чтение и следите за обновлениями!

Была ли вам полезна эта статья?
[24 / 4.5]

Spread the love
Подписаться
Уведомление о
guest
2 Комментарий
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
alex
alex
3 лет назад

Спасибо, отличное объяснение для новичков

Анонимно
Анонимно
2 лет назад

было круто!