Перейти к основному содержимому

Предикаты

Предикаты — это декларативный способ описания условий для проверки объектов, коллекций и данных в базе. Они позволяют формировать сложные проверки с помощью цепочек методов (fluent-интерфейс), что делает тесты более читаемыми и поддерживаемыми.

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

  1. В утверждениях для проверки коллекций
  2. В утверждениях для проверки записей базы
  3. Для получения данных базы
  4. Для указания условий при обучении Мокито

Предикаты расширяют и унифицируют функциональность тестового движка.

Механизм предикатов (ЮТест.Предикат):

  • позволяет формировать наборы условий (отборы) и передавать их в качества параметров;
  • построен по модели текучих выражений и имеет схожий с базовыми утверждениями синтаксис (ЮТест.ОжидаетЧто());
  • позволяет упростить и унифицировать многие механизмы движка, некоторые еще только в планах;
  • за счет этого, расширение функциональности предикатов автоматические расширяет функциональность многих механизмов движка.

Чтобы воспользоваться предикатами, вам нужно сначала создать их с помощью конструктора ЮТест.Предикат, а затем передать в метод.

Например, нам нужно проверить формирование записей в регистре.

Процедура АктуализацияУведомлений() Экспорт
    
    // Тест удостовериться в отсутствии нужных записей перед вызовом метода
    // Вызовет метод формирующий записи в регистре
    // Проверит наличие сформированных записей
    // А также проверит записи на соответствие требований

    ИмяРегистра = "РегистрСведений.ОповещенияПользователя";
    Объект = ТестовыеДанные.Объект();
    
    // Для этого мы формируем отбор поиска записей
    Отбор = ЮТест.Предикат()
        .Реквизит("Источник").Равно(Объект)
        .Реквизит("ТипОповещения").Равно(Справочники.ТипыОповещенийПользователя.Уведомление)
        .Получить();
    
    // По этому отбору проверим отсутствие нужных записей
    ЮТест.ОжидаетЧтоТаблицаБазы(ИмяРегистра)
        .НеСодержитЗаписи(Отбор);
    
    УведомленияВызовСервера.АктуализацияУведомлений();
    
    // А после вызова метода - присутствие
    ЮТест.ОжидаетЧтоТаблицаБазы(ИмяРегистра)
        .СодержитЗаписи(Отбор);
    
    // Также получим сами записи используя тот же отбор
    ДанныеУведомления = ЮТЗапросы.Запись(ИмяРегистра, Отбор);
    
    ЮТест.ОжидаетЧто(ДанныеУведомления)
        .Свойство("Прочитано").ЭтоЛожь()
        .Свойство("Пользователь").Равно(Справочники.ГруппыОповещенийПользователей.Инженер);
    
КонецПроцедуры

