Как стать автором
Обновить

Интеграция сценарного тестирования в процесс разработки решений на базе платформы 1С

Время на прочтение66 мин
Количество просмотров16K
Эта статья является практическим пособием по внедрению тестирования на основе сценариев в процесс разработки программного обеспечения на базе платформы 1С: Предприятие 8.3. Документ отличает прикладная направленность, в нем содержится много кода, подходов и конкретики. Все рассмотренные примеры основаны на использовании бесплатной конфигурации Тестер.


Вступление


Независимо от того, какой философии придерживается компания, одно остается неизменным – процесс программирования приложений с пользовательским интерфейсом, включает в себя запуск программы, эмуляцию действий пользователя, зрительный анализ ситуации, возврат к доработке кода, и так далее, с последующим повторением итерации. Принципиально, в этой схеме можно выделить два отрезка времени: программирование и проверка. Время, которое тратится на программирование, превращается в код. Время, которое тратится на проверки, уходит безвозвратно. В силу специфики процесса разработки, когда мы снова и снова запускаем приложение, повторяя итерации, компенсация потерянного времени достигается за счет уменьшения второго временного отрезка, либо сдвига всех проверок на некую «финальную» итерацию или запуск BDD-теста. Отсутствие возможности повторить опыт проверок, отрицательно сказывается на качестве программного продукта.

Во многом, эти вопросы решаются внедрением специальных методик, например, TDD, BDD, организацией дополнительных процессов по тестированию. Однако, везде есть свои нюансы.

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

BDD, за счет человеческого языка сценариев Gherkin, идеологически сшивает в одном месте специалистов прикладной и технической областей, а используемая конструкция описания сценариев Given-When-Then универсальна. Однако, BDD больше о процессе создания продукта, а не только о его тестировании. BDD основывается на ролевом взаимодействии сотрудников компании, что делает её требовательной (и в какой-то мере хрупкой) ко всем звеньям цепи разработки. Другой важной проблемой является не высокая эффективность языка Gherkin при разработке сложных (с точки зрения BDD) сценариев. К автоматическому документированию, как бонусу BDD, также, немало вопросов. Руководства пользователя к продуктам известных вендоров, не строятся по принципу Given – When – Then, а с техническим документированием, многие выбирают подход “Лучше устаревшая, но понятная, чем актуальная, но непонятная (или понятная только автору сценариев) документация.

Исторически, TDD, BDD и другие, пришли из мира, где системы 1С нет как класса, где идет очень четкая специализация, и разрыв между понимаем задачи техническим специалистом и бизнесом, как правило, велик. Например, в типовой 1С-франчайзи, очень часто задачи идут конвейером, могут приниматься по телефону, скайпу или электронной почте. Организация заседаний на каждую доработку в формате трех друзей (Three Amigos), выглядит несерьёзно, а значит почти вся методика BDD искажается или даже ломается.

В тоже время, на крупных проектах, эти переговоры необходимы. В идеале там будет аналитик, тестировщик и разработчик (BA+QA+Developer), но там не будет всего отдела программистов. А им, в конечном итоге, писать код, проводить тестирование, зачастую, намного более глубокое, с пограничными значениями, отклонениями, и другими особенностями реализации, возникновение которых рождается в коде и не может быть описано в приемочном тесте или постановке задачи. И это будет не TDD, а реальные сценарные тесты, которые программисты как правило не автоматизируют, а выполняют вручную в рамках процесса кодирования.

Другой пример из жизни программиста 1С: считается вполне типовым сценарий (пример описан не для российского законодательства): Расчет отпуска сотруднику, где нужно учесть предыдущие начисления за прошлые три месяца, включая количество праздников за период расчета, праздников, выпавших на выходные дни, если они предусмотрены графиком его работы, квартальные, годовые и фиксированной суммой премии, которые могут быть начислены не в периоде их расчетов, смена должности и/или графика работы, учет системы прямого табелирования или методом отклонений, а также, период самого отпуска должен быть очищен от праздников. В случае, если сотрудник часть отпуска проболел – следующее продление должно учитывать предыдущие начисления, кроме этого, нужно предусмотреть пересечение периодов начислений (неделя/две недели/месяц/вахта) с периодом отпуска и другие характеристики. В качестве сценария в данном случае, обычно выступает законодательный акт или статья из бухгалтерской периодики, что в большинстве случаев считается для программиста 1С включенной в область его ответственности задачей. Попытка организовать (а не только написать) сценарное тестирование на языке Gherkin для данного, вполне стандартного сценария, может стать настоящим испытанием.

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

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

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

О конфигурации Тестер


Бесплатное решение для проведения сценарного тестирования приложений на базе 1С: Предприятие 8.3, управляемые формы. Тестер призван сохранить и воспроизвести опыт программиста, время на приобретение которого было потрачено на ручные проверки и тестирование. Основным профитом от использования Тестера является повышение качества программ, без существенных организационных изменений, изменений принципов программирования, и других долгосрочных инвестиций времени на выпуски очередных версий продуктов. Тестер может использоваться как независимый инструмент, так и совместно с BDD, выступая в качестве платформы для разработки сложных тестов.

Возможности:


  • Программирование и запуск сложных сценарных тестов в одной среде
  • Глубокое тестирование интерфейса и бизнес логики
  • Запись работы пользователя с переводом сценария в программный код
  • Организация коллективной работы по созданию базы тестов
  • Гибкий ролевой доступ, раздельный RLS-доступ пользователей к тестируемым конфигурациям
  • Инкрементальная выгрузка/загрузка тестов во внешние файлы
  • Формирование протоколов и сводных отчетов по выполненным сценариям
  • Настройка рассылки результатов тестов по электронной почте
  • Тестирование по расписанию, организация непрерывного процесса прогона тестов в рамках CI

Особенности:


  • Быстро устанавливается, не требует специальных (кроме 1С) знаний и программного обеспечения
  • Быстро интегрируется в процесс разработки
  • Не требует фундаментального пересмотра философии программирования
  • Сфокусирован на процесс создания реальных тестов
  • Не требует подготовки отдельных баз и эталонных данных

Другое применение:


Тестер может быть использован как автоматизатор рутинных операций, как в процессе разработки, так и в режиме реальной эксплуатации продуктовых баз. Среди таких задач можно выделить:
  • Выгрузка загрузка данных, пакетный запуск 1С для административных задач
  • Запуск и манипуляции обработками, отчетами. Тестером можно написать сценарий, который будет формировать отчет, проверять какие-то данные или открывать обработку и нажимать там нужные кнопки и выбирать поля
  • Формирование начальных или тестовых данных для ваших решений (вместо использования конвертации данных)
  • Нагрузочное тестирование. Например, у вас есть доработка и вы хотите проверить работу этого функционала под нагрузкой. Для этого можно написать сценарий запуска Тестера нужное кол-во раз с передачей целевого тестируемого сценария в качестве параметра
Последние обновления https://github.com/grumagargler/tester
Депо общих тестов https://github.com/grumagargler/CommonTests
Депо демо тестов для ERP2 (демо) https://github.com/grumagargler/ERP2
Сайт проекта http://www.test1c.com
Язык Интерфейс: Английский, Русский
Справка: Английский (частично), Русский

Базовые определения


Процесс тестирования приложений при помощи Тестера основывается на взаимодействии Тестера с запущенной конфигурацией 1С в режиме 1С: Предприятие.

При этом считается, что Тестер выступает в роли Менеджера тестирования, а тестируемая конфигурация – в роли Клиент тестирования.

Тестер является системой сценарного тестирования. Это означает, что взаимодействие с тестируемым приложением происходит в режиме эмуляции действий пользователя.

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

Тестер может взаимодействовать с тестируемым приложением только так, как это может делать пользователь, от имени которого производится выполнение сценарных тестов. Отсюда формулируется важное замечание: разрабатываемые механизмы тестируемого приложения должны быть проверяемыми. Например, если стоит задача проверить правильность движений документа, и при этом в системе нет отчета по движениям документа и нет отчета/обработки/формы, через которую пользователь смог бы проверить записи, Тестер не сможет проверить правильность движений (в данном случае, таковой является концепция, однако технически, Тестер может запустить внешнюю обработку, в коде которой программист может выполнить произвольный программный код проверки).

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

Тестер не является системой TDD, и строго говоря, не является идеологически чистым BDD. Тестер старается не привязываться к религии в разработке, он решает те проблемы, с которыми сталкиваются абсолютно все разработчики программного обеспечения. Тестер старается решать эти задачи максимально эффективно.

Тестер предназначен для тестирования конфигураций, разработанных на управляемых формах версии 1С: Предприятие 8.3.x. Обычные формы не поддерживаются.

Предполагаемые пользователи Тестера, это:
Пользователи Задачи
Программисты Использование системы в процессе разработки. Эволюция процесса ручного тестирования
Тестировщики с базовыми знаниями программирования на языке 1С Написание сценариев, максимально приближенных к сценариям работы пользователей. Эти сценарии, обычно не такие глубокие, как у программистов, но более выраженные с точки зрения бизнес-процесса
Бизнес аналитики. Консультанты Запуск тестов, анализ результатов. Через чтение тестов, понимание работы функционала системы

Концепция


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

Имея на входе информацию по задаче, мы мысленно разбиваем весь процесс кодирования на небольшие транзакции, рывки. Это является для нас неким краткосрочным планом работ (даже если на входе BDD-сценарий). Затем, мы начинаем методично работать по схеме “написание кода – запуск приложения – проверка ожидаемого поведения – возврат к доработке кода”. Количество таких транзакций и степень их завершенности — очень хрупкие сущности, потому что опираются на способность и возможность программиста быть в сосредоточении.

Даже при идеальных условиях выполнения работ по кодированию, опыт этих транзакций без специальных инструментов – не восстановим. Кроме этого, если в середине намеченного пути, возникает непредусмотренная ситуация, ошибка в связанном коде или недоработка используемого механизма, программисту приходится переключаться в другой контекст для разбора проблемы, нередко делая это в спешке, потому что намеченный план работ начинает ускользать из рабочей памяти нашего мозга. При изменении сопутствующего кода, мы стараемся удержать в голове триггер “нужно протестировать потом этот код”.

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

Интегрирование в работу инструмента тестирования, потребует от программиста выработки навыка трансляции мысленно составленного плана работ в программный код сценария. Это дает ему возможность “разгружаться” сериализуясь в код теста. Накапливаемые сценарии, всегда можно воспроизвести, что позволяет сосредоточится на качестве выполняемых работ. Рост количества тестов дает свободу действий не только для рефакторинга, но и других существенных изменениях функциональности разрабатываемого приложения, что является одной из важных концепций инструмента.

Быстрый старт


Для быстрого развертывания инфраструктуры тестирования достаточно выполнить следующие шаги (предполагается, что Тестер скачан и прописан в списке информационных баз):
  1. Конфигурация Тестер должна быть запущена в режиме 1С: Предприятие с ключом /TESTMANAGER.
    Этот параметр может быть прописан непосредственно в профиле информационной базы Тестера, например так:

  2. Тестируемая конфигурация должна быть запущена в режиме 1С: Предприятие с ключом /TESTCLIENT.
    Этот параметр может быть прописан непосредственно в профиле тестируемой информационной базы, например так:
  3. Версия 1С, используемая для тестирования должна быть одинаковой как для Тестера, так и для тестируемой конфигурации.
  4. Запуск Тестера и тестируемой конфигурации желательно производить на одном компьютере. Для запуска программ на разных компьютерах, необходимо настроить порт и адрес тестируемого приложения в справочнике Приложения.
С технической точки зрения, для начала разработки тестов и тестирования больше ничего не требуется.

Первый сценарий


Рассмотрим пример создания теста к конфигурации БСП 2.2.
Напишем элементарный тест, который просто откроет форму списка справочника Партнеры.
  1. Запускаем Тестер с ключом /TESTMANAGER.
  2. Запускаем БСП с ключом /TESTCLIENT.
  3. Переключается в Тестер, открываем меню быстрых функций и создаем новое приложение:

  4. Через меню быстрых функций открываем сценарии и создадим новый сценарий:
    1. В поле ID напишем Test1
    2. В тексте сценария напишем:
    3. Подключить (); // Подключаем БСП к Тестеру
      Меню ( "Справочники / Демо: Партнеры" ); // Открываем в БСП форму списка
    4. Переключимся на вкладку Свойства, и в поле Приложение укажем БСП
    5. Нажмем на панели кнопку Основной
    6. Тест готов. Теперь нажмем кнопку Запустить (или F5 ) и запустим его.
В результате, в БСП должна открыться форма списка справочника Демо: Партнеры. Если бы во время открытия справочника, произошла какая-то ошибка, Тестер бы о ней сообщил.

Второй сценарий


Добавим в первый тест создание нового партнера, для этого внесем следующие изменения в сценарий:
// Подключаем БСП к Тестеру
Подключить ();

// Закроем все окна в БСП
ЗакрытьВсё ();

// Открываем в БСП форму списка
Меню ( "Справочники / Демо: Партнеры" );

// Говорим Тестеру, что мы будем сейчас работать с этим окном
Здесь ( "Демо: Партнеры" );

// Нажмем кнопку Создать
Нажать ( "Создать" );

// Говорим Тестеру, что мы будем сейчас работать с этим окном
Здесь ( "Демо: Партнер (создание)" );

// Установим наименование партнера
Установить ( "Наименование", "Мой тестовый партнер" );

// Кликнем на флажок Поставщик
Нажать ( "Поставщик" );

// Нажмем кнопку Записать и закрыть
Нажать ( "Записать и закрыть" );

После выполнения теста, в базе БСП должен быть новый партнер.
После выполнения теста, в окне сообщений Тестера, будет такое предупреждающее сообщение:
14: Поле "Создать" найдено в нескольких местах: ФормаСоздать (Тестируемая кнопка формы / Кнопка командной панели), СписокКонтекстноеМенюСоздать (Тестируемая кнопка формы / Кнопка командной панели) {Тест1[14]}

Сообщение говорит о том, что в 14 строке кода, метод Нажать нашел несколько мест, где можно нажать Создать.
Для того, чтобы задавать однозначно объекты, с которыми требуется взаимодействие, можно использовать идентификаторы, или полный путь.
Например, в 14 строке можно написать так:
// Вариант 1
Нажать ( "!ФормаСоздать" );

// Вариант 2
Нажать ( "!КоманднаяПанель / Создать" );

Для получения идентификаторов и внутреннего содержимого форм тестируемого приложения, см раздел Вкладка Поля.
Следующие разделы посвящены полному обзору функций Тестера.

Интерфейс


Интерфейс Тестера организован с позиций удобного написания и запуска тестов. При первом запуске, на домашнюю страницу выводится справка по системе (выкачивается из интернета) и открывается основной сценарий, если он задан. В левой части системы находится текстовый редактор для ввода кода сценария, справа – дерево сценариев.
К сожалению, на момент подготовки этой документации, система еще не поддерживает синтаксическое цветовое оформление программных модулей. Лишь поначалу это может показаться серьёзной проблемой, но спустя непродолжительное время, вы перестанете обращать на это внимание, потому что изначально, код сценария существенно проще, чем стандартный программный код конфигураций.
Некоторые специалисты ненадолго предпочли использование Visual Studio Code с расширением Language 1C (BSL) или обычный конфигуратор 1С для набора текста сценария, с последующим копированием или загрузкой в Тестер. Однако, практика показала, что ценность цветового оформления существенно ниже возможности взаимодействия программиста с полями и структурой тестируемого приложения, деревом сценариев, помощником и другими функциями, доступными только из Тестера.

Дерево сценариев


Тестер поставляется с демонстрационной базой. В демо-базе содержится небольшой набор универсальных тестов, а также специальные тесты для конфигурации ERP2. Я предлагаю использовать демо-базу в качестве начальной базы для создания вашей собственной инфраструктуры тестов.
Когда вы запускаете Тестер, в правой части экрана находится дерево сценариев. Это дерево позволяет организовать сценарии в виде иерархии. Каждый узел дерева имеет тип. Тип задает смысловое значение сценариев внутри узла, сортировку и пиктограмму. В терминологии 1С, это дерево – обычный иерархический справочник.
На картинке ниже показан пример дерева тестов из демонстрационной базы, и далее дано описание каждого маркера:


Типы сценариев, Маркер 1


Левее маркера, в зеленом цвете и специальной пиктограмме, находятся такие узлы как Корзина, Общее, Таблица и другие.
Такое оформление означает, что это библиотеки тестов. Признак, что узел является библиотекой, задается при создании/редактировании теста:

На картинке видно, что кроме библиотеки, тест может быть папкой, сценарием или методом.
Строгой технической привязки и контроля типа сценария в Тестере нет. Главное предназначение типов – это организация визуальной логики взаимосвязи сценариев.
В случае, если это библиотека, подразумевается, что внутри данной папки будут храниться только библиотечные сценарии-методы. Метод – это сценарий, который не должен привязываться к конкретной тестируемой логике приложения. Методы обычно могут принимать параметры, они работают как процедуры/функции. Методы не должны запускаться как отдельные, самостоятельные сценарии (запуск сценариев см. здесь).
Пример метода: ПроверитьОшибкуЗаписи.
Пример использования в коде сценария:
Вызвать ( "Общее.ПроверитьОшибкуЗаписи", "Не заполнен контрагент" );

Группы сценариев, Маркер 2


На этом маркере изображена папка Документы. Папка – это логическая группировка сценариев. Группировка может быть произвольной. Вы можете создавать папки с тестами по принципу тестируемых подсистем, ролей пользователей или технических заданий. Если вы не чувствуете, какая структура тестов вам нужна, тогда универсальным подходом является проецирование структуры тестов на объекты метаданных вашей конфигурации. Смело можно создавать группы Справочники, Документы и так далее. Внутри этих групп, создавать группы с названиями объектов метаданных, а следующим уровнем, располагать конкретные сценарии. В будущем, если потребуется, вы сможете сделать перегруппировку.

Обычный сценарий, Маркер 3


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

Сценарий-метод, Маркер 4


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

Основной сценарий, Маркер 5


Этим маркером отмечен обычный сценарий. Подчеркивание снизу указывает на то, что данный сценарий в настоящий момент текущий (основной). Основной сценарий, это тот сценарий, над которым сейчас работает программист. Любой сценарий может быть установлен основным (правый клик в дереве / Основной). У каждого пользователя системы Тестер может быть свой основной сценарий. Основной сценарий отличается от остальных тем, что его легко найти в дереве (правый клик в дереве / Найти основной сценарий) и легко запустить на выполнение (см. Запуск тестов). Также, основной сценарий автоматически открывается при запуске новой сессии Тестера.

Приложения, Маркер 6


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

Поле Приложение можно не устанавливать, оставить пустым. В этом случае, предполагается что сценарий универсальный и может работать для всех приложений в системе.
Обратите внимание, что для библиотечных тестов и групп первого уровня (см. картинку с деревом тестов) приложение не задано, а для группы ЗаказПоставщику, ТестСоздания и т.д. приложение задано. Логика следующая: библиотечные тесты могут работать для любой конфигурации, поэтому при их создании, приложение не задали. Группа тестов Документы тоже будет в любой конфигурации, приложение также не задано. Для группы ЗаказПоставщику, ТестСоздания и т.д. приложение задано, потому что они имеет четкую привязку к тестируемому приложению.
Задавать приложение для сценариев нужно:
  1. Для логической группировки сценариев и возможности отбора сценариев в дереве тестов, см. картинку:
  2. Для контроля уникальности имен сценариев. Например, может существовать несколько тестов с названием ЗаказПоставщику, если для каждого из них задано отдельное приложение. Создать еще одну группу тестов с названием Документы нельзя, потому что она задана как общая, без указания конкретного приложения.

Хранилище, Маркер 7


Правее маркера, находится колонка, определяющая в виде пиктограммы статус сценариев в хранилище тестов.
Стратегия редактирования сценариев в Тестере организована по принципу работы со стандартным хранилищем 1С. Для того, чтобы начать редактирование сценария, его нужно захватить (правый клик в дереве / Захватить). Для того, чтобы сценарий сохранить в хранилище тестов, его нужно туда поместить (правый клик в дереве / Поместить). В момент помещения теста в хранилище (или создания нового теста), создается его версия. В тот период времени, пока сценарий захвачен на редактирование, остальные участники тестирования могут использовать захваченный сценарий, но они не могут его изменять. При использовании захваченного сценария, программисты буду получать от Тестера последнюю версию сценария, а не текущую, которая редактируется в настоящий момент.
Например, если вы захватили и редактируете сценарий-метод ЗадолженностьПоставщикам, а другой программист запустил на выполнение сценарий Начало, который в свою очередь из кода вызовет заблокированный вами метод ЗадолженностьПоставщикам, Тестер отдаст этому программисту последнюю версию теста ЗадолженностьПоставщикам, которая была в хранилище до того, как вы начали его редактирование. Таким образом, ваши текущие изменения сценария не повлияют на работу других разработчиков.
При помещении изменений в хранилище, ваши изменения становятся доступны всем пользователям. Если вы разрабатываете определенный функционал, и ваши изменения теста могут затронуть работоспособность других связанных тестов, рекомендуется использовать метод МояВерсия ().

Запуск тестов


Любой тест из дерева тестов может быть запущен на выполнение, вне зависимости от его типа. Однако, как было сказано выше, предполагается, что запускаться должны только тесты с типом Сценарий, а сценарии-методы, должны вызываться из кода сценариев.
Основной способ запуска тестов это кнопка F5 или команда Запустить:

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

Вкладка Поля


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

По порядку следования маркеров:
  1. Позволяет получить структуру всех окон тестируемого приложения, которые сейчас открыты на экране.
  2. Позволяет получить только текущее окно тестируемого приложения.
  3. Позволяет быстро найти в дереве элементов текущий активный элемент тестируемого приложения. Очень удобная функция при написании тестов. Например, можно открыть нужную форму и встать на нужный элемент, затем, в Тестере, получить эту форму (п.2) и нажать Синхронизировать. После этого, Тестер попытается найти и активировать строку в дереве с данным элементом. В случае неудачи, генерируется ошибка.
  4. При навигации по дереву, Тестер пытается активировать выделенные элементы в тестируемом приложении. Будьте внимательны, фокус может “прыгать” с дерева на тестируемое приложение.
  5. Для выделенного поля можно выполнить метод или получить свойство. Набор методов и свойств зависит от типа выделенного элемента и применяется согласно объектной модели тестируемого приложения платформы 1С. Например, на картинке поле ОтборСостояние имеет тип ТестируемоеПолеФормы. В синтаксис помощнике 1С, можно посмотреть, какие методы и свойства доступны объектам этого типа. Одно из них, свойство ТекстЗаголовка, результат получения которого выведен на картинке выше.

Перед внедрением


Несмотря на простоту использования Тестера, внедрение тестирования требует определенных усилий. На практике, в среднем, нужно от недели до двух для того, чтобы адаптироваться к разработке с Тестером.
Перед погружением в детали, определимся с тем, когда автоматизированное тестирование применять неэффективно:
  1. Вы создаете макет, прототип приложения для демонстрации заказчику
  2. Вы вносите точечные исправления в старый код, в те механизмы, сценариев к которым нет и не будет, и вся эта работа без перспектив эволюции модулей
  3. Вы прощупываете будущую реализацию, когда еще нет четкого понимания, что это должно быть, документ или справочник, отчет или динамический список. В этом случае, программист нередко переключается в режим “потока” и быстро накидывает объекты метаданных для того, чтобы доказать или опровергнуть самому себе достижимость результата выбранным путем. В таких ситуациях, практически невозможно выработать сценарий, но это и не нужно. Строго говоря, пока не сформируется полное представление о способе выполнения задачи, сценарное тестирование применять не стоит

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

Установка


Если вы занимаетесь обслуживанием клиентов частным образом, Тестер имеет смысл установить локально на ваш компьютер или ноутбук, файловый вариант. Если вы работаете в команде, вне зависимости от того, работают все программисты над одним проектом или у каждого свой, Тестер желательно установить в локальной сети или облаке. Если ваш коллектив более 3 программистов, я бы рекомендовал установить клиент-серверный вариант платформы.
Даже если ваша команда сильно распределена, с пингом до ~125мс всё еще можно комфортно работать через интернет, разработка Тестера велась с учетом удаленности программистов. Если по каким-то причинам, облачное решение организовать не получается, можно установить программу локально на каждый компьютер специалиста, файловый вариант. Для организации общего хранилища тестов, можно использовать Git. У Тестера есть возможность инкрементальной выгрузки/загрузки тестов в файловую систему.

Кроме этого, для организации ночного тестирования, потребуется установить на выделенном для этого сервере или виртуальной машине тонкий клиент 1С: Предприятие, с возможностью его подключения к облаку, где ведется разработка тестов. В случае использования Git для хранения тестов, нужно будет обеспечить предварительную закачку тестов на сервер тестирования, с использованием утилит от Git. Подробнее, ночное тестирование описано разделе Запуск тестов по расписанию.
Тестер позволяет в одной базе вести работу с тестами для неограниченного числа приложений (конфигураций). Не стоит создавать отдельную базу с Тестером под каждый проект/конфигурацию/клиента. В Тестере возможна настройка ограничения доступа пользователей к приложениям.

Базы данных


Тестирование при помощи Тестера предполагает наличие двух баз данных: рабочей и начальной.

Рабочая база — это база данных в которой работает программист, и в которой выполняются все тесты. Соответственно, у каждого программиста своя рабочая база.

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

Создание и обновление начальной базы


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

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

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

