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

Текучий интерфейс

Большая часть публичной функциональности тестового движка YAxUnit реализована с использование текучих выражений (Fluent interface)

Основная цель их использования - улучшение читаемости кода и упрощение работы с движком. Текучие выражения хорошо зарекомендовали себя и широко применяются в других языках (примеры: C#: builder pattern, java: stream-api, php: dsl, C#: tests assertions, 1С: текучие утверждения, 1С: Элемент, 1С: объектная модель запроса). Текучие выражения - это цепочка методов, объединенных одним контекстом, который они настраивают/обрабатывают. Он упрощает множественные вызовы методов одного и того же объекта. Например:

Создание документа с использованием текучих выражений
    Документ = ЮТест.Данные().КонструкторОбъекта(Документы.ПриходТовара)
.Установить("Поставщик", Поставщик) // Устанавливает значение реквизита документа.
.Фикция("Дата") // Генерирует случайную дату.
.Фикция("Номер") // Генерирует случайный номер.
.ФикцияРеквизитов("Склад, Валюта") // Создает "пустышки" склада и валюты.
.ТабличнаяЧасть("Товары") // Переключает контекст на работу с табличной частью "Товары".
.ДобавитьСтроку() // Добавляет строку табличной части.
.Фикция("Товар", Новый Структура("Поставщик", Поставщик)) // Создает "пустышку" товары от конкретного поставщика.
.Фикция("Цена") // Генерирует случайную цену.
.Провести();
Проверка документа с использованием текучих выражений
    ЮТест.ОжидаетЧто(Документ)
.Свойство("Дата").Заполнено() // Проверяет, что дата документа заполнена.
.Свойство("Номер").Заполнено() // Проверяет, что номер документа заполнен.
.Свойство("Склад").Заполнено() // Проверяет, что склад документа заполнен.
.Свойство("Валюта").Заполнено() // Проверяет, что валюта документа заполнена.
.Свойство("Товары").ИмеетДлину(1) // Проверяет, что табличная часть "Товары" имеет длину 1.
.Свойство("Товары[0].Товар").Заполнено() // Проверяет, что товар из первой строки заполнен
.Свойство("Товары[0].Товар.Поставщик").Равно(Поставщик); // и имеет поставщика, равного нужному

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

Создание документа с заполнением случайных значений
Документ = Документы.ПриходТовара.СоздатьДокумент();
Документ.Поставщик = Поставщик;
Документ.Дата = ЮТест.Данные().СлучайнаяДата();
Документ.Номер = ЮТест.Данные().СлучайнаяСтрока();
Документ.Склад = ЮТест.Данные().Фикция(Тип("СправочникСсылка.Склад"));
Документ.Валюта = ЮТест.Данные().Фикция(Тип("СправочникСсылка.Валюты"));

СтрокаТовары = Документ.Товары.Добавить();
СтрокаТовары.Товар = ЮТест.Данные().Фикция(Тип("СправочникСсылка.Товары"), Новый Структура("Поставщик", Поставщик));
СтрокаТовары.Цена = ЮТест.Данные().СлучайноеПоложительноеЧисло();

Документ.Записать(РежимЗаписиДокумента.Проведение);
Ссылка = Документ.Ссылка;
Или вариант используя конструктора, но без цепочки вызовов
Конструктор = ЮТест.Данные().КонструкторОбъекта(Документы.ПриходТовара);
Конструктор.Установить("Поставщик", Поставщик);
Конструктор.ФикцияРеквизитов("Дата, Номер, Склад, Валюта");
Конструктор.ТабличнаяЧасть("Товары");
Конструктор.ДобавитьСтроку();
Конструктор.Фикция("Товар", Новый Структура("Поставщик", Поставщик));
Конструктор.Фикция("Цена");
Документ = Конструктор.Провести();

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

Но при этом текучие выражения имеют и ряд минусов:

  • Проблема с отладкой, нельзя поставить точку внутри цепочки, только проход по шагам.
  • Не работает контекстная подсказка в конфигураторе (хотя она и так редко работает).
  • Необходимость привычки и использовать "правильное" форматирование (отступы).

Принцип работы текучих выражений в YAxUnit

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

  • хранит внутри себя контекст(состояние)
  • возвращает сам себя из методов
  • или новый объект, передав в него текущий контекст.

В языках, поддерживающих ООП, под каждый текучий интерфейс создается свой класс, хранящий контекст и предоставляющий необходимый API. В 1С это можно реализовать в виде набора обработок. Таким образом в YAxUnit созданы:

  • Конструктор объектов информационной базы, ЮТест.Данные().КонструкторОбъекта.
  • Конструктор объектов XDTO, ЮТест.Данные().КонструкторОбъектаXDTO.
  • Эмулятор выборки данных ADO.RecordSet, ЮТест.Данные().ADORecordSet.
  • Эмулятор запроса к http сервису, ЮТест.Данные().HTTPСервисЗапрос.

А есть, другая часть API построенная на текучих выражения, она реализуется через общие модули и хранение состояния в глобальном контексте.
Общие модули позволяют, в отличии от обработок:

  • создавать API доступный и на клиенте, и на сервере
  • избежать дублирования кода, который приводи к расхождению логики api
  • не захламлять контекстную подсказку свойствами обработки (или формы для клиента)
  • оптимизировать время работы (нет затрат на создание обработок)

Дополнительно, так как контекст глобальный, не обязательно передавать объект, чтобы получить доступ к настроенному контексту.
Например:

  • При регистрации теста, информация о тестах никуда не возвращается, движок, после вызова метода ИсполняемыеСценарии, просто считывает результат из глобального контекста.
  • Или настройки мокито, выполненные в методе теста сразу же доступны в методах других модулей (перехватываемых).