Раздел 29 · Тестирование

Стек тестов: раннер, ассерты и дубли зависимостей

middle~50 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

Стек тестов: раннер, ассерты и дубли зависимостей

Сцена · три цеха одной фабрики

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

В прошлом блоке мы думали про тесты головой. Теперь ставим инструмент. Хорошая новость: Vitest объединяет все три цеха в одном пакете, и для эффектного кода поверх него ложится тонкая надстройка @effect/vitest.

Карта урока · что заберёшь

  • разложишь тестовый стек на три части: раннер, ассерты, дубли;
  • поймёшь, почему в проекте стоит Vitest, а не сборка из трёх пакетов;
  • разложишь дубли по полкам: dummy, stub, spy, mock, fake;
  • впервые проверишь тегированную ошибку как значение, через Effect.result, а не через toThrow.

Раздел 1 · из чего собран стек

Идея словами

Исторически это были три отдельных пакета: один раннер, к нему библиотека ассертов, к ним мок-фреймворк. Их приходилось склеивать и согласовывать версии. Vitest собирает всё в одном месте: запуск, expect, vi.fn, coverage, watch. Он понимает TypeScript и ESM из коробки и берёт конфиг от Vite, поэтому тесты видят проект так же, как его видит сборка.

Шаг 1 · самый простой тест

Чистый домен Kassa проверяется обычным Vitest без всякой обвязки. Один вход, один выход.

import { describe, expect, it } from 'vitest';

import { validatePayment } from '../src/validate-payment.ts';
import { makeAccount, makePayment } from './support/factories.ts';

describe('validatePayment', () => {
  it('пропускает платёж в пределах остатка', () => {
    const reasons = validatePayment(makeAccount({ balance: 100 }), makePayment(30));
    expect(reasons).toEqual([]);
  });
});

describe группирует, it это один случай, expect(...).toEqual(...) это работа цеха ассертов. Запусти pnpm test в пакете домена кассы и увидишь зелёную строку.

Что взять с собой

  • тестовый стек это раннер, ассерты, дубли;
  • Vitest объединяет всё и понимает TS и ESM без настройки;
  • чистую функцию проверяют простым it без обвязки.

Цех ассертов легко принять за магию, пока не соберёшь его сам. Ниже редактор: напиши свой expect на три матчера, с понятными сообщениями о провале и рабочим .not. Тесты гоняются прямо в браузере, ставить ничего не нужно.

Раздел 2 · карта дублей

Идея словами

Когда функция зависит от соседа (часов, базы, чужого API), в тесте соседа заменяют дублем. Дубли различаются не названием ради названия, а тем, что они умеют: что отдают, помнят ли вызовы, проверяют ли вызов сами, есть ли в них рабочая логика.

Картинка · переключи дубль

Переключай вид дубля и смотри на таблицу свойств. Заметь главное: чем правее в ряду dummy, stub, spy, mock, fake, тем больше дубль знает про взаимодействие. Mock самый требовательный: он сам валит тест, если его позвали не так. Fake самый честный: это рабочая мини-реализация.

Шаг 1 · stub часов через vi.fn

Самый частый дубль это stub: заранее заданный ответ. Подменим часы, чтобы тест не зависел от реального времени.

import { expect, it, vi } from 'vitest';

it('ставит время операции из часов', () => {
  const clock = { now: vi.fn(() => 1_700_000_000_000) };

  const op = recordOperation(clock, makePayment(30));

  expect(op.at).toBe(1_700_000_000_000);
});

vi.fn создаёт функцию-заглушку с заданным возвратом. Тест больше не зависит от того, который сейчас час.

Когда код пишет агент

Агент любит замокать всё подряд: и часы, и репозиторий, и саму проверяемую логику. Получается тест, который проверяет моки, а не код. Правило простое: подменяй только то, что мешает (время, сеть, БД), а домен оставляй настоящим. К избыточному мокированию вернёмся в блоке про unit-тесты отдельным уроком.

Что взять с собой

  • дубли различаются по умениям, а не по названию;
  • stub отдаёт готовый ответ, mock ещё и проверяет вызов, fake это рабочая мини-реализация;
  • подменяй границы, домен оставляй настоящим.

дальше после входа

Урок целиком открыт всем, кто вошёл. Войди по своему email, чтобы читать дальше.

Вход пока по приглашению. Если хочешь учиться, напиши Vassiliy.