Использовать такой тест удобно в следующих случаях:
  • Каждый программист/аналитик/тестировщик может четко проверить консистентность своей рабочей базы перед тем как вообще что-то тестировать (напомню, рабочая база создается на основании начальной)
  • Данный тест можно запускать первым в списке тестов для ночного тестирования, по результатам которого, определить целесообразность дальнейшего тестирования
  • Тест хорошо работает как напоминание разработчику, какие данные можно брать не думая об их существовании, а какие нужно создавать дополнительно. В процессе работы, рабочая база превращается в хлам, и помнить, какие данные там начальные, а какие нет, сложно. Разворачивать каждый раз начальную базу последней версии – может быть не всегда удобно или возможно, при этом, быстрое определение происхождения данных при написании сценария очень важный момент. Например, в базе есть группа материалов “Детали насосно-компрессорного оборудования”, и если есть тест проверки начальных данных, в нем можно произвести поиск по коду и вычислить, происхождение этой группы, буквально в несколько кликов.
Примечание: в качестве решения проблемы загрязнения рабочей базы я не рассматриваю откат транзакций в сценарном цикле, что вы можете найти как подход в некоторых авторитетных источниках. По моему опыту, используя такой подход говорить о сколь-нибудь серьёзном тестировании не приходится.
Начальную базу нужно подключить к хранилищу, куда сливаются финальные обновления конфигурации. Это необходимо для оперативного обновления начальной базы новым функционалом, обновления и/или заполнения базы новыми начальными данными.

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

Если новый функционал требует обновления/добавления начальных данных, такую задачу можно решить двумя способами:
  1. Ответственный разработчик, открывает обновленную начальную базу, дополняет вручную нужными данными, дорабатывает тест проверки начальных данных, запускает этот тест, убеждается в согласованности данных, и выгружает полученную базу в общедоступное место
  2. Всё тоже самое что в п.1 с той разницей, что вместо ручного обновления, ответственный разработчик, пишет сценарный тест.

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

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

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

Например, если ваш тест проверяет работу механизма контроля остатков в зависимости от системной настройки приложения, вашему тесту придётся изменять эту настройку. Если ваш тест упадет, настройка не будет возвращена в первоначальное положение и следующий запуск этого теста уже вряд ли отработает правильно, даже если с функциональной точки зрения всё в порядке. Таким образом, вначале желательно тестом сходить в настройки, установить требуемое значение и затем продолжить основной сценарий. К слову, подобные проверки не всегда требуются в оперативной работе, поэтому их запуск имеет смысл делать по условию, например, по имени пользователя ночного тестировщика, или флагу глобальных настроек (см. API Тестера).

Эталонная база


Эталонная база данных используется для тестирования бизнес-логики приложения.
Использование эталонной базы при интеграции тестирования в процесс разработки не эффективно и не используется Тестером по следующим причинам:
  1. Требуется особый уход за эталонными данными, их корректировка в соответствии с изменениями функционала решения
  2. Эталонные данные инертны: новый функционал требует новых эталонных данных, которые могут пересекаться с уже существующими эталонными данными. Вытекающая отсюда осторожность при манипуляции с эталонными данными ограничивает или замедляет развитие спектра тестируемого функционала
  3. Оперативный прогон тестов практически невозможен, выгружать/обновлять/загружать эталонные базы долго. Под оперативностью понимается тестирование при каждом запуске программистом приложения на выполнение, а не увеличение числа релизов и закачки схемы на CI-сервере с прогоном тестов в течение дня
  4. Количество эталонных баз стремится расти, это осложняет их обслуживание и весь процесс тестирования

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

Данные для тестирования


Данные для тестирования это входящая для сценариев информация, требуемая для успешного их прохождения. Если ваш тест открывает форму списка справочника изделий и устанавливает фильтр по заводу-производителю, вашими тестовыми данными как минимум будут: запись в справочнике заводов-изготовителей, запись в справочнике изделий, с заполненным реквизитом завода-изготовителя. Тестовыми данными в этом случае будут и пользователь, под которым была запущена информационная база, единица измерения для изделия и другие сущности.

Тестовые данные состоят из двух слоёв:
  1. Начальные данные, которые хранятся в начальной базе и обеспечивают минимальный набор типовой, редко изменяемой информации
  2. Создаваемые данные, которые необходимы для тестирования конкретного функционала

Наибольший интерес представляет второй слой. Существует много подходов создания тестовых данных, но в их совокупности можно выделить две стратегии:
  1. Выгрузка заготовленных данных из предварительно подготовленных баз или шаблонов, и последующая их загрузка перед началом или во время тестирования. Вариантов выгрузки/загрузки в данном подходе много. Это может быть конвертация данных, универсальный обмен данными, макеты с данными, выгрузки в dt-файлы или обычная сериализация прикладных объектов в JSON или XML.
    Рассмотрим плюсы и минусы такого подхода:
    Плюсы Минусы
    Очевидность подхода Сложность поддержки согласованности данных в развивающемся функционале приложения, проблемы загрузки данных при измененной структуре метаданных, смене владельцев, появлению полей обязательных для заполнения, переименованию или удалению объектов и их реквизитов.
    Быстрый старт в подготовке данных Потенциальная опасность получения не консистентных данных и ложного положительного прохождения теста. Такие тестовые данные как правило загружаются в специальном режиме, без дополнительных проверок и срабатывания обработчиков форм. Фактически, тестовые данные готовятся не так, как это бы сделал пользователь.
    Сложность логистики хранения, взаимосвязанности наборов файлов, данных и тестов.
    Статичность. Данные загружаются как есть и их сложно менять под вариативность тестов в случае необходимости.
    Проблемы с удалением тестовых данных для повторных запусков сценариев.
  2. Формирование тестовых данных самим сценарием. При этом подходе, все тестовые данные должны создаваться, а не храниться для загрузки. Сам процесс создания не должен отличаться от процесса разработки целевых сценарных тестов. Другими словами, сегодня вы пишите тест проверки создания нового товара, а завтра этот тест уже может быть использован как метод для создания тестовых данных, для тестирования создания документа Реализация товаров.
    Плюсы и минусы такого подхода:
    Плюсы Минусы
    Гибкость создания требуемого окружения тестовых данных под различные вариации тестов. Возможность разработки сложных взаимосвязанных тестов. Сложно без опыта выстроить структуру библиотечных тестов для создания тестовых данных
    Высокое качество тестов за счет дополнительного тестирование всего стека используемых объектов при подготовке данных. Это очень важный нюанс, потому что создание тестовых данных таким образом, выполняется для разных видов целевых сценариев, что автоматически покрывает тестами разнообразие условий создания объектов.
    Например, у нас может быть тест-метод создания поставщика. Но поставщик может создаваться из разных контекстов: из формы списка контрагентов или из поля ввода на форме документа. И если тестовые данные загружать “извне”, можно не отловить ошибку создания контрагента из документа. Тест проверки документа пройдет успешно, хотя фактически, пользователь документ ввести не сможет (потому что не сможет создать для документа поставщика).
    Требуется первоначальная инвестиция времени в разработку тестов-методов
    Консистентность полученного тестового окружения, заполненность реквизитов, отработка возможных событий и программных оповещений об изменении данных, другими словами — полная штатная эмуляция работы приложения.
    Слабая связанность с изменчивостью структуры метаданных. Если изменяется структура данных объекта, достаточно изменить (если необходимо) один тест-метод, при этом все остальные тестовые данные менять не придётся.
    Простота хранения. Все тест-методы хранятся в одной среде, и не являются внешними по отношению к системе тестирования.
    Использование одних и тех же тестовых данных даже в случае, когда их версии метаданных отличаются. В этом случае, в тест-методах можно организовывать условия по обработке/заполнению полей в зависимости от версии используемой программистом конфигурации (см. функцию МояВерсия ())
Тестер исповедует вторую стратегию в подготовке тестовых данных. С точки зрения Тестера, формирование тестовых данных не должно быть в отрыве от целевого тестирования, тестовые данные должны быть подвижны, и они не должны готовиться отдельно от системы разработки тестов.

Структура сценария


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

Для успешного внедрения тестирования, очень важно, чтобы скорость создания и прогона тестов была высокой. Чтобы быстро создавать тесты, нужны две составляющих: тренировка мышления, для быстрого выбора шаблона сценария под задачу и библиотека готовых тестов-методов, для создания всего необходимого по ходу тестирования. Эти вещи приходят со временем.

Чтобы быстро прогонять тесты, нужно чтобы они были следующей структуры:
  1. Определение окружения
  2. Создание окружения
  3. Целевая часть

Определение окружения – это функция в модуле вашего сценария, которая задает параметры теста в виде структуры. Примеры параметров теста: список приходуемых товаров, с ценами и количеством, наименование поставщика, валюта договора и т.д.

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

Целевая часть – это непосредственно код, который будет проверять то, что вы запланировали сделать, и ничего более.

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

Яснее на примере. Давайте представим, что вы занимаетесь доработкой документа Списание ТМЗ, который нужно дополнить особыми движениями по регистрам. Для тестирования (неважно как, вручную или нет) вам понадобится материал на складе, себестоимость которого вы знаете, установленные параметры учетной политики, на которые вы полагаетесь, и другие настройки системы, например, вариант контроля остатков. Таким образом, вам нужно будет вручную, как минимум один раз проверить, что настройки системы в порядке. Затем, нужно будет создать несколько материалов, возможно отдельный склад и как минимум одно поступление ТМЗ, и вы не забудете дату поступления сделать днем ранее. Таким образом – вы определяете и создаете окружение, чтобы затем выполнить целевую часть – провести списание и проверить, что особые движения появились и они правильные. Другими словами – выполняются все три описанных выше структурных действия для разработки автоматизированного сценария.

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

Пример. Добавим в описанную задачу необходимость проверки сообщения об ошибке при списании количества материала, большего, чем есть на складе. Я сильно не ошибусь, если предскажу работу смекалки программиста: он просто откроет уже проведенное списание (предварительно добавив его в избранное для максимальной “скорости”), отменит его проведение, в колонку с количеством введет 999999, запишет документ, а потом проведет его и проанализирует сообщение об ошибке. Если сообщения не будет, программист вернется в код, сделает необходимые доработки и будет повторять итерацию до тех пор, пока сообщение об ошибке не появится.

И тут начинаются проблемы. Во-первых, проверить нормальное поведение списания уже так просто не получится. Нужно будет привести данные в порядок, убрать 999999 и вернуть то количество, которое было на момент, когда мы проверяли правильность доработанных движений. И нам уже вряд удастся быстро вспомнить, каким именно было то первоначальное количество. Во-вторых, даже если программист предварительно скопирует документ, перед тем как менять данные – максимум он потом сможет проверить отсутствие ошибок проведения, но расчетные показатели, результат проведения, он проверить уже не сможет.

Другими словами, имеем классическую картину: последующая доработка механизма потенциальна опасна для предыдущего функционала. И при отсутствии возможности быстрого восстановления окружения теста и его оперативного прогона (а не ночью), потенциальная опасность, рано или поздно перейдет в кинетическую, реальную ошибку.
Дело не только в том, когда мы обнаружим проблему, сейчас, после ночного тестирования, или тестирования тестировщиками, а в том, что когда мы находимся глубоко в коде, нередко нужно здесь и сейчас проверить механизмы, чтобы определиться с выбранным направлением, понять, что наращиваемый функционал не конфликтует и не искажает другие алгоритмы.
Я хотел бы обратить внимание, что даже при условии, когда тесты программиста проходят, они могут содержать методологические ошибки или ошибки понимания задачи. Такие ошибки может выловить отдельный департамент, специальные тестировщики или в конце концов заказчик. Но это отнюдь не означает, что вся история с тестированием программистами не имеет смысла. Наоборот, на основе готовых тестов, программисту достаточно будет скорректировать целевую часть (третья часть сценария), а всю остальную работу по подготовке данные и их проверке выполнит система.

Практика


Перейдем к практической части: поработаем с Тестером в режиме создания нового функционала для конфигурации УТ11 на базе постановки задачи от бизнес-аналитика.

Постановка задачи


Компания торгует сигарами. Необходимо доработать документ Реализация товаров и услуг (далее Реализация) для возможности указания даты согласования следующих продаж по выбранным позициям.
Предполагается следующий сценарий:
— Менеджер вводит документ реализации, для выбранных позиций, на своё усмотрение он устанавливает дату, когда ему нужно было бы перезвонить клиенту и уточнить, насколько хорошо продаются сигары. Например, из всего перечня, его интересуют только сигары со вкусом ментола.
— У менеджера есть список, который он открывает каждый день и видит, когда, по каким клиентам и каким позициям ему нужно провести переговоры с клиентом для организации следующих поставок интересующих его позиций.
— Менеджер выбирает нужную позицию из списка, звонит клиенту, и вводит в систему результат переговоров

Документ Реализация

Необходимо в документ Реализация, в табличную часть, добавить поле для ввода даты согласования (без времени).
Поле не обязательно для заполнения. Если поле задано – тогда данная позиция считается отмеченной для согласования. По таким позициям необходимо предусмотреть обособленный учет на базе регистра сведений Согласование. Записи в данный регистр производить при проведении документа. При повторном проведении документа, существующие записи регистра сведений не обновлять, только добавлять новые.

