Проверка покрытия валидационных правил

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

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

Структура валидационной схемы и точки покрытия

Схемы Yup обычно состоят из комбинации базовых и составных правил:

  • проверка типов (string, number, boolean, array, object)
  • ограничения значений (min, max, length, matches)
  • обязательность (required)
  • трансформации (transform)
  • условная логика (when)
  • кастомные проверки (test)

Каждый из этих элементов формирует отдельный класс проверок, требующих индивидуального тестового покрытия. Например, схема:

const schema = Yup.object({
  username: Yup.string()
    .min(3)
    .max(20)
    .required(),

  age: Yup.number()
    .min(18)
    .max(65),

  email: Yup.string()
    .email()
    .required()
});

имеет как минимум следующие независимые ветви покрытия:

  • отсутствие username
  • длина username меньше минимума
  • превышение максимальной длины
  • отсутствие age (валидно, но важно проверить)
  • значение age ниже или выше диапазона
  • некорректный формат email
  • отсутствие email

Каждое правило должно рассматриваться как отдельная логическая ветка.

Ветвление и условные зависимости

Особое внимание требуется схемам с использованием when, где логика валидации зависит от других полей:

const schema = Yup.object({
  isCompany: Yup.boolean(),

  companyName: Yup.string().when('isCompany', {
    is: true,
    then: (schema) => schema.required(),
    otherwise: (schema) => schema.notRequired()
  })
});

Здесь покрытие включает минимум два состояния:

  • isCompany = true → поле обязательно
  • isCompany = false → поле не проверяется на обязательность

Отсутствие теста хотя бы одного из сценариев означает неполное покрытие логики схемы, даже если код формально выполняется.

Проверка граничных значений

В Yup значительное количество ошибок возникает на границах диапазонов. Поэтому покрытие должно включать не только валидные значения, но и предельные состояния:

  • значение ровно на минимуме (min)
  • значение на единицу меньше минимума
  • значение ровно на максимуме (max)
  • значение на единицу больше максимума

Пример для числового поля:

Yup.number().min(10).max(20)

Требуемые сценарии покрытия:

  • 9 (невалидно)
  • 10 (валидно)
  • 20 (валидно)
  • 21 (невалидно)

Аналогичный принцип применяется к строкам и массивам, где важны длина и количество элементов.

Покрытие кастомных проверок test()

Метод test() в Yup создаёт произвольные условия, часто содержащие бизнес-логику. Это один из наиболее критичных элементов с точки зрения покрытия.

Yup.string().test(
  'no-special-chars',
  'Недопустимые символы',
  (value) => /^[a-zA-Z0-9]+$/.test(value)
);

Минимальный набор покрываемых случаев:

  • строка соответствует шаблону
  • строка содержит запрещённый символ
  • пустое значение (если допускается)
  • undefined или null (если не требуется обязательность)

Каждый test() фактически формирует отдельную функцию, требующую полного ветвления условий.

Проверка обязательности и nullable

В Yup различаются состояния:

  • required()
  • notRequired()
  • nullable()

Комбинации этих модификаторов создают дополнительные ветви логики:

Yup.string().nullable().required()

Возможные сценарии:

  • null → невалидно
  • undefined → невалидно
  • пустая строка → зависит от конфигурации
  • валидное значение → проходит проверку

Недостаточное покрытие часто возникает, когда тестируются только валидные значения без проверки всех форм отсутствия данных.

Обработка преобразований transform()

Метод transform() изменяет входные данные перед валидацией, что добавляет скрытый слой логики:

Yup.number().transform((value, originalValue) =>
  originalValue === '' ? null : value
);

Покрытие должно учитывать:

  • исходное пустое значение
  • строковое представление числа
  • уже преобразованное значение
  • NaN и некорректные типы

Игнорирование трансформаций приводит к ложному ощущению корректности схемы, так как тестируется итоговое состояние, но не процесс преобразования.

Табличное представление покрытия правил

Для сложных схем используется структурирование покрытия:

Тип правила Сценарии проверки
required есть / отсутствует / null
min/max ниже / на границе / выше
string format корректный / некорректный
when true / false условия
test true / false / edge / null
transform исходные типы и преобразованные значения

Такое представление позволяет выявить непокрытые комбинации условий.

Комбинированные схемы и взрыв состояний

При увеличении числа полей возникает эффект комбинаторного роста возможных состояний. Например, схема из 5 полей с 3 состояниями на каждое уже даёт 243 возможные комбинации.

Yup не требует полного перебора всех комбинаций, но критически важные пересечения должны покрываться:

  • зависимые поля (when)
  • связанные ограничения (например, сумма значений)
  • взаимоисключающие поля

Типичные пробелы покрытия

На практике чаще всего отсутствует проверка следующих случаев:

  • undefined вместо null
  • пустые строки в числовых полях
  • частично заполненные объекты
  • некорректные типы (string вместо number)
  • условия when, проверенные только в одной ветке
  • кастомные test() без отрицательных сценариев

Инструментальная проверка схем Yup

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

schema.isValid(data)
schema.validate(data)

Разделение на позитивные и негативные сценарии позволяет явно фиксировать отсутствие веток.

Дополнительно применяется подход изоляции схем:

  • каждая схема тестируется отдельно
  • вложенные объекты проверяются рекурсивно
  • массивы тестируются по элементам и структуре

Рекурсивное покрытие вложенных объектов

Yup поддерживает вложенные структуры:

Yup.object({
  user: Yup.object({
    profile: Yup.object({
      age: Yup.number().required()
    })
  })
});

Покрытие должно учитывать:

  • отсутствие вложенного объекта
  • частичную заполненность
  • нарушение типов на любом уровне вложенности
  • корректные глубинные значения

Ошибки на глубине часто не выявляются при поверхностных тестах верхнего уровня.

Значение отрицательных сценариев

Покрытие валидационных правил считается неполным без отрицательных сценариев. В Yup это особенно важно, так как библиотека ориентирована на декларативное описание ограничений, где отсутствие проверки равносильно пропуску правила.

Отрицательные сценарии включают:

  • некорректный тип данных
  • выход за пределы диапазона
  • нарушение формата
  • несоответствие условной логике
  • отсутствие обязательных полей

Без них схема формально существует, но не выполняет защитную функцию.