Покрытие кода показывает, какие части приложения были выполнены во время тестирования, а какие остались непроверенными. В экосистеме Vite и Vitest поддерживаются два основных механизма покрытия:
@vitest/coverage-v8@vitest/coverage-istanbulОба варианта интегрируются напрямую в тестовый раннер Vitest и позволяют получать отчёты о покрытии в различных форматах.
Покрытие используется для:
Vitest отделяет механизм запуска тестов от механизма анализа покрытия. Для этого используются отдельные провайдеры.
Основные провайдеры:
| Провайдер | Пакет | Основа |
|---|---|---|
| V8 | @vitest/coverage-v8 |
встроенный profiler движка V8 |
| Istanbul | @vitest/coverage-istanbul |
инструментирование кода |
Настройка производится через параметр
coverage.provider.
Пример:
export default defineConfig({
test: {
coverage: {
provider: 'v8'
}
}
})
или:
export default defineConfig({
test: {
coverage: {
provider: 'istanbul'
}
}
})
npm install -D vitest @vitest/coverage-v8
Для pnpm:
pnpm add -D vitest @vitest/coverage-v8
Для yarn:
yarn add -D vitest @vitest/coverage-v8
Файл vite.config.ts:
import { defineConfig } from 'vite'
export default defineConfig({
test: {
coverage: {
provider: 'v8',
reporter: ['text', 'html']
}
}
})
Через CLI:
vitest run --coverage
или:
npx vitest --coverage
После выполнения тестов появляется каталог:
coverage/
Внутри:
coverage/index.html
HTML-отчёт содержит:
@vitest/coverage-v8 использует встроенный profiler
JavaScript-движка V8.
Преимущества такого подхода:
В отличие от Istanbul, код не модифицируется перед выполнением.
V8-покрытие обычно быстрее Istanbul, особенно в крупных проектах.
Так как код не переписывается, сохраняются:
V8 особенно эффективен в проектах Vite благодаря нативной работе с ESM.
Несмотря на производительность, существуют ограничения:
npm install -D @vitest/coverage-istanbul
import { defineConfig } from 'vite'
export default defineConfig({
test: {
coverage: {
provider: 'istanbul',
reporter: ['text', 'html']
}
}
})
Istanbul работает иначе:
Пример концептуального инструментирования:
Исходный код:
function sum(a, b) {
return a + b
}
После инструментирования:
function sum(a, b) {
coverage.counter++
return a + b
}
Istanbul традиционно считается эталоном branch coverage.
Особенно хорошо отслеживаются:
Istanbul существует давно и поддерживается многими инструментами:
Поддерживаются:
Инструментирование создаёт дополнительную нагрузку.
В крупных проектах разница может быть существенной.
Изменение исходного кода иногда приводит к:
| Характеристика | V8 | Istanbul |
|---|---|---|
| Скорость | высокая | ниже |
| Инструментирование | нет | да |
| Точность branch coverage | хорошая | очень высокая |
| Потребление памяти | ниже | выше |
| Работа с sourcemaps | проще | сложнее |
| Подходит для больших проектов | отлично | зависит от размера |
| Совместимость с legacy-инструментами | средняя | высокая |
coverage: {
reporter: ['text']
}
Вывод в терминал:
File | % Stmts | % Branch | % Funcs | % Lines
coverage: {
reporter: ['html']
}
Создаёт визуальный интерфейс отчёта.
coverage: {
reporter: ['lcov']
}
Используется CI-сервисами:
coverage: {
reporter: [
'text',
'html',
'lcov'
]
}
coverage: {
include: ['src/**/*.ts']
}
coverage: {
exclude: [
'node_modules/',
'dist/',
'coverage/',
'**/*.d.ts'
]
}
coverage: {
exclude: [
'tests/',
'**/*.test.ts'
]
}
По умолчанию покрываются только файлы, загруженные во время тестирования.
Параметр:
all: true
заставляет анализировать все файлы проекта.
Пример:
coverage: {
all: true,
include: ['src/**/*.ts']
}
Без all: true файл без тестов может вообще не попасть в
отчёт.
С all: true такой файл покажет:
0% coverage
Это позволяет видеть реальные пробелы тестирования.
Vitest умеет падать при недостаточном покрытии.
Пример:
coverage: {
thresholds: {
lines: 80,
functions: 80,
branches: 70,
statements: 80
}
}
Если покрытие ниже порога:
Exit code 1
Это особенно важно для:
Branch coverage анализирует выполнение всех логических ветвей.
Пример:
function getRole(user) {
if (user.isAdmin) {
return 'admin'
}
return 'user'
}
Если протестирован только isAdmin = true, branch
coverage будет неполным.
import { describe, it, expect } from 'vitest'
function getRole(user) {
if (user.isAdmin) {
return 'admin'
}
return 'user'
}
describe('getRole', () => {
it('admin', () => {
expect(
getRole({ isAdmin: true })
).toBe('admin')
})
it('user', () => {
expect(
getRole({ isAdmin: false })
).toBe('user')
})
})
Для Istanbul:
/* istanbul ignore next */
if (import.meta.env.DEV) {
debug()
}
/* istanbul ignore file */
V8 использует совместимые pragma-комментарии:
/* v8 ignore next */
Vitest корректно работает с TypeScript через:
Некоторые конструкции TypeScript преобразуются в дополнительный JavaScript-код.
Например:
enum Status {
Active,
Disabled
}
После компиляции появляется дополнительная логика, которая тоже может участвовать в покрытии.
Для TypeScript-проектов обычно рекомендуется:
Vitest умеет анализировать:
.vue
файлы через Vite pipeline.
<script setup lang="ts">
const visible = true
</script>
<template>
<div v-if="visible">
Content
</div>
</template>
Условие v-if участвует в branch coverage.
Vitest корректно покрывает:
type Props = {
loading: boolean
}
export function Button(props: Props) {
if (props.loading) {
return <span>Loading</span>
}
return <button>Send</button>
}
Для полного покрытия нужны тесты обеих веток.
coverage: {
reportsDirectory: './custom-coverage'
}
coverage: {
clean: true
}
coverage: {
clean: false
}
Полезно при объединении нескольких запусков.
Каждый пакет может иметь:
packages/app
packages/ui
packages/core
с собственным coverage.
Можно объединять LCOV-файлы через внешние инструменты:
Пример:
- name: Run tests
run: npm run test -- --coverage
- name: Upload coverage
uses: codecov/codecov-action@v4
Используется LCOV:
coverage: {
reporter: ['lcov']
}
Файл:
coverage/lcov.info
coverage-v8 особенно подходит:
istanbul предпочтителен:
import { defineConfig } from 'vite'
export default defineConfig({
test: {
coverage: {
provider: 'v8',
all: true,
include: [
'src/**/*.ts',
'src/**/*.tsx'
],
exclude: [
'node_modules/',
'dist/',
'coverage/',
'**/*.test.ts',
'**/*.spec.ts'
],
reporter: [
'text',
'html',
'lcov'
],
thresholds: {
lines: 80,
functions: 80,
branches: 70,
statements: 80
}
}
}
})
import { defineConfig } from 'vite'
export default defineConfig({
test: {
coverage: {
provider: 'istanbul',
all: true,
reporter: [
'text',
'html',
'lcov'
],
thresholds: {
lines: 85,
branches: 80
}
}
}
})
Причины:
all: true;Причины:
Причины:
Подходит для:
Подходит для:
Для большинства современных Vite-проектов оптимальным вариантом считается:
Vitest + @vitest/coverage-v8
Причины: