Тестирование и эксплуатация
Урок о двух опорах надёжной эксплуатации: автотестах, которые ловят регрессии до прода, и менеджере процессов, который держит сервис живым.
Менеджер процессов — это программа, которая запускает ваш сервис, перезапускает его при падении и следит за его здоровьем, освобождая вас от ручного
node server.jsв терминале.
Код без тестов страшно менять: любая правка может тихо что-то сломать. А сервис без менеджера процессов умирает при первом сбое и не поднимается после перезагрузки сервера. Этот урок закрывает обе темы: сначала — что и чем тестировать, затем — как стабильно крутить приложение в проде через pm2 или systemd, масштабировать по ядрам и следить за его состоянием.
Зачем это нужно на практике
Тесты — это страховка скорости: с ними вы рефакторите и выкатываете уверенно, потому что регрессию поймает CI, а не пользователь. Менеджер процессов — страховка доступности: процесс упал в 3 ночи — он сам поднялся, сервер перезагрузился — сервис стартовал автоматически. Вместе они превращают «работает на моей машине» в «работает в проде без няньки».
node:test и Jest
В современном Node есть встроенный тест-раннер node:test — без установки зависимостей. Этого достаточно для большинства проектов.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { sum } from './math.js';
test('sum складывает два числа', () => {
assert.equal(sum(2, 3), 5);
});
test('sum бросает на нечисловой ввод', () => {
assert.throws(() => sum(2, 'x'));
});
Запуск — node --test. Альтернатива — Jest: богатая экосистема, моки, снапшоты, удобен для больших проектов и фронтенда. Выбор между ними — вопрос масштаба и привычек команды; принципы тестирования одинаковы.
# встроенный раннер: найдёт и прогонит *.test.js
node --test
# Jest
npx jest
Что тестировать
Тестируют не «всё подряд», а то, что важно и ломается. Полезно держать в голове «пирамиду тестов»:
- Unit-тесты (много). Чистая логика: расчёты, парсинг, валидация. Быстрые, без сети и БД.
- Интеграционные (меньше). Связки: маршрут + БД, сервис + очередь. Проверяют, что части стыкуются.
- E2E (мало). Сценарий целиком через HTTP. Дорогие и медленные, поэтому их немного.
Фокус — на бизнес-правилах и граничных случаях: пустой ввод, отрицательные числа, ошибка БД, повторный запрос. Не тратьте силы на тесты тривиальных геттеров — цельтесь в код, где есть логика и риск ошибки.
// интеграционный тест маршрута через supertest
import request from 'supertest';
import { app } from './app.js';
test('POST /users отклоняет невалидный email', async () => {
const res = await request(app)
.post('/users')
.send({ email: 'not-an-email', age: 30 });
assert.equal(res.status, 400);
});
Менеджер процессов: pm2 и systemd
Запускать прод-сервис как node server.js в терминале нельзя: закрылась сессия — упал сервис. Нужен менеджер. pm2 — популярный выбор для Node: рестарт при падении, автозапуск, логи, кластер.
# запустить под именем
pm2 start server.js --name api
# список процессов и их состояние
pm2 list
# логи и статус
pm2 logs api
# сохранить набор процессов и включить автозапуск при загрузке ОС
pm2 save
pm2 startup
Альтернатива без лишних зависимостей — systemd, штатный init в Linux. Он тоже умеет рестарт и автозапуск, а заодно — изоляцию и лимиты ресурсов.
[Unit]
Description=Node API
After=network.target
[Service]
User=node
WorkingDirectory=/app
ExecStart=/usr/bin/node server.js
Restart=on-failure
Environment=NODE_ENV=production
Environment=PORT=3000
[Install]
WantedBy=multi-user.target
Здесь Restart=on-failure поднимает сервис после падения, User=node — запуск без root (см. урок о безопасности), а WantedBy=multi-user.target включает старт при загрузке системы.
Рестарт и кластеризация
Один Node-процесс использует одно ядро CPU. На многоядерном сервере это расточительно. Решение — кластеризация: запустить несколько одинаковых процессов (по числу ядер) за общим портом; ОС/менеджер распределяет соединения между ними. Это и масштабирование, и отказоустойчивость — упал один воркер, остальные держат нагрузку.
# pm2: запустить по процессу на каждое ядро
pm2 start server.js -i max --name api
# плавный перезапуск без простоя при деплое
pm2 reload api
Команда pm2 reload важна для деплоя без даунтайма: pm2 поднимает новые воркеры и гасит старые по одному, поэтому сервис не «мигает». Чтобы это работало чисто, приложение должно корректно обрабатывать SIGTERM (graceful shutdown из первого урока) — иначе при перезапуске вы оборвёте текущие запросы.
Мониторинг здоровья
Менеджер перезапускает упавший процесс, но «процесс жив» ≠ «сервис здоров»: он может висеть, не отвечая. Поэтому добавляют health-эндпоинт — лёгкий маршрут, который проверяет ключевые зависимости и отвечает быстро.
app.get('/healthz', async (req, res) => {
try {
await db.query('SELECT 1'); // БД доступна?
res.status(200).json({ status: 'ok' });
} catch (err) {
res.status(503).json({ status: 'unhealthy' });
}
});
Этот маршрут опрашивает балансировщик или оркестратор: ответил 200 — шлём трафик, ответил 503 или молчит — выводим из ротации. Дополняйте картину метриками (latency, частота ошибок, память) и алёртами, чтобы узнавать о проблеме раньше пользователей. pm2 показывает базовое здоровье через pm2 monit.
Как это работает под капотом
Кластеризация в Node опирается на модуль cluster: главный процесс открывает слушающий сокет и через механизм ОС передаёт файловый дескриптор воркерам, поэтому несколько процессов делят один порт, а ядро раздаёт входящие соединения между ними. pm2 в режиме -i — это обёртка над тем же механизмом. Менеджер процессов отслеживает PID дочернего процесса: когда тот завершается с ненулевым кодом, ОС уведомляет родителя (через сигнал SIGCHLD), и менеджер по своей политике (Restart=on-failure у systemd) поднимает процесс заново. Автозапуск при загрузке держится на том, что и pm2 (pm2 startup), и systemd регистрируют сервис как unit, который init-система стартует на нужном этапе загрузки. Health-эндпоинт — обычный HTTP-маршрут; «магия» лишь в том, что внешний опрашиватель трактует код ответа как сигнал «годен/негоден».
Частые ошибки
- Прод как
node server.jsв терминале. Закрылась сессия или упал процесс — сервиса нет, автозапуска нет. - Тесты «для галочки». Покрыты тривиальные геттеры, а бизнес-логика и граничные случаи — нет.
- Кластер без graceful shutdown.
pm2 reloadпри деплое обрывает текущие запросы, если не обработанSIGTERM. - Health-чек, который всегда отвечает
200. Не проверяет зависимости — «здоров» даже когда БД лежит. - Сервис под root в unit-файле. Забыли
User=— нарушили least privilege. - Нет метрик и алёртов. О проблеме узнаёте от пользователей, а не из мониторинга.
Итоги
- Тестируйте через
node:testили Jest; стройте пирамиду: много unit, меньше интеграционных, мало E2E. - Цельтесь в бизнес-логику и граничные случаи, не в тривиальный код.
- В проде держите сервис под менеджером процессов (pm2 или systemd) с рестартом и автозапуском.
- Кластеризуйте по числу ядер; для деплоя без простоя используйте
pm2 reloadвместе с graceful shutdown. - Добавьте health-эндпоинт, проверяющий зависимости, и дополните его метриками и алёртами.