Возможности

  • Проверка вложенных свойств:
    • Реквизит - Устанавливает имя реквизита, который необходимо проверить. Все последующие проверки будут относится к нему.
    • Свойство - Это алиас (псевдоним) для Реквизит
  • Проверки
    • Равно - Добавляет предикат, проверяющий равенство объекта (свойства) указанному значению
    • НеРавно - Добавляет предикат, проверяющий не равенство объекта (свойства) указанному значению
    • Заполнено - Добавляет предикат, проверяющий заполненность объекта (свойства)
    • Пусто - Добавляет предикат, проверяющий, что объект (свойств) не заполнено
    • Больше - Добавляет предикат, проверяющий, что значение объекта (свойства) больше указанного
    • БольшеИлиРавно - Добавляет предикат, проверяющий, что значение объекта (свойства) больше или равно указанному
    • Меньше - Добавляет предикат, проверяющий, что значение объекта (свойства) меньше указанного
    • МеньшеИлиРавно - Добавляет предикат, проверяющий, что значение объекта (свойства) меньше или равно указанному
    • ИмеетТип - Добавляет предикат, проверяющий, что значение объекта (свойства) имеет указанный тип
    • ИмеетТипОтличныйОт - Добавляет предикат, проверяющий, что значение объекта (свойства) имеет тип отличный от указанного
    • ИмеетДлину - Добавляет предикат, проверяющий, длину/размер значение объекта (свойства) на равенство указанному значению
    • ИмеетДлинуОтличнуюОт - Добавляет предикат, проверяющий, длину/размер значение объекта (свойства) на не равенство указанному значению
    • ИмеетСвойство - Добавляет предикат, проверяющий, что значение объекта (реквизита) содержит вложенное свойство
    • НеИмеетСвойства - Добавляет предикат, проверяющий, что значение объекта (реквизита) не содержит вложенное свойство
    • Содержит - Добавляет предикат, проверяющий, что значение объекта (реквизита) содержит указанное значение
    • НеСодержит - Добавляет предикат, проверяющий, что значение объекта (реквизита) не содержит указанное значение
    • СодержитСтрокуПоШаблону - Добавляет предикат, проверяющий, что строка соответствует указанному регулярному выражению
    • НеСодержитСтрокуПоШаблону - Добавляет предикат, проверяющий, что строка не соответствует указанному регулярному выражению
    • ВСписке - Добавляет условие, что проверяемое значение (или значение его свойства) входит в список значений
    • Между
      • Между/МеждуВключаяГраницы- Добавляет условие, что проверяемое значение (или значение его свойства) входит в заданный интервал.
        Проверяемое значение может находится на границе интервала.
      • МеждуИсключаяГраницы- Добавляет условие, что проверяемое значение (или значение его свойства) входит в заданный интервал.
        Проверяемое значение не может находится на границе интервала.
      • МеждуВключаяНачалоГраницы- Добавляет условие, что проверяемое значение (или значение его свойства) входит в заданный интервал.
        Проверяемое значение может находится на начальной границе интервала.
      • МеждуВключаяОкончаниеГраницы- Добавляет условие, что проверяемое значение (или значение его свойства) входит в заданный интервал.
        Проверяемое значение может находится на конечной границе интервала.
  • Служебные
    • Получить - Возвращает набор сформированных утверждений.
      Рекомендуется использовать этот метод, если планируется отложенная проверка предикатов. Например, вы хотите сформировать два набору предикатов и проверять их в зависимости от условия.
      Метод копирует настроенный набор утверждений в массив и возвращает его, таким образом сохраняется состояние, которое можно передавать дальше.

Возможно создавать предикаты на основании структуры - ЮТест.Предикат(Структура), например:

// Вместо
Предикат = ЮТест.Предикат()
    .Свойство("Наименование").Равно(НаименованиеОбъекта)
    .Свойство("Код").Равно(КодОбъекта);

// Можно использовать структур
Условия = Новый Структура("Наименование, Код", НаименованиеОбъекта, КодОбъекта);
Предикат = ЮТест.Предикат(Условия);

Примеры использования

Проверка коллекции

// Проверяет, что в коллекции есть элементы с реквизитом `Число`, значение которого равно `2`
ЮТест.ОжидаетЧто(Коллекция)
    .ЛюбойЭлементСоответствуетПредикату(ЮТест.Предикат()
        .Реквизит("Число").Равно(2));

// Тоже самое, что и проверка выше
ЮТест.ОжидаетЧто(Коллекция)
    .Содержит(ЮТест.Предикат()
        .Реквизит("Число").Равно(2));

// Проверяет, что каждый элемент коллекции — заполненный массив
ЮТест.ОжидаетЧто(Коллекция)
    .КаждыйЭлементСоответствуетПредикату(ЮТест.Предикат()
        .Заполнено().ИмеетТип("Массив"));

// Проверят, что в коллекции нет элементов с реквизитом `Число`, значение которого равно `2`
ЮТест.ОжидаетЧто(Коллекция)
    .НеСодержит(ЮТест.Предикат()
        .Реквизит("Число").Равно(2));

Проверка нескольких условий на один реквизит

ЮТест.Предикат()
    .Реквизит("Число")
        .ИмеетТип("Число")
        .Больше(0)
        .Меньше(2)
        .Равно(1)
    .Получить();

Проверка вложенных свойств

ЮТест.Предикат()
    .Свойство("ВложенныйОбъект.Число").Равно(2);

Использование в моках

Мокито.Обучение(Модуль)
    .Когда(Модуль.Посчитать(ЮТест.Предикат()
        .Реквизит("Оператор").Равно("Сложить")))
    .ВернутьРезультат(Результат1);
    .Когда(Модуль.Посчитать(ЮТест.Предикат()
        .Реквизит("Оператор").Равно("Вычесть")))
    .ВернутьРезультат(Результат2);

Использование в запросах

