Тесты в CI: кэш, шардинг, coverage gate и удобство работы
Сцена · конвейер с воротами
Каждый коммит едет по ленте через ворота. Первые ворота: линтер. Дальше: типы. Дальше: тесты. Последние: порог покрытия. Красный на любых воротах останавливает ленту, и код не доезжает до ветки, в которую целился. Локально ты можешь забыть прогнать проверку, CI не забывает никогда. Это и есть его смысл: объективная приёмка, одинаковая для всех.
В этом уроке соберём пайплайн для Kassa и заодно наладим удобство локальной работы, чтобы CI не был единственным местом, где ты видишь результат тестов.
Карта урока · что заберёшь
- разложишь CI-пайплайн на шаги и поймёшь роль кэша и шардинга;
- научишься ставить coverage gate как порог в PR;
- поймёшь, почему порог это не 100 процентов;
- наладишь watch mode, фокус на одном тесте и дебаг в IDE.
Раздел 1 · пайплайн по шагам
Идея словами
CI это последовательность шагов, где каждый следующий имеет смысл только при зелёном предыдущем. Сначала дешёвое и быстрое (линт, типы), потом дорогое (тесты). Так обратная связь приходит раньше: незачем гонять весь набор, если код даже не типизируется.
Два приёма ускоряют ленту. Кэш зависимостей убирает повторную установку. Шардинг разбивает набор на части и раскидывает по машинам.
Шаг 1 · минимальный пайплайн
# .github/workflows/ci.yml
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm lint
- run: pnpm typecheck
- run: pnpm exec vitest run --coverage
- uses: actions/upload-artifact@v4
with:
name: coverage
path: coverage/
cache: pnpm хранит зависимости между прогонами. Порядок шагов не случаен: линт и типы дёшевы и стоят первыми, тесты последними. Отчёт о покрытии уезжает артефактом, чтобы его можно было открыть из PR.
Шаг 2 · шардинг на несколько машин
# машина 1 из 2
vitest run --shard=1/2
# машина 2 из 2
vitest run --shard=2/2
Каждая машина берёт свою половину набора. На большом наборе это превращает десять минут в пять. Шардинг работает с любыми тестами, в том числе эффектными: для CI тест на Effect это обычный Vitest-тест.
Что взять с собой
- дешёвые шаги (линт, типы) идут раньше дорогих (тесты);
- кэш зависимостей убирает повторную установку;
- шардинг режет время прогона почти в число машин.
Раздел 2 · coverage gate
Идея словами
Coverage gate превращает покрытие из информации в правило: ниже порога, и PR не вливается. Это защита от молчаливой деградации, когда новый код приезжает без тестов и общий процент тихо ползёт вниз.
Порог почти никогда не ставят на 100. Сотня заставляет покрывать тривиальный glue и геттеры, и команда начинает писать пустые тесты ради цифры. Разумный порог (часто около 80) держит планку, не превращая её в фетиш. И помни главную оговорку серии: высокий процент покрытия это ещё не честный набор. Что с этим делать, разберём в следующем уроке про mutation testing.
Шаг 1 · порог в конфиге
// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
coverage: {
provider: 'v8',
thresholds: { lines: 80, branches: 80, functions: 80 },
},
},
});
Теперь pnpm test --coverage сам валит сборку, если покрытие просело ниже порога. Правило живёт в репозитории, а не в голове ревьюера.
Что взять с собой
- coverage gate блокирует merge при просадке покрытия;
- порог обычно не 100, чтобы не плодить пустые тесты;
- высокое покрытие это ещё не честный набор.
Ворота это не магия конфига, а несколько строк кода между списком результатов и кодом возврата. Ниже редактор: собери репортер, который считает исходы, показывает самых медленных и решает, красная сборка или зелёная. Тесты гоняются прямо в браузере, ставить ничего не нужно.
дальше после входа
Урок целиком открыт всем, кто вошёл. Войди по своему email, чтобы читать дальше.
Вход пока по приглашению. Если хочешь учиться, напиши Vassiliy.