Регистр сведений Согласование

Это новый, независимый регистр сведений, с возможностью редактирования.
Регистр должен хранить такие данные:
  1. Документ Реализация, Клиент, Менеджер, Товар, Количество, Сумма – автоматически должны заполняться при проведении реализации и не должны быть доступны для редактирования
  2. Флаг Отработано – будет устанавливаться пользователем
  3. Флаг Комментарий – будет вводиться пользователем и фиксировать результат переговоров. Поле должно быть доступно для редактирования только если флаг Отработано = истина.

Для регистра необходимо разработать форму списка и форму элемента. В форме списка должны быть предусмотрены отборы по менеджеру, клиенту и товару.
Запретить пользователю вводить новые записи в данный регистр интерактивно. Примечание: запрет на ввод нового сделать программно с выводом соответствующего сообщения. Не использовать вместо этого настройку команд и ролей пользователя.
Объект расположить в подсистеме Продажи / Оптовые продажи.

Тесты

Необходимо разработать следующий тест:
  1. Ввод одной реализации с двумя позициями, одна из которых будет отмечена для согласования
  2. Открытие списка на согласование, отбор по клиенту, открытие формы регистра на редактирование, установка галочки, сохранение и закрытие
  3. Убедиться, что позиция ушла из списка

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

Разработка


По этой задаче мы можем прикинуть ход разработки и тесты, которые нам понадобятся. Условимся, что начальная база у нас уже есть, в ней проведены нужные настройки, отключены окна-подсказки, проверки контрагентов и так далее (о начальной базе см. здесь). В моем случае, начальной базой будет демо-база УТ11.

Можно выделить как минимум четыре этапа:

Этап 1: добавить реквизит Дата согласования в табличную часть, в процедуре ОбработкаПроверкиЗаполнения реализовать проверку, чтобы дата согласования была больше даты документа

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

Этап 3: реализовать форму списка с фильтрами, как требует бизнес-аналитик

Этап 4: реализовать форму редактирования записи регистра для ввода данных по согласованным позициям.

Этап 1


В таблице ниже состыкуем работу по кодированию и тестированию:
Программирование Тестирование
В Конфигураторе:
  1. Добавляем реквизит ДатаСогласования в табличную часть Товары документа Реализация
  2. В процедуре ОбработкаПроверкиЗаполнения реализуем проверку даты
В Тестере :
Создадим тест ПроверкаДатыСогласования, где:
  1. Введем новую реализацию, добавим строку, впишем в строку некорректную дату
  2. Попытаемся провести, и в списке сообщений найдем требуемое сообщение об ошибке
  3. Затем изменим дату на правильную, проведем и убедимся, что сообщения об ошибке уже нет
Так как это будет наш первый тест для конфигурации УТ11, нам нужно произвести разовую настройку приложения в Тестере.
Необходимо создать приложение УТ11, см. Меню функций / Приложения. Для создаваемого приложения нужно задать актуальную на момент написания теста версию, например так:

Затем, это приложение нужно установить приложением по умолчанию, для этого нужно переключиться в дерево сценариев, открыть Опции, выбрать в поле Приложение УТ11 и нажать кнопку справа:

Следующий шаг – это создание папки РеализацияТоваровУслуг внутри папки Документы, и следом, создание нашего теста ПроверкаДатыСогласования.
В итоге, должно получится так:

Откроем выделенный на картинке сценарий, переключимся в текстовый редактор и введем этот текст:
// Сценарий:
// - Вводим новый документ Реализация
// - Устанавливаем неправильную дату согласования, проверяем наличие ошибки
// - Устанавливаем правильную дату, проверяем отсутствие ошибки

Подключить ();
ЗакрытьВсё ();

// Вводим новый документ
Коммандос ( "e1cib/data/Document.РеализацияТоваровУслуг" );
Здесь ( "Реализация *" );

// Определяем даты
дата = Дата ( Взять ( "!Дата" ) );
плохая = Формат ( дата - 86400, "DLF=D" );
хорошая = Формат ( дата + 86400, "DLF=D" );

// Вводим плохую дату
Нажать ( "!ТоварыДобавить" );
Установить ( "!ТоварыДатаСогласования", плохая );

// Проводим документ
Нажать ( "!ФормаПровести" );
если ( НайтиСообщения ( "Дата согласования*" ).Количество () = 0 ) тогда
	Стоп ( "Должна быть ошибка неверной установки даты согласования" );
конецесли;

// Вводим хорошую дату
товары = Получить ( "!Товары" );
Установить ( "!ТоварыДатаСогласования [1]", хорошая, товары );

// Проводим документ
Нажать ( "!ФормаПровести" );
если ( НайтиСообщения ( "Дата согласования*" ).Количество () > 0 ) тогда
	Стоп ( "Не должно быть ошибок неверной установки даты согласования" );
конецесли;

// Закрываем документ не сохраняя
Закрыть ();
Нажать ( "Нет", "1*" );

Комментарии по коду:
  1. В этом тесте у нас нет эталонных данных, мы их формируем кодом сценария. Например, правильную и неправильную дату мы вычисляем согласно дате документа, которая нигде в свою очередь не хранится, а присваивается системой автоматически при вводе нового документа
  2. Может показаться, что для простого тестирования корректности даты, сценарий получился большой. В данном конкретном случае, возможно и не стоило создавать специальный тест для такой задачи, ведь мы всё равно следующими шагами будем проверять формирование движений по регистру, и если что-то пойдет не так, оно пойдет не так и в следующих тестах. Однако, с точки зрения подачи информации, и постепенного раскрытия темы, я посчитал нужным наличие этого сценария. Кроме этого, всё-таки этот сценарий теперь у нас навсегда, мы можем запускать еще ночью, и мы можем быть спокойны, что делая какие-то глобальные замены мы не повредим (например) текст сообщения об ошибке
  3. В этом тесте нет всей трехступенчатой структуры, которую я описывал ранее (см. здесь), но здесь это и не нужно в силу простоты сценария и как следствие – скорости его прогона

Настало время запуска сценария. Для этого убедитесь, что в конфигураторе настроен запуск приложения в режиме клиента тестирования:

(эта настройка сохраняется и её не придётся устанавливать каждый раз)
Запускаем приложение, затем переключаемся в Тестер, устанавливаем наш сценарий основным и нажимаем кнопку Запустить:

Если вы на момент запуска теста еще не реализовали процедуру проверки даты согласования, наш тест упадет.
Реализуем теперь в модуле реализации, в обработчике ОбработкаПроверкиЗаполнения, такую проверку:
пустая = Дата ( 1, 1, 1 );
для каждого строка из Товары цикл
	согласование = строка.ДатаСогласования;
	если ( согласование = пустая ) тогда
		продолжить;
	иначеесли ( согласование < Дата ) тогда
		Сообщить ( "Дата согласования установлена неверно" );
		Отказ = истина;
	конецесли;
конеццикла;

Запускаем приложения на выполнение, переключаемся в Тестер и жмем там F5. В результате чего, тест должен завершиться без ошибок.

Этап 2


Переходим ко второму запланированному рывку. В таблице ниже состыкуем работу по кодированию и тестированию:
Программирование Тестирование
В Конфигураторе:
  1. Добавляем регистр сведений Согласование
  2. Дорабатываем обработку проведения для формирования записей в регистр
В Тестере:
Создадим тест ПродажаСДатойСогласования, где:
  1. Определим окружение:
    1. Наименование Поставщика
    2. Наименование Покупателя
    3. Товар1 с ценой и количеством
    4. Товар2 с ценой, количеством и датой согласования
  2. Создадим окружение
    1. Создадим поставщика, покупателя и товары
    2. Сделаем поступление
    3. Создадим и проведем реализацию
  3. Целевая часть
    1. Откроем список реализаций и найдем там созданную реализацию
    2. Откроем реализацию и проведем
    3. Откроем регистр согласований и проверим наличие нашей записи
Этот тест сложнее, потому что перед проверкой целевой части, нам потребуется создать окружение. Напомню, что окружение будет создано только один раз, при первом запуске сценария, а затем, будет прогоняться только его целевая часть. Этот трюк будет понятен из кода сценария, который будет разработан в конце этого этапа.

Для создания окружения, нам будут нужны следующие объекты:
  1. Поставщик
  2. Товар
  3. Склад
  4. Покупатель
  5. Поступление

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

Метод для создания поставщика


Практически все тест-методы имеют одинаковую структуру: они создаются в группе тестов, соответствующей прикладной модели объектов 1С. Это не жесткое правило, в своих проектах вы можете создавать тест-методы по-своему, но предлагаемый подход уже себя зарекомендовал.
Создадим первый тест-метод. В дереве сценариев, в папке Справочники, создадим папку Поставщики, внутри неё метод Создать, а следом метод Параметры, с тем, чтобы получилась такая структура:

В результате, у нас получился метод Создать и подчиненный ему метод Параметры. Для того, чтобы в каком-нибудь сценарии воспользоваться этим методом, нам достаточно будет написать такой код:
// Вначале вызовем тест-метод для получения параметров
параметры = Вызвать ( "Справочники.Поставщики.Создать.Параметры" );

// Заполняем параметры
параметры.Наименование = "ТОО Ромашка";

// Запускаем главный метод передавая ему параметры
Вызвать ( "Справочники.Поставщики.Создать", параметры );

Справку по всем функциям Тестера см. в разделе API Тестера.
Для создания сценариев-методов, в форме элемента сценария, на вкладке Свойства, нужно назначить соответствующий тип:

Подробнее о дереве и свойствах сценариев см. здесь.
Перейдем в редактор кода сценария Параметры, введем следующий текст:
// Параметры создания нового элемента
//
// Возврат:
// Структура параметров

СтандартнаяОбработка = ложь;

п = новый Структура ();
п.Вставить ( "Наименование" );
п.Вставить ( "Доступ", "Поставщики" );

возврат п;

Затем, откроем редактор сценария Справочники.Поставщики.Создать и введем:
// Создает нового поставщика по переданным параметрам
//
// Параметры:
// Структура параметров, полученная методом "Справочники.Поставщики.Создать.Параметры"
//
// Возврат:
// Код созданного поставщика

Коммандос ( "e1cib/data/Справочник.Партнеры" );

форма = Здесь ( "Партнер (соз*" );

// *********************************************
// Заполнение полей
// *********************************************

Установить ( "!НаименованиеПолноеКомпания", _.Наименование );
Нажать ( "Поставщик" );
Установить ( "!ГруппаДоступа", _.Доступ );

// *********************************************
// Запись, получение кода
// *********************************************

Нажать ( "!ФормаЗаписать" );
код = Взять ( "!Код" );

// *********************************************
// Создание контрагента
// *********************************************

Нажать ( "Контрагенты", ПолучитьСсылки () );
список = Здесь ( "Контрагенты*" );

Нажать ( "!ФормаСоздать" );
Здесь ( "Контрагент (соз*" );
Установить ( "!Наименование", _.Наименование );
Нажать ( "!ФормаЗаписатьИЗакрыть" );
Закрыть ( список );

возврат код;

Параметризированный метод создания нового поставщика готов. Перейдем к созданию остальных методов.

Метод для создания покупателя


Проделаем в дереве сценариев практически те же действия для разработки сценария создания покупателя:

Затем введем код сценариев, Справочники.Покупатели.Создать.Параметры:
// Параметры создания нового элемента
//
// Возврат:
// Структура параметров

СтандартнаяОбработка = ложь;

п = новый Структура ();
п.Вставить ( "Наименование" );
п.Вставить ( "Доступ", "Прочие" );
п.Вставить ( "Сделка", "Произвольная продажа" );

возврат п;

и Справочники.Покупатели.Создать:
// Создает нового покупателя по переданным параметрам
//
// Параметры:
// Структура параметров, полученная методом "Справочники.Покупатели.Создать.Параметры"
//
// Возврат:
// Код созданного поставщика

Коммандос ( "e1cib/data/Справочник.Партнеры" );

форма = Здесь ( "Партнер (соз*" );

// *********************************************
// Заполнение полей
// *********************************************

Установить ( "!НаименованиеПолноеКомпания", _.Наименование );
Нажать ( "!Клиент" );
Установить ( "!ГруппаДоступа", _.Доступ );

// *********************************************
// Запись, получение кода
// *********************************************

Нажать ( "!ФормаЗаписать" );
код = Взять ( "!Код" );

// *********************************************
// Создание контрагента
// *********************************************

Нажать ( "Контрагенты", ПолучитьСсылки () );
список = Здесь ( "Контрагенты*" );

Нажать ( "!ФормаСоздать" );
Здесь ( "Контрагент (соз*" );
Установить ( "!Наименование", _.Наименование );
Нажать ( "!ФормаЗаписатьИЗакрыть" );
Закрыть ( список );

возврат код;

Метод для создания товаров



Справочники.Номенклатура.Создать.Параметры
// Параметры создания нового элемента
//
// Возврат:
// Структура параметров

п = новый Структура ();
п.Вставить ( "Наименование" );
п.Вставить ( "Единица", "шт" );
п.Вставить ( "Вид", "Товар" );
п.Вставить ( "СтавкаНДС", "18%" );
возврат п;

Справочники.Номенклатура.Создать
// Создает новую номенклатуру по переданным параметрам
//
// Параметры:
// Структура параметров, полученная методом "Справочники.Номенклатура.Создать.Параметры"
//
// Возврат:
// Код созданной номенклатуры

Коммандос ( "e1cib/data/Справочник.Номенклатура" );

форма = Здесь ( "Номенклатура (соз*" );

// *********************************************
// Заполнение полей
// *********************************************

//установитьВид ( форма, _.Вид );
Установить ( "!ВидНоменклатурыОбязательныеПоля", _.Вид );
Установить ( "Рабочее наименование", _.Наименование );
Установить ( "Единица хранения", _.Единица );
Установить ( "Ставка НДС", _.СтавкаНДС );

// *********************************************
// Запись, получение кода и завершение
// *********************************************

Нажать ( "!ФормаЗаписать" );
код = Взять ( "!Код" );

Закрыть ();

возврат код;

Метод для создания складов



Справочники.Склады.Создать.Параметры
// Параметры создания нового элемента
//
// Возврат:
// Структура параметров

п = новый Структура ();
п.Вставить ( "Наименование" );
возврат п;

Справочники.Склады.Создать
// Создает новый склад по переданным параметрам
//
// Параметры:
// Структура параметров, полученная методом "Справочники.Склады.Создать.Параметры"

Коммандос ( "e1cib/data/Справочник.Склады" );

форма = Здесь ( "* (соз*" );

// *********************************************
// Заполнение полей
// *********************************************

Установить ( "!Наименование", _.Наименование );
Установить ( "!ТипСкладаОптовый", "Оптовый склад" );

// *********************************************
// Запись, получение кода и завершение
// *********************************************

Нажать ( "!ФормаЗаписатьИЗакрыть" );

Метод для создания документа ПоступлениеТоваровУслуг



Документы.ПоступлениеТоваровУслуг.Создать.Параметры
// Параметры создания нового документа
//
// Возврат:
// Структура параметров

СтандартнаяОбработка = ложь;

п = новый Структура ();
п.Вставить ( "Дата" );
п.Вставить ( "Организация", __.Организация );
п.Вставить ( "Склад" );
п.Вставить ( "Поставщик" );
п.Вставить ( "Товары", новый Массив ); // Массив Документы.ЗаказПоставщику.Создать.Строка
п.Вставить ( "ОставитьОткрытым", ложь ); // Закрыть или оставить на экране форму документа

возврат п;

Документы.ПоступлениеТоваровУслуг.Создать.Товар
// Параметры создания новой строки документа
//
// Возврат:
// Структура параметров

СтандартнаяОбработка = ложь;

п = новый Структура ();
п.Вставить ( "Товар" ); // Наименование или Код товара
п.Вставить ( "Количество" );
п.Вставить ( "Цена" );

возврат п;

Документы.ПоступлениеТоваровУслуг.Создать
// Создает новый документ Поступление товаров и услуг по переданным параметрам
//
// Параметры:
// Структура параметров, полученная методом "Документы.ПоступлениеТоваровУслуг.Создать.Параметры"
//
// Возврат:
// Номер созданного заказа

Коммандос ( "e1cib/data/Документ.ПоступлениеТоваровУслуг" );

форма = Здесь ( "Поступление товаров и услуг (соз*" );

// *********************************************
// Заполнение шапки
// *********************************************

дата = _.Дата;
если ( дата <> неопределено ) тогда
	Установить ( "!Дата", Формат ( дата, "DLF=DT" ) );
конецесли;

Установить ( "!Партнер", _.Поставщик );
Установить ( "!Организация", _.Организация );

значение = _.Склад;
если ( значение <> неопределено ) тогда
	Установить ( "!Склад", значение );
конецесли;

// *********************************************
// Заполнение товаров
// *********************************************

таблица = Фокус ( "!Товары" );
для каждого товар из _.Товары цикл

	Нажать ( "!ТоварыДобавить", таблица );
	Установить ( "!ТоварыНоменклатура", товар.Товар );
	Установить ( "!ТоварыКоличествоУпаковок", товар.Количество );
	Установить ( "!ТоварыЦена", товар.Цена );

конеццикла;

// *********************************************
// Запись документа, получение номера
// *********************************************

Нажать ( "!ФормаЗаписать" );
номер = Взять ( "!Номер" );

// *********************************************
// Проведение документа и возврат номера
// *********************************************

Нажать ( "!ФормаПровести" );

закрыть = не _.ОставитьОткрытым;
если ( закрыть ) тогда
	Закрыть ();
конецесли;

возврат номер;

Метод для создания документа РеализацияТоваровУслуг



Документы.РеализацияТоваровУслуг.Создать.Параметры
// Параметры создания нового документа
//
// Возврат:
// Структура параметров

СтандартнаяОбработка = ложь;

п = новый Структура ();
п.Вставить ( "Дата" );
п.Вставить ( "Организация", __.Организация );
п.Вставить ( "Склад" );
п.Вставить ( "Покупатель" );
п.Вставить ( "Товары", новый Массив ); // Массив Документы.ЗаказПоставщику.Создать.Строка
п.Вставить ( "ОставитьОткрытым", ложь ); // Закрыть или оставить на экране форму документа
п.Вставить ( "Комментарий" );

возврат п;

Документы.РеализацияТоваровУслуг.Создать.Товар
// Параметры создания новой строки документа
//
// Возврат:
// Структура параметров

СтандартнаяОбработка = ложь;

п = новый Структура ();
п.Вставить ( "Товар" ); // Наименование или Код товара
п.Вставить ( "Количество" );
п.Вставить ( "Цена" );
п.Вставить ( "Согласование" );

возврат п;

Документы.РеализацияТоваровУслуг.Создать
// Создает новый документ Реализация товаров и услуг по переданным параметрам
//
// Параметры:
// Структура параметров, полученная методом "Документы.РеализацияТоваровУслуг.Создать.Параметры"
//
// Возврат:
// Номер созданного заказа

Коммандос ( "e1cib/data/Документ.РеализацияТоваровУслуг" );

форма = Здесь ( "Реализация товаров и услуг (соз*" );

// *********************************************
// Заполнение шапки
// *********************************************

дата = _.Дата;
если ( дата <> неопределено ) тогда
	Установить ( "!Дата", Формат ( дата, "DLF=DT" ) );
конецесли;

Установить ( "!Партнер", _.Покупатель );
Установить ( "!Организация", _.Организация );

значение = _.Склад;
если ( значение <> неопределено ) тогда
	Установить ( "!Склад", значение );
конецесли;

значение = _.Комментарий;
если ( значение <> неопределено ) тогда
	Установить ( "!Комментарий", значение );
конецесли;

// *********************************************
// Заполнение товаров
// *********************************************

таблица = Фокус ( "!Товары" );
никогда = Дата ( 1, 1, 1 );
для каждого товар из _.Товары цикл

	Нажать ( "!ТоварыДобавить", таблица );
	Установить ( "!ТоварыНоменклатура", товар.Товар );
	Установить ( "!ТоварыКоличествоУпаковок", товар.Количество );
	Очистить ( "!ТоварыВидЦены" );
	Установить ( "!ТоварыЦена", товар.Цена );
	согласование = товар.Согласование;
	если ( согласование <> никогда ) тогда
		Установить ( "!ТоварыДатаСогласования", Формат ( согласование, "DLF=D" ) );
	конецесли;

конеццикла;

// *********************************************
// Запись документа, получение номера
// *********************************************

Нажать ( "!ФормаЗаписать" );
номер = Взять ( "!Номер" );

// *********************************************
// Проведение документа и возврат номера
// *********************************************

Нажать ( "!ФормаПровести" );

закрыть = не _.ОставитьОткрытым;
если ( закрыть ) тогда
	Закрыть ();
конецесли;

возврат номер;

На этом, подготовка методов закончена.

Создание теста ПродажаСДатойСогласования


Переходим к написанию конечного теста для второго этапа. Для этого, в дереве сценариев, создадим такой тест:

Сделаем его основным, и внесем следующий код:
// Сценарий:
// - Создадим окружение в результате которого будет существовать документ
//  реализация с двумя товарами, для второго товара указана дата согласования
// - Откроем список реализаций
// - Найдем созданную реализацию и (пере)проведем её
// - Откроем регистр согласований и найдем там запись, которую должна была сделать реализация
// - Проверим правильность сформированной записи

Вызвать ( "Общее.Начало" );
ЗакрытьВсё ();

ид = "25C555B6";
тест = окружение ( ид );
создатьОкружение ( тест );

// ************************************
// Целевой сценарий
// ************************************

// Откроем реализации
Коммандос ( "e1cib/list/Документ.РеализацияТоваровУслуг" );
Здесь ( "Документы реализации" );

// Перейдем к нашему документу
Кстроке ( "!СписокРеализацииТоваровУслуг", "Клиент", тест.Покупатель );

// Проведем
Нажать ( "!СписокРеализацииТоваровУслугПровести" );

// Откроем регистр согласований
Коммандос ( "e1cib/list/РегистрСведений.Согласование" );
Здесь ( "Согласование" );

// Перейдем к записи
ок = Кстроке ( "!Список", "Клиент", тест.Покупатель );
если ( не ок ) тогда
	Стоп ( "После проведения реализации не сформировались записи в регистре Согласование" );
конецесли;

// Проверим правильность сформированной записи.
// Выведем стандартный список
Нажать ( "!ФормаВывестиСписок" );
Здесь ( "Вывести список" );

// Включим флажок "Только выделенные"
было = Взять ( "!OnlySelectedElement" );
если ( было = "Нет" ) тогда
	Нажать ( "!OnlySelectedElement" );
конецесли;

// Укажем, что выводить будем в табличный документ
Подобрать ( "!DocumentTypeElement", "Табличный документ" );

// Формируем мини-отчет
Нажать ( "!Ok" );

// Просим тестер проверить этот отчет
Здесь ( "Список" );
ПроверитьШаблон ( "" );

// *********************************************
// Процедуры
// *********************************************

Функция окружение ( ИД )

	дата = ТекущаяДата ();
	п = новый Структура ();
	п.Вставить ( "ИД", ИД );
	п.Вставить ( "Дата", дата );
	п.Вставить ( "Согласование", ТекущаяДата () + 172800 ); // Через два дня
	п.Вставить ( "ДатаПоступления", дата - 86400 );
	п.Вставить ( "Поставщик", "Поставщик " + ИД );
	п.Вставить ( "Покупатель", "Покупатель " + ИД );
	п.Вставить ( "Склад", "Склад " + ИД );
	п.Вставить ( "Товары", определитьТовары ( п ) );
	возврат п;

КонецФункции

Функция определитьТовары ( Тест )

	ид = Тест.ИД;
	список = новый Массив ();
	список.Добавить ( новыйТовар ( "Товар1 " + ид, 5, 150, 250 ) );
	список.Добавить ( новыйТовар ( "Товар2 " + ид, 15, 250, 350, Тест.Согласование ) );
	возврат список;

КонецФункции

Функция новыйТовар ( Товар, Количество, Стоимость, Цена, Согласование = неопределено )

	п = новый Структура ();
	п.Вставить ( "Товар", Товар );
	п.Вставить ( "Количество", Количество );
	п.Вставить ( "Стоимость", Стоимость );
	п.Вставить ( "Цена", Цена );
	п.Вставить ( "Согласование", Согласование );
	возврат п;

КонецФункции