ДанныеТовара = ЮТЗапросы.Запись("Справочник.Товары", ЮТест.Предикат()
    .Реквизит("Наименование").Равно("Товар 1")
    .Реквизит("Ссылка").НеРавно(Исключение));

Получение записей из базы

ДанныеТовара = ЮТЗапросы.Запись("Справочник.Товары", ЮТест.Предикат()
  .Реквизит("Наименование").Равно("Товар 1")
  .Реквизит("Ссылка").НеРавно(Исключение));

Особенности

Структура предиката

Каждый предикат — это структура с полями:

  • ИмяРеквизита — имя свойства/реквизита, к которому применяется условие (или Неопределено для всего объекта)
  • ВидСравнения — тип проверки (например, "Равно", "Больше", "ИмеетТип" и др.)
  • Значение — значение для сравнения
  • ОкончаниеИнтервала — (опционально) для условий "между"

Пример структуры:

{
    ИмяРеквизита: "Число",
    ВидСравнения: "Больше",
    Значение: 0
}

Особенности контекста

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

Внимание!

Все методы предикатов работают с глобальным контекстом. Если вы создаёте несколько предикатов подряд, они будут ссылаться на один и тот же набор условий. Чтобы получить независимые предикаты, используйте метод .Получить():

Схема "залипания" контекста

УсловиеСтрока = ЮТест.Предикат().ИмеетТип("Строка");
УсловиеЧисло = ЮТест.Предикат().ИмеетТип("Число");

Результат: обе переменные будут ссылаться на один и тот же глобальный контекст, и оба предиката будут ожидать тип "Число".

Схематично:

[Глобальный контекст]
|
+-- ЮТест.Предикат() --> инициализация контекста: []
+-- .ИмеетТип("Строка") --> контекст: [ИмеетТип: "Строка"]
|
+-- ЮТест.Предикат() --> обнуление контекста: []
+-- .ИмеетТип("Число") --> контекст: [ИмеетТип: "Число"]

Правильный способ:

УсловиеСтрока = ЮТест.Предикат().ИмеетТип("Строка").Получить();
УсловиеЧисло = ЮТест.Предикат().ИмеетТип("Число").Получить();
// Теперь это независимые массивы условий

Схематично:

[Глобальный контекст]
|
+-- ЮТест.Предикат()
+-- .ИмеетТип("Строка")
+-- .Получить() --> копия условий для строки
|
+-- ЮТест.Предикат()
+-- .ИмеетТип("Число")
+-- .Получить() --> копия условий для числа

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

Мокито.Обучение(Модуль)
    .Когда(Модуль.СделатьЧтоТо(
        ЮТест.Предикат().ИмеетТип("Строка"),
        ЮТест.Предикат().ИмеетТип("Число")))
    .ВернутьРезультат(Результат1);

Результат: оба параметра будут ссылаться на один и тот же глобальный контекст, и оба предиката будут ожидать тип "Число".

Правильный способ:

Мокито.Обучение(Модуль)
    .Когда(Модуль.СделатьЧтоТо(
        ЮТест.Предикат().ИмеетТип("Строка").Получить(),
        ЮТест.Предикат().ИмеетТип("Число").Получить()))
    .ВернутьРезультат(Результат1);

Особенности реализации

Модуль предикатов предназначен исключительно для создания и описания набора условий и утверждений, которые затем будут использованы.

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

Типичные ошибки и отладка

  • Забыли вызвать .Получить() — предикаты "залипают" и условия накапливаются в одном объекте.
  • Использование без реквизита — условие применяется к самому объекту.
  • Несовпадение типов — например, сравнение строки и числа.
  • Не все проверки поддерживаются во всех механизмах — например, не все предикаты работают в запросах.

FAQ

Q: Как проверить несколько свойств одновременно?

A: Просто добавьте несколько .Реквизит(...).Равно(...) в цепочку:

ЮТест.Предикат()
    .Реквизит("Имя").Равно("Тест")
    .Реквизит("Код").Равно("001");

Q: Как использовать предикаты для табличных частей?

A: Указывайте имя табличной и реквизита в методе Реквизит:

ЮТест.Предикат()
    .Реквизит("Товары.Номенклатура").Равно(Товар1);

Q: Как проверить, что объект не содержит свойство?

A: Используйте .НеИмеетСвойства("ИмяСвойства").

Интеграция с другими механизмами

Предикаты используются: