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

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

В этой статье вы узнаете, как писать и запускать качественные юнит-тесты в Node.js с помощью популярного фреймворка для тестирования Jest.

Настройка Jest в Node.js

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

Запустите команду ниже, чтобы установить Jest:

npm install --save-dev jest

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

mkdir test

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

Далее вы создадите тестовый файл. Тестовые файлы — это файлы, в которые вы записываете свои тесты. Jest считает файлы с расширениями .test.js и .spec.js тестовыми файлами.

Вы можете создать тестовый файл, выполнив следующую команду:

touch example.test.js

Наконец, добавьте приведенную ниже команду в свои сценарии в файле package.json:

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

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

Создание тестов с помощью Jest

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

Например, рассмотрим функцию ниже:

//index.js
function isEven(number) {
  if (number < 0) throw new Error("Number must be positive");
  if (typeof number !== "number") throw new Error("Number must be a number");
  return number % 2 === 0;
}
module.exports = isEven;

Приведенная выше функция принимает число в качестве аргумента и возвращает true, если число четное, и false, если нет.

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

Набор тестов — это набор тестовых случаев, сгруппированных вместе для тестирования определенной функциональности или функции. В этом случае ваш набор тестов будет проверять функциональность функции isEven.

Вещи, которые вам нужно проверить в функции isEven, включают, если:

  • Возвращает true, если число четное.
  • Возвращает false, если число нечетное.
  • Выдает ошибку, если число отрицательное.
  • Выдает ошибку, если аргумент не является числом.

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

//example.test.js
const isEven = require("../index");
describe("isEven", () => {
  test("returns true if number is even", () => {
    expect(isEven(2)).toBe(true);
  });
  test("returns false if number is odd", () => {
    expect(isEven(3)).toBe(false);
  });
  test("throws an error if number is negative", () => {
    expect(() => isEven(-1)).toThrow();
  });
  test("throws an error if number is not a number", () => {
    expect(() => isEven("1")).toThrow();
  });
});

В Jest функция describe используется для группировки связанных тестовых случаев. В данном случае тестовые случаи связаны с функцией isEven, поэтому мы используем "isEven" в качестве описания. Функция принимает обратный вызов, содержащий все отдельные тесты, связанные с тестируемой функцией. В данном случае отдельные тесты для функции isEven.

Функция test определяет один тестовый пример, аналогичный функции describe, но для одного случая. В первых двух тестовых случаях тест подтверждает, возвращает ли функция соответствующее логическое значение, передавая четное и нечетное число соответственно. Он подтверждает возвращаемые значения с помощью Сопоставителя шуток toBe.

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

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

npm test

Выполнение команды должно привести к выводу, показанному на изображении ниже, так как все тесты должны быть пройдены.

Тестирование асинхронного кода

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

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

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

// fetchdata.js
function fetchData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(JSON.stringify({
        name: "Jon Snow",
        age: 30,
        email: "[email protected]",
      }));
    }, 1000);
  });
}

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

Вот тест для функции fetchData, который не учитывает ее асинхронный характер:

// fetchdata.test.js
test("fetchData returns the correct data", () => {
  const data = fetchData();
  expect(data).toEqual(
    JSON.stringify({
      name: "Jon Snow",
      age: 30,
      email: "[email protected]",
    })
  );
});

Хотя тест выглядит так, как будто он должен пройти, если вы запустите приведенный выше тест, он завершится ошибкой, как показано на изображении ниже.

Тест провалился, потому что Jest завершил тестирование до того, как функция fetchData смогла вернуть свое значение.

В Jest есть несколько способов тестирования асинхронного кода, включая использование промисов, async/await, обратных вызовов и сопоставителя Jest resolves/rejects.

Используя сопоставитель Jest resolves, вы можете переписать тест следующим образом:

test("fetchData returns the correct data", () => {
    return expect(fetchData()).resolves.toBe(
      JSON.stringify({
        name: "Jon Snow",
        age: 30,
        email: "[email protected]",
      })
    );
  });

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

Имитация зависимостей с помощью Jest

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

Jest предоставляет встроенные возможности имитации, которые упрощают создание и использование фиктивных функций и модулей.

Например, предположим, что у вас есть модуль с именем user.js, который зависит от модуля с именем database.js для операций с базой данных.

Вот как вы можете смоделировать модуль database.js, чтобы протестировать модуль user.js без фактического доступа к базе данных:

// user.test.js
const user = require("./user");
const database = require("./database");
jest.mock("./database");
describe("getUser", () => {
  test("should return the user with the given userId", () => {
    const userId = "123";
    const userObj = { id: userId, name: "John" };
    database.getUser.mockReturnValue(userObj);
    const result = user.getUser(userId);
    expect(result).toBe(userObj);
  });
});

Здесь мы моделируем модуль database.js, используя встроенную в Jest функцию jest.mock(). Эта функция заменяет фактический модуль фиктивной версией, которую вы можете настроить так, чтобы она возвращала определенные значения или вел себя определенным образом.

В тестовом примере getUser мы используем метод mockReturnValue фиктивного модуля database.js, чтобы указать, что когда функция getUser вызывается с определенным идентификатором пользователя, она должна возвращать определенный userObj. Затем мы вызываем функцию getUser модуля user.js с тем же userId и ожидаем, что она вернет тот же userObj.

Смоделировав модуль database.js, вы можете изолировать и протестировать функцию getUser модуля user.js без фактического доступа к базе данных.

Сравнение паттернов TDD и BDD в Jest

Разработка через тестирование (TDD) — методология разработки программного обеспечения, предполагающая написание тестов до написания кода.

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

С другой стороны, Behavior-Driven Development (BDD) — это методология разработки программного обеспечения, которая фокусируется на описании поведения программного обеспечения простым языком, понятным всем членам команды.

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

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

TDD с шуткой

Давайте рассмотрим сценарий, который объясняет TDD в Jest, используя функцию, которая возвращает произведение двух чисел.

Начнем с написания теста для функции, которая возвращает произведение двух чисел.

// multiply.test.js
const multiply = require("./multiply");
describe('multiply function', () => {
  it('should return the product of two numbers', () => {
    expect(multiply(2, 3)).toEqual(6);
    expect(multiply(0, 5)).toEqual(0);
    expect(multiply(-2, 3)).toEqual(-6);
    expect(multiply(-2, -3)).toEqual(6);
  });
});

Затем запустите тест и убедитесь, что он не работает. Выполнение приведенного выше теста завершится ошибкой, поскольку multiply еще не существует.

Затем реализуйте функцию на основе результатов, полученных из ваших неудачных тестов, например:

function multiply(a, b) {
  return a * b;
}

Перезапустите тест и посмотрите, пройдет ли он. Теперь, когда вы написали функцию multiply, тесты должны пройти.

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

BDD с шуткой

Давайте рассмотрим сценарий, объясняющий BDD в Jest с помощью функции, возвращающей произведение двух чисел.

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

После того, как вы описали поведение функции, вы создадите тестовый файл Jest и реализуете тесты для этого сценария, следуя описанному поведению, например так:

// multiply_bdd.test.js
describe('Multiplication', () => {
  test('Multiply two numbers', () => {
    // Given
    const a = 5;
    const b = 10;
    // When
    const result = multiply(a, b);
    // Then
    expect(result).toEqual(50);
  });
});
function multiply(a, b) {
  return a * b;
}

Следование подходу BDD гарантирует, что ваши тесты точно соответствуют желаемому поведению вашего кода.

Джест против Мокко

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

Jest и Mocha — популярные фреймворки для тестирования JavaScript, которые вы можете использовать для написания модульных тестов. Вот таблица, показывающая некоторые различия между ними.

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

Заключение

В этой статье вы узнали, как настроить Jest в Node.js, создать и запустить наборы тестов, а также имитировать зависимости для ваших тестов с помощью среды тестирования Jest. Кроме того, вы изучили шаблоны тестирования BDD и TDD и способы их реализации в Jest. Наконец, чтобы предоставить вам широкий выбор сред тестирования, мы сравнили Jest с Mocha, еще одной популярной средой тестирования в экосистеме Node.js.

Вы можете узнать больше о Jest в официальной документации Jest.

Узнайте больше о тестировании:

Первоначально опубликовано на https://semaphoreci.com 27 апреля 2023 г.