Что такое модульное (Unit) тестирование?

Ответ

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

В контексте Node.js это особенно важно из-за его асинхронной природы. Для тестирования используются специальные библиотеки и подходы:

  • Изоляция зависимостей: Внешние сервисы (базы данных, API) заменяются моками (mocks) или стабами (stubs) с помощью библиотек вроде sinon или встроенных возможностей Jest.
  • Асинхронный код: Тесты должны корректно обрабатывать промисы и колбэки. Jest и Mocha предоставляют для этого специальный синтаксис (async/await, done-колбэк).
  • Интеграция в процесс: Unit-тесты обычно выполняются быстро и являются частью пайплайна CI/CD.

Пример модульного теста для Node.js-функции с использованием Jest:

// utils/calculate.js
function calculateDiscount(price, discountPercent) {
    if (price <= 0 || discountPercent < 0) {
        throw new Error('Invalid input parameters');
    }
    return price * (1 - discountPercent / 100);
}

module.exports = { calculateDiscount };
// utils/calculate.test.js
const { calculateDiscount } = require('./calculate');

describe('calculateDiscount', () => {
    test('should apply correct discount', () => {
        expect(calculateDiscount(100, 20)).toBe(80);
    });

    test('should throw error for negative price', () => {
        expect(() => calculateDiscount(-10, 5)).toThrow('Invalid input parameters');
    });

    test('should handle zero discount', async () => {
        // Пример с асинхронным тестом, если бы функция была async
        const result = calculateDiscount(100, 0);
        expect(result).toBe(100);
    });
});

Популярные инструменты в экосистеме Node.js: Jest (чаще всего, "из коробки" имеет всё необходимое), Mocha в связке с Chai (для assertions) и Sinon (для моков). Модульные тесты — это фундамент надёжности приложения, они позволяют безопасно рефакторить код и ловить регрессии на ранней стадии.

Ответ 18+ 🔞

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

В мире Node.js это вообще святое дело, потому что тут всё асинхронное, всё летит в разнобой. Если не проверить каждую функцию в изоляции, потом можно искать баг неделями, а он окажется в какой-то мелкой хуйне, которая сломалась из-за промиса.

Как это делается на практике? Всё просто, но с подвохами:

  • Изоляция зависимостей: Твоя функция может хотеть сходить в базу данных или дернуть чужой API. Нахуй это в тестах? Мы подсовываем ей подделку — мок или стаб. Библиотека sinon для этого — лучший друг. Или сам Jest умеет.
  • Асинхронный код: Главная головная боль. Твой тест должен дождаться, пока промис выполнится, а не свалиться с ошибкой, потому что он уже закончился, а результат ещё не пришёл. В Jest и Mocha для этого есть async/await или колбэк done. Выглядит страшно, но привыкаешь быстро.
  • Интеграция в процесс: Эти тесты должны быть быстрыми и запускаться автоматически, например, когда ты пушишь код в репозиторий. Чтобы если ты что-то сломал, тебе сразу прилетело письмо счастья, а не через месяц на проде.

Вот смотри, живой пример. Допустим, у нас есть функция, которая считает скидку.

Файл с логикой (utils/calculate.js):

function calculateDiscount(price, discountPercent) {
    if (price <= 0 || discountPercent < 0) {
        throw new Error('Invalid input parameters');
    }
    return price * (1 - discountPercent / 100);
}

module.exports = { calculateDiscount };

А теперь файл с тестами к ней (utils/calculate.test.js). Смотри, как всё просто и по полочкам:

const { calculateDiscount } = require('./calculate');

describe('calculateDiscount', () => {
    test('should apply correct discount', () => {
        expect(calculateDiscount(100, 20)).toBe(80); // От ста рублей 20% — должно быть 80. Элементарно.
    });

    test('should throw error for negative price', () => {
        expect(() => calculateDiscount(-10, 5)).toThrow('Invalid input parameters'); // Проверяем, что на хуйню функция ругается, а не молча считает ерунду.
    });

    test('should handle zero discount', async () => { // Примерно так же тестируют асинхронные штуки
        const result = calculateDiscount(100, 0);
        expect(result).toBe(100); // Нулевая скидка — цена та же.
    });
});

Из инструментов народ в основном юзает: либо Jest — он вообще красавчик, из коробки всё есть, либо связку Mocha + Chai + Sinon. Это как выбрать отвёртку: какая удобнее, той и работаешь.

Смысл всей этой движухи в чём? Доверия ебать ноль к самому себе и к коллегам. Ты написал функцию, она работает. Через полгода ты её меняешь или кто-то другой лезет в код. Как убедиться, что ты ничего не сломал? Запускаешь тесты. Если они зелёные — спи спокойно. Если красные — сразу видно, где и что пошло не так. Это не панацея, но без этого сейчас — вообще пидарас шерстяной, а не разработчик.