Покрытие валидационной логики в Vest определяется степенью, в которой тесты или сценарии проверяют все возможные ветвления правил, условия выполнения валидаторов, комбинации полей и поведение системы при различных входных данных. В отличие от классического покрытия кода, здесь акцент смещается на семантику правил: важно не только исполнение строки кода, но и то, какие логические состояния валидации были активированы.
Валидационная система Vest строится на декларативных блоках, где каждая проверка может включать:
equals, isEmpty,
matches)when, skip)group)Каждый из этих элементов создаёт отдельные точки ветвления, которые требуют отдельного покрытия.
Простейший пример:
import { test, enforce } from 'vest';
const suite = (data) => {
test('username', 'required', () => {
enforce(data.username).isNotEmpty();
});
test('username', 'min_length', () => {
enforce(data.username).longerThanOrEquals(3);
});
};
Минимальное покрытие здесь включает:
Отсутствие любого из этих сценариев создаёт неполное логическое покрытие.
Условные проверки создают экспоненциальный рост комбинаций, которые необходимо учитывать.
import { test, when, enforce } from 'vest';
const suite = (data) => {
when(data.role === 'admin', () => {
test('accessCode', 'required_for_admin', () => {
enforce(data.accessCode).isNotEmpty();
});
});
};
Здесь возникает минимум два состояния:
role === 'admin'role !== 'admin'Но для покрытия логики важно учитывать также:
Даже если последний вариант не вызывает проверок, он важен для
покрытия ветки when.
Группы в Vest используются для логического объединения правил и могут влиять на поведение ошибок.
import { group, test, enforce } from 'vest';
group('auth', () => {
test('email', 'invalid_email', () => {
enforce(data.email).matches(/.+@.+/);
});
test('password', 'weak_password', () => {
enforce(data.password).longerThan(8);
});
});
Для полного покрытия необходимо учитывать:
Дополнительно важно учитывать агрегированное состояние группы:
Асинхронные проверки добавляют временную и стохастическую составляющую. Основная проблема покрытия заключается не в количестве веток, а в состоянии результата промиса.
test('email', 'email_taken', async () => {
await enforce(data.email).isNotTaken();
});
Минимальный набор сценариев:
Важно различать логическое покрытие и покрытие состояний выполнения:
Недостаточное покрытие асинхронных сценариев часто приводит к ложноположительным результатам тестов.
Динамическое отключение правил через skip или условные
конструкции изменяет структуру дерева валидации.
test('phone', 'invalid', () => {
if (data.country !== 'KZ') {
return;
}
enforce(data.phone).matches(/^\d+$/);
});
Здесь покрытие должно включать:
Особое внимание требуется к тому, что отсутствие ошибки не всегда означает корректное прохождение логики — иногда это следствие пропуска.
Связанные поля создают мультипликативное увеличение количества сценариев.
test('password_confirm', 'mismatch', () => {
enforce(data.passwordConfirm).equals(data.password);
});
Для покрытия необходимо учитывать:
При наличии нескольких зависимых полей (например, email + confirm email + recovery email) пространство состояний становится комбинаторным.
Классические метрики (line coverage, branch coverage) недостаточны для Vest-подобных систем. Более релевантные метрики:
Branch coverage validation rules
whenenforceState coverage
Rule activation coverage
test была активирована хотя бы один
разError-path coverage
Типовые пропуски в тестировании валидации:
undefined вместо строкиnull значения" "enforce(data.username).isNotEmpty();
Данный вызов требует покрытия:
''' 'nullundefined'a'Каждый из этих случаев может вести к разному поведению в зависимости от реализации enforce-логики.
В сложных схемах важно фиксировать неизменные свойства:
Нарушение этих инвариантов обычно указывает на пробелы в покрытии, а не в логике.
Практически полное покрытие достигается через разбиение логики на уровни:
Каждый уровень добавляет собственное пространство состояний, и игнорирование любого из них приводит к неполному покрытию даже при формально высоком проценте тестов.