Тестирование в Node.js через Jest

Для тестирования кода на Node.js используется Jest — популярный фреймворк, обеспечивающий быстрый запуск тестов, встроенные функции мокирования и удобное покрытие кода. Установка производится через npm:

npm install --save-dev jest

В package.json рекомендуется добавить скрипт для запуска тестов:

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

После этого можно запускать тесты командой:

npm test

Для проектов с TypeScript часто добавляют ts-jest:

npm install --save-dev ts-jest @types/jest

и создают конфигурационный файл jest.config.js:

module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
};

Структура тестов

Тесты в Jest группируются в файлы с расширением .test.js или .spec.js. Основные функции для организации тестов:

  • describe() — группирует связанные тесты.
  • test() или it() — описывает отдельный тест.
  • beforeAll(), afterAll(), beforeEach(), afterEach() — хуки для подготовки и очистки окружения.

Пример структуры:

describe('Математические функции', () => {
  let value;

  beforeEach(() => {
    value = 10;
  });

  test('Сложение чисел', () => {
    expect(value + 5).toBe(15);
  });

  test('Вычитание чисел', () => {
    expect(value - 3).toBe(7);
  });
});

Асинхронные тесты

Node.js часто работает с асинхронным кодом. Jest поддерживает промисы и async/await. Для тестирования асинхронных функций можно использовать:

test('Асинхронная функция возвращает данные', async () => {
  const data = await fetchData();
  expect(data).toEqual({ success: true });
});

Для функций, возвращающих промис, также работает вариант с return:

test('Проверка промиса', () => {
  return fetchData().then(data => {
    expect(data.success).toBe(true);
  });
});

Мокирование зависимостей

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

jest.mock('./api', () => ({
  fetchData: jest.fn().mockResolvedValue({ success: true })
}));

test('Тест с мокированной функцией', async () => {
  const { fetchData } = require('./api');
  const result = await fetchData();
  expect(result.success).toBe(true);
});

Проверка исключений и ошибок

Для тестирования кода, который выбрасывает ошибки, используются специальные методы toThrow() и toThrowError():

function divide(a, b) {
  if (b === 0) throw new Error('Деление на ноль');
  return a / b;
}

test('Проверка деления на ноль', () => {
  expect(() => divide(10, 0)).toThrow('Деление на ноль');
});

Для асинхронных функций, которые выбрасывают ошибки, применяется rejects:

test('Асинхронная функция выбрасывает ошибку', async () => {
  await expect(asyncFunction()).rejects.toThrow('Ошибка выполнения');
});

Покрытие кода и отчёты

Jest позволяет генерировать отчёты о покрытии кода с помощью флага --coverage:

npx jest --coverage

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

Конфигурация среды

Node.js предоставляет возможность использовать различные окружения для тестов. В jest.config.js можно настроить:

module.exports = {
  testEnvironment: 'node', // или 'jsdom' для браузероподобной среды
  verbose: true,
  collectCoverage: true
};

Параметризованные тесты

Для проверки одного функционала с разными входными данными удобно использовать test.each():

describe('Проверка сложения чисел', () => {
  test.each([
    [1, 2, 3],
    [5, 5, 10],
    [10, -2, 8]
  ])('Сумма %i и %i равна %i', (a, b, expected) => {
    expect(a + b).toBe(expected);
  });
});

Взаимодействие с TensorFlow.js

При тестировании функций TensorFlow.js важно учитывать асинхронность операций и освобождение ресурсов. Например:

const tf = require('@tensorflow/tfjs-node');

test('Создание и умножение тензоров', () => {
  const a = tf.tensor([1, 2]);
  const b = tf.tensor([3, 4]);
  const c = a.mul(b);
  expect(c.arraySync()).toEqual([3, 8]);
  a.dispose();
  b.dispose();
  c.dispose();
});

Для тестов с моделью можно загружать предварительно сохранённые веса:

test('Прогнозирование модели', async () => {
  const model = await tf.loadLayersModel('file://./model/model.json');
  const input = tf.tensor2d([[1, 2, 3, 4]]);
  const output = model.predict(input);
  expect(output.shape).toEqual([1, 1]);
  input.dispose();
  output.dispose();
});

Организация больших тестовых наборов

Для крупных проектов рекомендуется разбивать тесты на отдельные файлы и папки по функциональным модулям. Jest автоматически находит файлы с расширениями .test.js и .spec.js. Можно также создавать конфигурацию roots:

module.exports = {
  roots: ['<rootDir>/tests/unit', '<rootDir>/tests/integration'],
};

Это позволяет поддерживать чистую структуру проекта и ускоряет поиск и запуск тестов.