Процедура создатьОкружение ( Тест )

	ид = Тест.ИД;

	// *****************************************************
	// Проверяем, нужно ли создавать данные для тестирования
	// *****************************************************

	если ( Вызвать ( "Общее.ОкружениеСоздано", ид ) ) тогда
		возврат;
	конецесли;

	// ******************************************************************
	// Проверим, чтобы в настройках были отключены соглашения с клиентами
	// ******************************************************************

	Коммандос ( "e1cib/command/Обработка.ПанельАдминистрированияУТ.Команда.Продажи" );
	Здесь ( "Продажи" );
	опция = Взять ( "!ИспользованиеСоглашенийСКлиентами" );
	если ( опция <> "Не использовать" ) тогда
		Стоп ( "В целях обучения, этап создания соглашений с клиентами в сценариях не предусмотрен.
		|В окне, которые сейчас на экране с тестируемым приложением, откройте группу
		|Оптовые продажи и в поле Использование соглашений с клиентами, выберите Не использовать" );
	конецесли;

	// ******************
	// Создаем склад
	// ******************

	п = Вызвать ( "Справочники.Склады.Создать.Параметры" );
	п.Наименование = Тест.Склад;
	Вызвать ( "Справочники.Склады.Создать", п );

	// ******************
	// Создаем поставщика
	// ******************

	п = Вызвать ( "Справочники.Поставщики.Создать.Параметры" );
	п.Наименование = Тест.Поставщик;
	Вызвать ( "Справочники.Поставщики.Создать", п );

	// ******************
	// Создаем покупателя
	// ******************

	п = Вызвать ( "Справочники.Покупатели.Создать.Параметры" );
	п.Наименование = Тест.Покупатель;
	Вызвать ( "Справочники.Покупатели.Создать", п );

	// **************
	// Создаем товары
	// **************

	для каждого товар из Тест.Товары цикл
		п = Вызвать ( "Справочники.Номенклатура.Создать.Параметры" );
		п.Наименование = товар.Товар;
		Вызвать ( "Справочники.Номенклатура.Создать", п );
	конеццикла;

	// *******************
	// Создаем поступление
	// *******************

	товары = новый Массив ();
	для каждого позиция из тест.Товары цикл
		товар = Вызвать ( "Документы.ПоступлениеТоваровУслуг.Создать.Товар" );
		товар.Товар = позиция.Товар;
		товар.Количество = позиция.Количество;
		товар.Цена = позиция.Стоимость;
		товары.Добавить ( товар );
	конеццикла;

	п = Вызвать ( "Документы.ПоступлениеТоваровУслуг.Создать.Параметры" );
	п.Товары = товары;
	п.Дата = тест.ДатаПоступления;
	п.Поставщик = тест.Поставщик;
	п.Склад = тест.Склад;

	Вызвать ( "Документы.ПоступлениеТоваровУслуг.Создать", п );

	// *******************
	// Создаем реализацию
	// *******************

	товары = новый Массив ();
	для каждого позиция из тест.Товары цикл
		товар = Вызвать ( "Документы.РеализацияТоваровУслуг.Создать.Товар" );
		товар.Товар = позиция.Товар;
		товар.Количество = позиция.Количество;
		товар.Цена = позиция.Цена;
		товар.Согласование = позиция.Согласование;
		товары.Добавить ( товар );
	конеццикла;

	п = Вызвать ( "Документы.РеализацияТоваровУслуг.Создать.Параметры" );
	п.Товары = товары;
	п.Дата = тест.Дата;
	п.Покупатель = тест.Покупатель;
	п.Склад = тест.Склад;

	Вызвать ( "Документы.РеализацияТоваровУслуг.Создать", п );

	// ********************************
	// Регистрируем созданное окружение
	// ********************************

	Вызвать ( "Общее.ШтампОкружения", ид );

КонецПроцедуры

Так как это наш главный сценарий для второго этапа, разберем его подробней.
Сценарий начинается с Вызвать ( "Общее.Начало" );. Я делаю этот вызов для задания глобальных переменных и выполнения общих задач перед началом работы любого теста. Посмотрим, что внутри теста Общее.Начало:

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

СтандартнаяОбработка = ложь;

если ( __ = неопределено ) тогда
	__ = новый Структура ();
иначе
	возврат;
конецесли;

// *************************************************************************
// Используем переменную "__" для задания произвольных глобальных параметров
// *************************************************************************

__.Вставить ( "ПроверятьЛогику", истина );
__.Вставить ( "ЛокальнаяВалюта", "РУБ" );

если ( ИмяПриложения = "УТ11" ) тогда
	__.Вставить ( "Организация", "Торговый дом ""Комплексный""" );
конецесли;

// ***********************************************
// Выполняем подключение к тестируемому приложению
// ***********************************************

Подключить ();

Если вы используете демо-базу Тестера, тогда тест Общее.Начало там уже есть. Откройте этот тест и внесите в него изменения, в частности, нам нужна вот эта часть модуля:
если ( ИмяПриложения = "УТ11" ) тогда
	__.Вставить ( "Организация", "Торговый дом ""Комплексный""" );
конецесли;

Работает это всё следующим образом. Когда тест ПродажаСДатойСогласования будет запущен, он в свою очередь запустит тест Общее.Начало, который определит несколько переменных и подключится к тестируемому приложению. Все тесты, которые будут запускаться дальше, уже будут знать, что в переменной “__” хранится имя организации, локальная валюта и другие параметры. Это своего рода глобальное параметризирование тестируемой среды (использование двойного подчеркивания в качестве глобальной переменной выбрано для независимости от варианта языка 1С).

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

Следующие три строки определяют и создают окружение (см. Структура сценария):
// Переменная "ид" задает идентификатор окружения.
// Идентификатор окружения может быть любой, но я рекомендую использовать
// указанный ниже шаблон алфавитно-цифровой последовательности,
// которая может быть автоматически сгенерирована в редакторе модуля
// по комбинации клавиш: Ctrl+Shit+I
// Таким образом, чтобы пересоздать окружение, нужно будет просто сменить "ид".
// Почему это так - будет понятно ниже.
ид = "25C555B6";

// В переменную "тест" функция окружение () вернет структуру с параметрами
// нашего теста. Эти параметры и есть определение окружения.
тест = окружение ( ид );

// Процедура создатьОкружение () выполняет создание необходимых тестовых данных.
// Внутри этой процедуры будут вызываться тесты-методы, которые мы создавали в разделах выше.
создатьОкружение ( тест );

Посмотрим, что происходит внутри функции окружение () :
Функция окружение ( ИД )

	дата = ТекущаяДата ();
	п = новый Структура ();
	п.Вставить ( "ИД", ИД );
	п.Вставить ( "Дата", дата );
	п.Вставить ( "Согласование", ТекущаяДата () + 172800 ); // Через два дня
	п.Вставить ( "ДатаПоступления", дата - 86400 );
	п.Вставить ( "Поставщик", "Поставщик " + ИД );
	п.Вставить ( "Покупатель", "Покупатель " + ИД );
	п.Вставить ( "Склад", "Склад " + ИД );
	п.Вставить ( "Товары", определитьТовары ( п ) );
	возврат п;

КонецФункции

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

Тестовые данные создаются, а значит, они должны быть уникальны. В противном случае, будут создаваться дубли объектов, которые практически, будет невозможно использовать. Действительно, если мы будем создавать “ТОО Ромашка" каждый раз, какую тогда "ТОО Ромашка" выбрать при создании документа? Таким образом, строки кода "Поставщик " + ИД, "Покупатель " + ИД и другие, гарантируют создание уникальных тестовых данных. Это также позволяет выполнять ваши тесты у других участников разработки, без синхронизации заготовленных шаблонов, использования предзаполненных баз данных и других внешних файлов.

Вероятно, данный подход вызывает недоумение, потому что база со временем превращается в месиво из ничего незначащих идентификаторов. Но на практике, любая база разработчика превращается в обрывки экспериментов из “порванных” данных, это лишь вопрос времени. Если вы в рабочих базах бережно храните некую информацию, на которую опирается ваша разработка – перенести эти данные в начальную базу. Если базы содержат ошибки для воспроизведения – напишите тесты, которые эти ошибки воспроизводят. Другими словами, если у вас в рабочей базе есть что-то ценное с прикладной точки зрения – напишите сценарий. Это высвободит вас от содержания хозяйства из информационных баз, даст возможность другим разработчикам проверить ваши варианты в их базах на любом этапе разработки. Весь полезный опыт, сосредоточенный в данных, лучше перевести в код сценария, что бы его всегда можно было повторить.

Другой, кажущийся тревожным момент – это глубина уникальности тестовых данных и их объем. И тут нужно отметить, что не все тестовые данные нужно делать уникальными. Уникальности требует только то, что вам нужно для тестирования (включая проверку бизнес логики). К примеру, для нашего теста нужна уникальность наименований поставщика, покупателя, склада и товаров. Нам не требуется уникальность единиц измерений, договоров, соглашений, групп товаров и других связанных данных. Рост данных также не будет проблемой. Даже если вы каждые 5 минут запускаете тест создающий окружение (что крайне маловероятно), то в течение дня таких запусков будет не больше 100, что для дневного роста базы ничтожно мало. На практике, необходимость в обнулении рабочей базы загрузкой в неё начальной, возникает примерно раз в полгода. Согласитесь, что программисты, тестирующие всё вручную, примерно с той же периодичностью выводят из строя свои рабочие базы.

Следующий участок кода, процедура создатьОкружение (). Внутри этой процедуры происходит создание тестовых данных. Создание окружения процесс не быстрый, но и не обязательный для каждого запуска сценария в процессе программирования задачи. Для того, чтобы окружение не создавалось каждый раз, можно использовать два подхода.

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

Второй – вначале процедуры, проверять, было ли окружение уже создано, и если нет, в конце процедуры фиксировать факт его создания:
Процедура создатьОкружение ( Тест )

	ид = Тест.ИД;

	// *****************************************************
	// Проверяем, нужно ли создавать данные для тестирования
	// *****************************************************

	если ( Вызвать ( "Общее.ОкружениеСоздано", ид ) ) тогда
		возврат;
	конецесли;

	//...код создания окружения...

	// ********************************
	// Регистрируем созданное окружение
	// ********************************

	Вызвать ( "Общее.ШтампОкружения", ид );

КонецПроцедуры

Для хранения идентификатора созданного окружения можно использовать “простой” объект конфигурации, куда можно легко помещать и извлекать значения. Вот что происходит в библиотечном сценарии Общее.ОкружениеСоздано :
// Описание:
// По переданному ИД определяет было ли создано окружение
//
// Параметры:
// Строка, идентификатор окружения
//
// Возврат:
// Булево, истина - окружение уже создано, ложь - окружение еще не создано

Коммандос ( "e1cib/list/Справочник.Заметки" );
Здесь ( "Заметки" );

попытка
	создано = КСтроке ( "!Список", "Тема", _ );
исключение
	создано = ложь;
конецпопытки;

Закрыть ();
возврат создано;

Таким образом, в качестве объекта для хранения окружения используется справочник Заметки.

Примечание 1: Переход к строке сделан через попытку, потому что в версиях платформы <= 8.3.9 есть ошибка работы метода перехода к строке. В случае отсутствия искомой строки, платформа ошибочно генерирует исключение вместо возврата булевого значения.

Примечание 2: Данный подход к хранению идентификатора окружения не эстетичен, напрашивается более элегантное решение, например — хранение ИД внутри самого Тестера. К сожалению, не все описываемые решения пришли в один момент, это был долгий путь из проб и ошибок, который продолжается по сей день. Концепция работы с идентификаторами доказала свою пригодность, и я обязательно рассмотрю реализацию этого функционала на уровне конфигурации Тестер в обозримом будущем.

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

// Отлаживаем по-быстрому...
п = Вызвать ( "Справочники.Склады.Создать.Параметры" );
п.Наименование = "Тестовый склад";
Вызвать ( "Справочники.Склады.Создать", п );

возврат; // Когда отладим, вырежем этот код в тело процедуры создатьОкружение ()

// Сценарий:
// - Создадим окружение в результате которого будет существовать документ
//  реализация с двумя товарами, для второго товара указана дата согласования
// - Откроем список реализаций
// - Найдем созданную реализацию и (пере)проведем её
// - Откроем регистр согласований и найдем там запись, которую должна была сделать реализация
// - Проверим правильность сформированной записи

Вызвать ( "Общее.Начало" );
ЗакрытьВсё ();

//....

Таким образом, кусок за куском логики можно отлаживать и переносить в процедуру создатьОкружение (). Обратите внимание, что для пересоздания всего окружения, вам просто нужно будет сменить ИД в начале теста.

Я описал определение и создание окружения, перейдем теперь к целевой части сценария ПродажаСДатойСогласования :
// Откроем реализации
Коммандос ( "e1cib/list/Документ.РеализацияТоваровУслуг" );
Здесь ( "Документы реализации" );

// Перейдем к нашему документу
Кстроке ( "!СписокРеализацииТоваровУслуг", "Клиент", тест.Покупатель );

// Проведем
Нажать ( "!СписокРеализацииТоваровУслугПровести" );

// Откроем регистр согласований
Коммандос ( "e1cib/list/РегистрСведений.Согласование" );
Здесь ( "Согласование" );

// Перейдем к записи
ок = Кстроке ( "!Список", "Клиент", тест.Покупатель );
если ( не ок ) тогда
	Стоп ( "После проведения реализации не сформировались записи в регистре Согласование" );
конецесли;

// Проверим правильность сформированной записи.
// Выведем стандартный список
Нажать ( "!ФормаВывестиСписок" );
Здесь ( "Вывести список" );

// Включим флажок "Только выделенные"
было = Взять ( "!OnlySelectedElement" );
если ( было = "Нет" ) тогда
	Нажать ( "!OnlySelectedElement" );
конецесли;

// Укажем, что выводить будем в табличный документ
Подобрать ( "!DocumentTypeElement", "Табличный документ" );

// Формируем мини-отчет
Нажать ( "!Ok" );

// Просим тестер проверить этот отчет
Здесь ( "Список" );
ПроверитьШаблон ( "" );

Последовательность шагов вполне очевидна, привлекает интерес часть кода проверки сформированных движений. Для этого используется стандартная функция платформы по выводу любого табличного поля в табличный документ:
Нажать ( "!ФормаВывестиСписок" );
Здесь ( "Вывести список" );
// и далее...

и возможность Тестера сравнивать табличный документ с макетом, сохраненным в сценарии:
Здесь ( "Список" );
ПроверитьШаблон ( "" );


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

Если мы сейчас запустим сценарий на выполнение, он упадет на этапе создания окружения, ведь у нас кроме логики проведения, еще нет реквизита ДатаСогласования в табличной части Товары документа РеализацияТоваровУслуг.

Займемся программированием:
— Добавим нужный реквизит:


— Выведем на форму:



— В процедуру ОбработкаПроведения модуля объекта, добавим код:
Процедура ОбработкаПроведения(Отказ, РежимПроведения)

	с = "
	|выбрать Товары.Ссылка.Менеджер как Менеджер, Товары.Ссылка.Партнер как Клиент,
	|	Товары.Номенклатура как Товар, Товары.Количество как Количество, Товары.Сумма как Сумма,
	|	Товары.КодСтроки как Строка, Товары.ДатаСогласования как ДатаСогласования, &Ссылка как Реализация
	|из Документ.РеализацияТоваровУслуг.Товары как Товары
	|	//
	|	// Согласованные
	|	//
	|	левое соединение РегистрСведений.Согласование как Согласованные
	|	по Согласованные.Строка = Товары.КодСтроки
	|где Товары.Ссылка = &Ссылка
	|и Товары.ДатаСогласования <> датавремя ( 1, 1, 1 )
	|и не isnull ( Согласованные.Отработано, ложь )
	|";
	з = новый Запрос ( с );
	з.УстановитьПараметр ( "Ссылка", Ссылка );
	таблица = з.Выполнить ().Выгрузить ();
	для каждого строка из таблица цикл
		запись = РегистрыСведений.Согласование.СоздатьМенеджерЗаписи ();
		ЗаполнитьЗначенияСвойств ( запись, строка );
		запись.Записать ();
	конеццикла;

//...далее идет типовой код конфигурации

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

Выделите в УТ11 этот макет и скопируйте в Тестер на вкладку Шаблон сценария ПродажаСДатойСогласования. Затем, в Тестере, выделите значимую область, вызовите правым кликом контекстное меню и укажите Отметить область:

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

Проделаем так с каждым полем, чтобы получилась в итоге следующая картина:

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

Этап 3


В этом рывке нам нужно разработать форму списка для регистра Согласование с фильтрами по клиенту, товару и менеджеру.
В таблице ниже состыкуем работу по кодированию и тестированию:
Программирование Тестирование
В Конфигураторе:
  1. Добавляем форму списка в регистр сведений Согласование
  2. Создаем три реквизита формы соответствующих типов, выкладываем их на форму
  3. Настраиваем свойства и обработчики
  4. Прописываем код обработчиков фильтрации
В Тестере:
Создадим тест ФильтрацияСписка, где:
  1. Определим окружение:
    1. Наименование Поставщика
    2. Наименование Покупателя
    3. Наименование Потенциального клиента, по которому продаж нет
    4. Товар с датой согласования
  2. Создадим окружение
    1. Создадим поставщика, покупателей и товар
    2. Сделаем поступление
    3. Создадим и проведем реализацию
  3. Целевая часть
    1. Откроем список регистра Согласование
    2. Установим отбор по покупателю, проверим список
    3. Установим отбор по потенциальному клиенту, проверим пустой список
    4. Установим/снимем отборы по товару и менеджеру

Для разнообразия, в этот раз начнем с программирования, а затем напишем тест.


— Добавим основную форму списка, добавим реквизиты формы, расположим на форме:


— В модуле формы списка, определим обработчики полей фильтрации:
&НаКлиенте
Процедура КлиентФильтрПриИзменении ( Элемент )

	ОбщегоНазначенияКлиентСервер.УстановитьЭлементОтбораДинамическогоСписка(Список,
		"Клиент",
		КлиентФильтр,
		ВидСравненияКомпоновкиДанных.Равно,
		,
		ЗначениеЗаполнено(КлиентФильтр));

КонецПроцедуры

&НаКлиенте
Процедура ТоварФильтрПриИзменении ( Элемент )

	ОбщегоНазначенияКлиентСервер.УстановитьЭлементОтбораДинамическогоСписка(Список,
		"Товар",
		ТоварФильтр,
		ВидСравненияКомпоновкиДанных.Равно,
		,
		ЗначениеЗаполнено(ТоварФильтр));

КонецПроцедуры

&НаКлиенте
Процедура МенеджерФильтрПриИзменении ( Элемент )

	ОбщегоНазначенияКлиентСервер.УстановитьЭлементОтбораДинамическогоСписка(Список,
		"Менеджер",
		МенеджерФильтр,
		ВидСравненияКомпоновкиДанных.Равно,
		,
		ЗначениеЗаполнено(МенеджерФильтр));

КонецПроцедуры

На этом этапе с программированием всё. Перейдем к разработке теста. Для этого, как и в прошлые разы, в дереве сценариев, создадим новый сценарий. Установим этот сценарий основным, и назовем его ФильтрацияСписка :

Введем следующий код сценария:
// Сценарий:
// - Создадим окружение в результате которого будет существовать проведенный документ
//  реализация с товаром, для которого указана дата согласования
// - Откроем список Согласование
// - Установим отбор по покупателю, убедимся, что в списке есть отфильтрованная запись
// - Установим отбор по потенциальному клиенту, убедимся, что список пуст
// - Очистим фильтры по товару и менеджеру, проверим что они отрабатывают,
//  допускаем, что если нет ошибок тогда фильтрация происходит

Вызвать ( "Общее.Начало" );
ЗакрытьВсё ();

ид = "25CB69CB";
тест = окружение ( ид );
создатьОкружение ( тест );

// ************************************
// Целевой сценарий
// ************************************

// Откроем регистр согласований
Коммандос ( "e1cib/list/РегистрСведений.Согласование" );
Здесь ( "Согласование" );

// Установим фильтр по покупателю
покупатель = тест.Покупатель;
Ввести ( "!КлиентФильтр", покупатель );

// Найдем его в списке
ок = Кстроке ( "!Список", "Клиент", покупатель );
если ( не ок ) тогда
	Стоп ( "В списке не найдена отфильтрованная по клиенту запись" );
конецесли;

// Установим фильтр по потенциальному клиенту
Ввести ( "!КлиентФильтр", тест.ПотенциальныйКлиент );

// Список должен быть пустой
пусто = 0 = Вызвать ( "Таблица.Количество", Получить ( "!Список" ) );
если ( не пусто ) тогда
	Стоп ( "Список должен быть пуст" );
конецесли;
Очистить ( "!КлиентФильтр" );

// Установим и очистим фильтр по товару
Ввести ( "!ТоварФильтр", тест.Товары [ 0 ].Товар );
Очистить ( "!ТоварФильтр" );

// Очистим фильтр по менеджеру
Очистить ( "!МенеджерФильтр" );

// *********************************************
// Процедуры
// *********************************************

Функция окружение ( ИД )

	дата = ТекущаяДата ();
	п = новый Структура ();
	п.Вставить ( "ИД", ИД );
	п.Вставить ( "Дата", дата );
	п.Вставить ( "Согласование", дата + 172800 ); // Через два дня
	п.Вставить ( "ДатаПоступления", дата - 86400 );
	п.Вставить ( "Поставщик", "Поставщик " + ИД );
	п.Вставить ( "Покупатель", "Покупатель " + ИД );
	п.Вставить ( "ПотенциальныйКлиент", "Потенциальный клиент " + ИД );
	п.Вставить ( "Склад", "Склад " + ИД );
	п.Вставить ( "Товары", определитьТовары ( п ) );
	возврат п;

КонецФункции

Функция определитьТовары ( Тест )

	ид = Тест.ИД;
	список = новый Массив ();
	список.Добавить ( новыйТовар ( "Товар1 " + ид, 15, 250, 350, Тест.Согласование ) );
	возврат список;

КонецФункции

Функция новыйТовар ( Товар, Количество, Стоимость, Цена, Согласование = неопределено )

	п = новый Структура ();
	п.Вставить ( "Товар", Товар );
	п.Вставить ( "Количество", Количество );
	п.Вставить ( "Стоимость", Стоимость );
	п.Вставить ( "Цена", Цена );
	п.Вставить ( "Согласование", Согласование );
	возврат п;

КонецФункции

Процедура создатьОкружение ( Тест )

	ид = Тест.ИД;

	// *****************************************************
	// Проверяем, нужно ли создавать данные для тестирования
	// *****************************************************

	если ( Вызвать ( "Общее.ОкружениеСоздано", ид ) ) тогда
		возврат;
	конецесли;

	// ******************
	// Создаем склад
	// ******************

	п = Вызвать ( "Справочники.Склады.Создать.Параметры" );
	п.Наименование = Тест.Склад;
	Вызвать ( "Справочники.Склады.Создать", п );

	// ******************
	// Создаем поставщика
	// ******************

	п = Вызвать ( "Справочники.Поставщики.Создать.Параметры" );
	п.Наименование = Тест.Поставщик;
	Вызвать ( "Справочники.Поставщики.Создать", п );

	// *******************
	// Создаем покупателей
	// *******************

	п = Вызвать ( "Справочники.Покупатели.Создать.Параметры" );
	п.Наименование = Тест.Покупатель;
	Вызвать ( "Справочники.Покупатели.Создать", п );

	п.Наименование = Тест.ПотенциальныйКлиент;
	Вызвать ( "Справочники.Покупатели.Создать", п );

	// **************
	// Создаем товары
	// **************

	для каждого товар из Тест.Товары цикл
		п = Вызвать ( "Справочники.Номенклатура.Создать.Параметры" );
		п.Наименование = товар.Товар;
		Вызвать ( "Справочники.Номенклатура.Создать", п );
	конеццикла;

	// *******************
	// Создаем поступление
	// *******************

	товары = новый Массив ();
	для каждого позиция из тест.Товары цикл
		товар = Вызвать ( "Документы.ПоступлениеТоваровУслуг.Создать.Товар" );
		товар.Товар = позиция.Товар;
		товар.Количество = позиция.Количество;
		товар.Цена = позиция.Стоимость;
		товары.Добавить ( товар );
	конеццикла;

	п = Вызвать ( "Документы.ПоступлениеТоваровУслуг.Создать.Параметры" );
	п.Товары = товары;
	п.Дата = тест.ДатаПоступления;
	п.Поставщик = тест.Поставщик;
	п.Склад = тест.Склад;

	Вызвать ( "Документы.ПоступлениеТоваровУслуг.Создать", п );

	// *******************
	// Создаем реализацию
	// *******************

	товары = новый Массив ();
	для каждого позиция из тест.Товары цикл
		товар = Вызвать ( "Документы.РеализацияТоваровУслуг.Создать.Товар" );
		товар.Товар = позиция.Товар;
		товар.Количество = позиция.Количество;
		товар.Цена = позиция.Цена;
		товар.Согласование = позиция.Согласование;
		товары.Добавить ( товар );
	конеццикла;

	п = Вызвать ( "Документы.РеализацияТоваровУслуг.Создать.Параметры" );
	п.Товары = товары;
	п.Дата = тест.Дата;
	п.Покупатель = тест.Покупатель;
	п.Склад = тест.Склад;

	Вызвать ( "Документы.РеализацияТоваровУслуг.Создать", п );

	// ********************************
	// Регистрируем созданное окружение
	// ********************************

	Вызвать ( "Общее.ШтампОкружения", ид );

КонецПроцедуры

Комментарии по коду теста:
  1. Фактически, этот сценарий является упрощенной версией сценария, который мы создали на предыдущем этапе.
  2. Я опустил в этом сценарии проверку настройки соглашений с клиентами, в реальном проекте вы эту настройку выделите в отдельный тест проверки окружения и не будете включать в каждый ваш тест.
  3. В этом сценарии используется библиотечный тест Вызвать ( "Таблица.Количество", Получить ( "! Список" ) ). Хороший пример того, как можно общую логику по работе с объектами системы выделять в отдельные тесты.
  4. Чистого времени на разработку этого теста, у освоившего Тестер программиста уходит до 7 минут.
  5. Не стесняйтесь копировать сценарии, всё-таки код сценария не совсем тоже самое, что код решения. Во многих случаях, лучше не злоупотреблять возможностью организации в тестах изощренных зависимостей ради ухода от повторения кода. Ведь тесты падают и время на анализ “почему?” тоже нужно брать в расчет. При сложных зависимостях, это будет сделать не просто (хотя полный стек ошибок и отладчик в Тестере есть).
  6. При копировании тестов не забывайте менять идентификатор окружения.
Запустите тест, он должен пройти без ошибок. Этот этап готов, идем дальше.


Этап 4


На этом этапе нам нужно разработать форму редактирования записи для регистра Согласование. Форма для редактирования нужна пользователю для фиксации результатов переговоров с заказчиком (условия задачи были описаны здесь).

В таблице ниже состыкуем работу по кодированию и тестированию:
Программирование Тестирование
В Конфигураторе:
  1. Добавляем форму записи в регистр сведений Согласование
  2. Формируем внешний вид
  3. Пишем логику работы формы
В Тестере:
Создадим тест РедактированиеФормыСогласования, где:
  1. Определим окружение:
    1. Наименование Поставщика
    2. Наименование Покупателя
    3. Товар с датой согласования
  2. Создадим окружение
    1. Создадим поставщика, покупателя и товар
    2. Сделаем поступление
    3. Создадим и проведем реализацию
  3. Целевая часть
    1. Откроем список регистра Согласование
    2. Установим отбор по покупателю и откроем единственную в списке запись
    3. Убедимся, что поле Комментарий недоступно для редактирования
    4. Нажмем галку Отработано, убедимся, что поле Комментарий теперь доступно.
    5. Сохраним и закроем форму
    6. Откроем её еще раз и убедимся, что поле Комментарий доступно
    7. Снимем галку Отработано, затем опять включим и убедимся, что поле Комментарий очищено
    8. Попробуем в списке создать новую запись
    9. Проверим, получил ли пользователь предупреждение о запрете на непосредственный ввод записей в регистр
Этот рывок будем делать комбинированно. Начнем с создания макета формы, в конфигураторе, создадим такую запись регистра:

Запустим приложение. Переключимся в Тестер, и в дереве сценариев создадим и зададим как основной тест РедактированиеФормыСогласования:

И введем такой код:
// Сценарий:
// - Создадим окружение в результате которого будет существовать проведенный документ
//  реализация с товаром, для которого указана дата согласования
// - Откроем список регистра Согласование
// - Установим отбор по покупателю и откроем единственную в списке запись
// - Убедимся, что поле Комментарий недоступно для редактирования (условия задачи были описаны здесь)
// - Нажмем галку Отработано, убедимся, что поле Комментарий теперь доступно.
// - Сохраним и закроем форму
// - Откроем её еще раз и убедимся, что поле Комментарий доступно
// - Снимем галку Отработано, затем опять включим и убедимся, что поле Комментарий очищено
// - Попробуем в списке создать новую запись
// - Проверим, получил ли пользователь предупреждение о запрете на непосредственный ввод записей в регистр

Вызвать ( "Общее.Начало" );
ЗакрытьВсё ();

ид = "25CB8B15";
тест = окружение ( ид );
создатьОкружение ( тест );

// ************************************
// Целевой сценарий
// ************************************

// Откроем регистр согласований
Коммандос ( "e1cib/list/РегистрСведений.Согласование" );
список = Здесь ( "Согласование" );

// Установим фильтр по покупателю
покупатель = тест.Покупатель;
Ввести ( "!КлиентФильтр", покупатель );

// Найдем его в списке
Кстроке ( "!Список", "Клиент", покупатель );

// Откроем запись
Нажать ( "!ФормаИзменить" );
Здесь ( Приложение.ПолучитьАктивноеОкно ().ПолучитьОбъект () );

// Проверим, что с галкой, может с прошлого раза осталась включенной
если ( "Да" = Взять ( "!Отработано" ) ) тогда
	Нажать ( "!Отработано" ); // выключим шалку
конецесли;

// Комментарий должен быть недоступен
ПроверитьСтатус ( "!Комментарий", "Доступность", ложь );

// Жмем галку Отработано
Нажать ( "!Отработано" );

// Комментарий должен быть доступен
ПроверитьСтатус ( "!Комментарий", "Доступность" );

// Вводим что-то в комментарий
Установить ( "!Комментарий", "Тестовый комментарий" );

// Нажимаем Записать и закрыть
Нажать ( "!ФормаЗаписатьИЗакрыть" );

// Возвращаемся в список и опять открываем эту запись
Здесь ( список );
Нажать ( "!ФормаИзменить" );
Здесь ( Приложение.ПолучитьАктивноеОкно ().ПолучитьОбъект () );

// Комментарий должен быть доступен
ПроверитьСтатус ( "!Комментарий", "Доступность" );

// Снимаем галку Отработано и устанавливаем заново
Нажать ( "!Отработано" );
Нажать ( "!Отработано" );

// Комментарий должен быть очищен
Проверить ( "!Комментарий", "" );
Нажать ( "!ФормаЗаписатьИЗакрыть" );

// Закроем форму, вернемся в список и попробуем ввести новую запись
Здесь ( список );
Нажать ( "!ФормаСоздать" );

// Проверим обязательное наличие сообщения об ошибке
если ( НайтиСообщения ( "*формируются автоматически*" ).Количество () = 0 ) тогда
  Стоп ( "Отсутствует сообщение об ошибке" );
конецесли;
Закрыть ();

// *********************************************
// Процедуры
// *********************************************

Функция окружение ( ИД )

	дата = ТекущаяДата ();
	п = новый Структура ();
	п.Вставить ( "ИД", ИД );
	п.Вставить ( "Дата", дата );
	п.Вставить ( "Согласование", дата + 172800 ); // Через два дня
	п.Вставить ( "ДатаПоступления", дата - 86400 );
	п.Вставить ( "Поставщик", "Поставщик " + ИД );
	п.Вставить ( "Покупатель", "Покупатель " + ИД );
	п.Вставить ( "Склад", "Склад " + ИД );
	п.Вставить ( "Товары", определитьТовары ( п ) );
	возврат п;

КонецФункции

Функция определитьТовары ( Тест )

	ид = Тест.ИД;
	список = новый Массив ();
	список.Добавить ( новыйТовар ( "Товар1 " + ид, 15, 250, 350, Тест.Согласование ) );
	возврат список;

КонецФункции

Функция новыйТовар ( Товар, Количество, Стоимость, Цена, Согласование = неопределено )

	п = новый Структура ();
	п.Вставить ( "Товар", Товар );
	п.Вставить ( "Количество", Количество );
	п.Вставить ( "Стоимость", Стоимость );
	п.Вставить ( "Цена", Цена );
	п.Вставить ( "Согласование", Согласование );
	возврат п;

КонецФункции

Процедура создатьОкружение ( Тест )

	ид = Тест.ИД;

	// *****************************************************
	// Проверяем, нужно ли создавать данные для тестирования
	// *****************************************************

	если ( Вызвать ( "Общее.ОкружениеСоздано", ид ) ) тогда
		возврат;
	конецесли;

	// ******************
	// Создаем склад
	// ******************

	п = Вызвать ( "Справочники.Склады.Создать.Параметры" );
	п.Наименование = Тест.Склад;
	Вызвать ( "Справочники.Склады.Создать", п );

	// ******************
	// Создаем поставщика
	// ******************

	п = Вызвать ( "Справочники.Поставщики.Создать.Параметры" );
	п.Наименование = Тест.Поставщик;
	Вызвать ( "Справочники.Поставщики.Создать", п );

	// *******************
	// Создаем покупателей
	// *******************

	п = Вызвать ( "Справочники.Покупатели.Создать.Параметры" );
	п.Наименование = Тест.Покупатель;
	Вызвать ( "Справочники.Покупатели.Создать", п );

	// **************
	// Создаем товары
	// **************

	для каждого товар из Тест.Товары цикл
		п = Вызвать ( "Справочники.Номенклатура.Создать.Параметры" );
		п.Наименование = товар.Товар;
		Вызвать ( "Справочники.Номенклатура.Создать", п );
	конеццикла;

	// *******************
	// Создаем поступление
	// *******************

	товары = новый Массив ();
	для каждого позиция из тест.Товары цикл
		товар = Вызвать ( "Документы.ПоступлениеТоваровУслуг.Создать.Товар" );
		товар.Товар = позиция.Товар;
		товар.Количество = позиция.Количество;
		товар.Цена = позиция.Стоимость;
		товары.Добавить ( товар );
	конеццикла;

	п = Вызвать ( "Документы.ПоступлениеТоваровУслуг.Создать.Параметры" );
	п.Товары = товары;
	п.Дата = тест.ДатаПоступления;
	п.Поставщик = тест.Поставщик;
	п.Склад = тест.Склад;

	Вызвать ( "Документы.ПоступлениеТоваровУслуг.Создать", п );

	// *******************
	// Создаем реализацию
	// *******************

	товары = новый Массив ();
	для каждого позиция из тест.Товары цикл
		товар = Вызвать ( "Документы.РеализацияТоваровУслуг.Создать.Товар" );
		товар.Товар = позиция.Товар;
		товар.Количество = позиция.Количество;
		товар.Цена = позиция.Цена;
		товар.Согласование = позиция.Согласование;
		товары.Добавить ( товар );
	конеццикла;

	п = Вызвать ( "Документы.РеализацияТоваровУслуг.Создать.Параметры" );
	п.Товары = товары;
	п.Дата = тест.Дата;
	п.Покупатель = тест.Покупатель;
	п.Склад = тест.Склад;

	Вызвать ( "Документы.РеализацияТоваровУслуг.Создать", п );

	// ********************************
	// Регистрируем созданное окружение
	// ********************************

	Вызвать ( "Общее.ШтампОкружения", ид );

КонецПроцедуры

Комментарии по коду сценария:
  1. Этот тест был создан путем копирования предыдущего, окружение практически не модифицировалось, я убрал создание потенциального клиента
  2. Может вызвать интерес такая строка кода: Здесь ( Приложение.ПолучитьАктивноеОкно ().ПолучитьОбъект () );. В данном случае показан пример работы с тестируемым приложением через стандартные методы платформы. В переменной Приложение, Тестер хранит объект ТестируемоеПриложение, через который можно вызывать все остальные методы платформы в режиме клиента тестирования (подробнее см. API Тестера).

Итак, запускаем тест и убеждаемся, что он падает, так как у нас не реализована логика работы формы. Далее, возвращаемся в конфигуратор, переключаемся в модуль формы записи и вводим такой код:
// *****************************************
// *********** События формы

&НаСервере
Процедура ПриЧтенииНаСервере ( ТекущийОбъект )

	оформить ();

КонецПроцедуры

&НаСервере
Процедура оформить ( Зависимость = неопределено )

	если ( Зависимость = неопределено или есть ( Зависимость, "Отработано" ) ) тогда
		Элементы.Комментарий.Доступность = Запись.Отработано;
	конецесли;

КонецПроцедуры

&НаСервере
Функция есть ( Зависимость, Список )

	возврат СтрРазделить ( Список, "," ).Найти ( Зависимость ) <> неопределено;

КонецФункции

&НаСервере
Процедура ПриСозданииНаСервере ( Отказ, СтандартнаяОбработка )

	если ( Запись.ИсходныйКлючЗаписи.Пустой () ) тогда
		ошибка ();
		Отказ = истина;
	конецесли;

КонецПроцедуры

&НаСервере
Функция ошибка ()

	Сообщить ( Нстр ( "ru = 'Записи в данном регистре формируются автоматически, при проведении документа Реализация товаров и услуг'" ) );

КонецФункции

// *****************************************
// *********** Группа Форма

&НаКлиенте
Процедура ОтработаноПриИзменении ( Элемент )

	применитьОтработано ();
	оформить ( "Отработано" );

КонецПроцедуры

&НаКлиенте
Процедура применитьОтработано ()

	если ( не Запись.Отработано ) тогда
		Запись.Комментарий = "";
	конецесли;

КонецПроцедуры

Затем, переключимся в форму и зададим обработчики событий: ПриЧтенииНаСервере, ПриСозданииНаСервере, ОтработаноПриИзменении. Запустите приложение, запустите тест и убедитесь, что он проходит.

Заключение


Мы рассмотрели теоретическую и практическую части применения системы Тестер для организации работы программиста через тестирование управляемого кодом клиентского приложения. Многие выкладки можно найти спорными и даже прямо противоположными учениям авторитетов в области построения процессов тестирования. Не вопреки, а с осознанием этого, готовился данный материал. Основной целью было показать практическое применение методики и средств сценарного тестирования.
Я допускаю, что в каждом разработанном сценарии, кто-то найдет “перебор” по части объема тестируемого функционала, а кто-то “недобор”. Мне остается лишь надеяться, что в этой статье был достигнут баланс между всем надоевшей простотой и малоинтересной сложностью. Не менее важной задачей, было показать, как можно вживить в разработку автоматизированное сценарное тестирование, чтобы оно не казалось аппендиксом или очередной надстройкой над сущностью программирования.

Большое спасибо, что не оставили без внимания этот труд. Удачных вам тестов, друзья!
Теги:
Хабы:
Всего голосов 15: ↑14 и ↓1+13
Комментарии6

Публикации

Истории

Работа

Ближайшие события

15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань