Приложения 1С редко работают в изоляции — чаще всего они интегрируются с различными сервисами, которые могут быть расположены как в локальной сети предприятия, так и на удаленных серверах. И иногда встает вопрос тестирования этой интеграции в рамках общего сценарного тестирования. Посмотрим, с какими проблемами при этом можно столкнуться и при чем тут вообще эти самые «заглушки».
Содержание
Проблемы тестирования интеграции
Запускаем проект SoapUI из консоли
С чем будем работать
Давайте предположим, что мы ведем разработку функционала, который обращается к удаленному веб-сервису, получает из него данные и запускает в базе 1С механизм расчетов. Не важно какие именно данные возвращает сервис: важно, то, что полученный результат прямо влияет на работу наших алгоритмов.
Предположим, что веб-сервис отвечает на запросы с ощутимым задержками, потому что обращается в нагруженную базу данных в реальном времени. При этом он расположен в VPN-сети и физическое его нахождение нам неизвестно: сервер может находиться в соседнем здании, может быть поднят в облаке или физически располагаться в зарубежном дата-центре.
В целом, используется стандартная схема: 1С обращается к сервису, который опубликован на веб-сервере; веб-сервис обращается к базе данных и возвращает результат.
Мы уже используем сценарное тестирование и хотим, чтобы наши тесты работали стабильно и не зависели от доступности удаленного сервера, загруженности серверного оборудования или от неполадок в сети. Поскольку прогон тестов — процесс не однократный, а регулярный (и в целом автоматический), мы хотим сделать так, чтобы в момент запуска тестов веб-сервис был всегда доступен и работал максимально быстро, стабильно и предсказуемо.
Обратите внимание, что мы не собираемся проверять работоспособность веб-сервиса и заниматься его тестированием — этим занимаются разработчики веб-сервиса. Нам нужно обеспечить его бесперебойную работу на момент проведения тестирования.
Получается, нам нужна такая «локальная копия» удаленного сервиса, которую можно настроить нужным нам образом, а своему приложению сказать: «Во время тестирования не ходи на удаленный сервер, а ходи на localhost».
Именно такую задачу - подменять реальный объект или сервис его упрощенной реализацией — и помогают решить так называемые «mock-service» (от англ. mock object: «объект-имитация» или «подставка»). Еще их называют просто «моки» или «заглушки».
Проблемы тестирования интеграции
Вообще, для использования заглушек веб-сервисов может быть много причин. Например, следующих:
-
Тесты замедляются. Если поставщик сервиса находится далеко, сетевая среда нестабильная и при вызове сервиса происходит задержка, то время прохождения тестов может значительно увеличиваться.
-
Тесты работают нестабильно. Веб-сервисы могут быть не всегда доступны из-за технических обновлений, подвержены перегрузкам или ошибкам сетевых протоколов.
-
Тесты покрывают не все возможные варианты ответов сервиса. Не всегда есть возможность получить некоторые ответы от реального веб-сервиса и промоделировать все рабочие ситуации — следовательно, максимально полно протестировать взаимодействие.
-
Доступ к рабочим веб-сервисам ограничен. Часто встречается ситуация, когда рабочие сервисы недоступны из тестового контура, в котором программисты ведут разработку. Это делает как по причине безопасности, так и для того, чтобы не подвергать лишней нагрузке рабочее окружение.
-
Из-за ошибок разработчика могут быть подвергнуты риску реальные данные. Например, есть веб-сервисы, которые позволяют добавлять или изменять данные в удаленной системе. Ошибка при вызове такого сервиса может привести к потере данных.
Давайте теперь поближе рассмотрим мок-объекты.
Заглушки
«Мок» в программировании - это объект-заглушка, реализующий заданный аспект моделируемого программного окружения.
Существует несколько видов объектов, которые позволяют симулировать поведение реальных объектов во время тестирования:
- Dummy — пустые объекты, которые передаются в вызываемые методы, но не используются. Предназначены лишь для заполнения параметров методов.
- Fake — объекты, которые имеют реализации, но в таком виде, который делает их неподходящими для использования в рабочей ситуации.
- Stub — предоставляют заранее заготовленные ответы на вызовы во время теста и не отвечают ни на какие другие вызовы, которые не требуются в тесте.
- Mock — объекты, которые заменяют реальный объект в условиях теста и позволяют проверять вызовы своих методов. Содержат заранее подготовленные описания вызовов, которые они ожидают получить. Применяются в основном для тестирования поведения пользователя.
Относительно веб-сервисов можно сказать, что мок-сервис позволяет переопределить реальный сервис и подставить вместо него упрощенную реализацию, которая работает нужным разработчику образом и дает доступ к собственным данным и настройкам. Мы как бы создаем у себя аналог удаленного веб-сервиса, который отвечает на вызовы нашего приложения.
Правда, тут возникают две сложности.
Во-первых, моки надо создать. Надо устанавливать дополнительный софт, описывать логику, загружать нужные данные в макет ответа. Не то чтобы это сложно, но это требует усилий и времени. Может быть, кому-то будет проще сохранить результат вызова в файл и при тестировании обращаться не к заглушке, а к файлу. В небольших проектах с малым количеством тестов такой подход вполне оправдан.
Во-вторых, веб-сервис может поменяться. Например, программист написал рабочую логику для заглушки, а через какое-то время веб-сервис стал работать по-другому (а разработчика, как часто бывает, об этом никто не уведомил). О том, что в работе веб-сервиса что-то поменялось он узнал, когда выкатил обновление на рабочую базу.
Решение простое — для сервиса, который может быть подвержен изменениям нужно сделать набор дымовых тестов (smoke-тесты). Дымовые тесты проверяют, что основные методы сервиса не изменились и обращение к ним не вызывает ошибок.
Мы рассмотрим способ создания мок-сервисов в программе SoapUI, которая является достаточно популярной в мире тестирования. Первоначальная схема работы примет такой вид:
Тестируемое приложение 1С больше не будет обращаться к удаленному сервису, а получит тот ответ, который мы заранее подготовили и опубликовали из SoapUI.
Запускаем моки через SoapUI
Шаг 0. Готовим окружение
Программа SoapUI (подробнее о ней можно прочитать в публикации Использование SoapUi для работы с веб-сервисами. Часть1) позволяет на основании WSDL-схемы сервиса создать Mock-service и опубликовать его на собственном веб-сервере, который умеет запускать.
SoapUI можно получить на официальном сайте компании SmartBear. Для наших целей достаточно бесплатной 32х-разрядной OpenSource версии — ее нужно скачать и установить.
В конфигурации 1С, которую мы дорабатываем, есть обработка Расчет трафика — она обращается к веб-сервису CalculateService и вызывает метод CalculateTraffic, в который передает несколько параметров, вроде ID, StoreID и других. Рассчитанный веб-сервисом результат записывается в регистр и запускает внутренний механизм расчетов (что он именно делает, в рамках статьи значения не имеет).
Мы дорабатываем механизм расчетов и во время проведения тестов хотим вместо ответов удаленного сервиса подставлять свои заранее заготовленные варианты.
Сервис, к которому мы обращаемся, расположен по адресу http://localhost/DEMO/ws/CalculateService.1cws?wsdl
! Поскольку я воспроизвожу пример для статьи на своем компьютере, в адресе вы видите «localhost». Но подразумевается, что в рабочей ситуации мы будем обращаться к рабочему сервису на рабочем сервере.
Для начала запустим браузер и убедимся, что веб-сервис доступен:
Дальше добавим веб-сервис в SoapUI и сделаем для него заглушку.
Шаг 1. Импортируем WSDL-схему
Запустим SoapUI и создадим новый проект.
Импортируем в проект описание нашего веб-сервиса: File -> New Soap Project:
Если при импорте возникает ошибка, дело скорее всего в авторизации: нужно ввести имя пользователя и пароль.
Забегая немного вперед, добавлю, что в SoapUI есть удобный способ сохранить данные авторизации: нужно заполнить параметры Username и Password на вкладке Auth, которая станет доступна в редакторе запроса к сервису:
В дереве проекта появился веб-сервис CalculateService.
Теперь мы можем заполнить параметры и обратиться к сервису:
Шаг 2. Запускаем mock-сервис
Добавляем метод CalculateTraffic в новый мок-сервис: контекстное меню -> Generate Soap MOCK Service:
Задаем параметры MockService:
Обратите внимание на порт, на котором будет доступен сервис и на путь к нему.
Жмем ОК и заполняем имя мок-сервиса:
Открывается окно, из которого можно запустить мок-сервис и указать его настройки:
Зайдем в настройки и укажем, что мок будет подниматься на локальном интерфейсе localhost:
Жмем на Запуск. Если запуск пройдет успешно, станет доступна кнопка Открыть WSDL-схему в браузере:
Проверяем - должна открыться WSDL-схема по адресу http://localhost:8090/MockCalculateService?WSDL.
Теперь имеем:
- реальный сервис на http://localhost/DEMO/ws/CalculateService.1cws?wsdl
- mock-реализацию на http://localhost:8090/MockCalculateService?WSDL
В дереве проекта появился мок-сервис MoskCalculateService с методом CalculateTraffic:
Шаг 3. Настраиваем ответ
Открываем редактор ответа Response 1. В этой форме можно определить данные, которые будет возвращать наш мок-сервис.
Здесь можно заполнить значения полей ответа, подставить любую валидную XML или написать скрипт на Groovy, который будет определять содержание ответа в зависимости от запроса к сервису. Например, мы можем в получать в скрипте значения переданных параметров, считывать файлы с диска и возвращать их клиенту с результатом вызова, при это логгируя все входящие запросы.
Я покажу простой вариант: получаем из входящего запроса значения параметров StoreID и Offset, сам запрос сохраняем на диск, а клиенту возвращаем значение, рассчитанное по формуле Offset*24 + StoreID:
Сохраняем проект командной Project -> Save project as на диск — этот файл нам понадобится дальше.
Я сохранил проект в d:\BASE1C\DEMO\MockCalculateService.xml
Запускаем проект SoapUI из консоли
Часть дела сделана — наш «заглушечный» веб-сервис запущен и к нему уже можно обращаться из 1С, но поскольку наша главная цель - автоматизировать его запуск, идем дальше.
Для того чтобы поднять мок-сервис из командной строки (а, следовательно, иметь возможность сделать это программным образом), нужно выполнить файл mockservicerunner.bat из директории C:\Program Files (x86)\SmartBear\SoapUI-5.5.0\bin.
Подробное описание параметров запуска mockservicerunner.bat можно найти на странице сайте SoapUI или в консоли:
Главный обязательный параметр — путь к xml-файлу с проектом из SoapUI. Программа считывает настройки проекта из файла и по умолчанию поднимает все мок-сервисы, которые в нем описаны.
Открываем консоль в папке c:\Program Files (x86)\SmartBear\SoapUI-5.5.0\bin\ и запускам наш сервис командной:
c:\Program Files (x86)\SmartBear\SoapUI-5.5.0\bin>mockservicerunner.bat d:\BASE1C\DEMO\MockCalculateService.xml
В консоли наблюдаем, что MockService started on port 8090:
Подробный лог запуска записывается в c:\Program Files (x86)\SmartBear\SoapUI-5.5.0\bin\soapui.log, а ошибки можно найти в файлах soapui-errors.log и errors.log
Проверим еще раз, что WSDL-схема доступна по адресу http://localhost:8090/MockCalculateService?WSDL.
Теперь мы умеем запускать заглушки с помощью командной строки. Пойдем дальше и попробуем прикрутить все это дело к Ванессе.
Подключаем Ванессу
Поскольку на Инфостарте уже есть замечательные статьи про тестирование и применение Ванессы на практике, сразу перехожу к делу.
План такой: при запуске теста будем поднимать наш мок-сервис, проверять его доступность, заполнять поля на форме и нажатие на кнопку Рассчитать выызвать его метод CalculateTraffic.
Определим примерный набросок сценария:
-
Поднимаем сервис из файла;
-
Проверяем, что сервис доступен;
-
Заполняем параметры на форме обработки;
-
Выполняем вызов процедуры, которая обращается к сервису;
-
Проверяем результат расчета;
-
Завершаем работу мок-сервиса.
Создадим файл ПроверкаСервиса.feature (я взял за основу одну из своих существующих фич) и загрузим его в Ванессу:
Дальше мы будем заниматься реализацией шагов. Итоговый текст фичи будет приведен в конце.
Подготовка данных
Пара слов о том, как будем хранить настройки для обращений к веб-сервисам.
Полный путь к файлу mockservicerunner.bat записываем в константу ПутьКЗапускателюЗаглушекВебСервисов, а для описаний веб-сервисов добавим справочник ВебСервисы:
При выполнении сценария каждый раз будем заполнять константу:
И добавлять новый элемент в справочник Веб-сервисы:
Делаем это потому, что другой тест или пользователь может перезаписать или удалить наши значения — то есть нет гарантии, что во время тесты в них будут заполнены правильном образом.
Элемент справочника будем загружать из макета. Так удобнее, чем генерировать заполнение полей через кнопко-нажималку.
Чтобы создать данные для макета идем в Внешние инструменты -> Генератор макетов данных и в колонке «Данные для выгрузки» выбираем наш справочник ВебСервисы:
В сгенерированной Ванессой обработке (этап генерации обработки я опускаю, подразумевая, что делать это мы умеем) нужно добавить макет и скопировать туда содержимое табличного документа:
Запускаем моки
Для запуска моков нам нужно выполнить батник mockservicerunner.bat с параметром d:\BASE1C\DEMO\MockCalculateService.xml.
Посмотрим, можно ли это сделать стандартным шагом. Идем в Библиотеки -> Добавить известный шаг и поищем по строке «выполн»:
Видим, что в стандартной библиотеке Ванессы есть шаг Я Запускаю команду с параметром, который позволяет выполнить команду систему с произвольным набором параметров.
Можно было бы воспользоваться этим шагом, но, как говорится — есть нюансы.
Во-первых, придется каждый раз писать что-то вроде «Я запускаю команду 'c:\Program Files (x86)\SmartBear\SoapUI-5.5.0\bin\mockservicerunner.bat' с параметром «d:\BASE1C\DEMO\MyServiceProject.xml» — это длинно и неудобно.
Во-вторых, библиотечный шаг выполняется только в синхронном режиме — то есть вызывающий процесс (в нашем случае это 1С) будет ожидать завершения работы вызываемой команды, чтобы продолжить свое выполнение. Это значит, что 1С запустит мок-сервис и будет висеть, пока он будет запущен. Не совсем то, что нам нужно.
Вот так реализован шаг Я запускаю команду в файле из поставки libraries\Плагины\step_definitions\Фича_УправлениеПриложениями.epf:
А вот реализация функции Выполнить команду ОС без показа черного окна в одной из главных обработок Ванессы bddRunner.epf:
Видно, что вызывается метод Run() объекта WScript.Shell и параметр ЖдатьОкончания вроде бы есть, но передать значения в него нельзя. Баг это или фича, честно говоря, я не понял :) Удобнее всего для запуска моков создать свой шаг, в котором можно принудительно указать параметр метода Run().
Реализуем шаг Я запускаю моки из файла:
&НаКлиенте
Процедура ЯЗапускаюМокиИзФайла(Парам01) Экспорт
ЯЗапускаюМокиИзФайлаНаСервере(Парам01)
КонецПроцедуры
&НаСервере
Процедура ЯЗапускаюМокиИзФайлаНаСервере(ФайлСОписаниемМокСервисов)
ПутьКЗапускателю = Константы.ПутьКЗапускателюЗаглушекВебСервисов.Получить();
Если НЕ ЗначениеЗаполнено(ПутьКЗапускателю) Тогда
ВызватьИсключение "Не заполнена константа ПутьКЗапускателюЗаглушекВебСервисов";
КонецЕсли;
ТекстКоманды = """" + ПутьКЗапускателю + """" """" + ФайлСОписаниемМокСервисов + """";
ИмяВременногоФайлаКоманды = ПолучитьИмяВременногоФайла("bat");
ЗТ = Новый ЗаписьТекста(ИмяВременногоФайлаКоманды, КодировкаТекста.ANSI);
ЗТ.Закрыть();
ЗТ = Новый ЗаписьТекста(ИмяВременногоФайлаКоманды, КодировкаТекста.UTF8, , Истина);
ЗТ.ЗаписатьСтроку("chcp 65001");
ЗТ.ЗаписатьСтроку(ТекстКоманды);
ЗТ.Закрыть();
WshShell = Новый COMОбъект("WScript.Shell");
Рез = WshShell.Run("""" + ИмяВременногоФайлаКоманды + """", 0, 0);
WshShell = Неопределено;
КонецПроцедуры
Ожидание запуска моков
Чтобы понять, что мок-сервис поднялся корректно, нужно к нему обратится. Если он недоступен — подождать немного и попробовать обратиться еще — и так несколько раз. А если он и после 10-й попытки недоступен - значит, что-то пошло не так и тест провален.
Чтобы сделать цикличную проверку доступности сервисов, используем функцию ПодключитьОбработчикОжидания и функцию Ванессы ПродолжитьВыполнениеШагов:
&НаКлиенте
Процедура ЯОжидаюПоднятиеМок_сервиса(Парам01) Экспорт
КонтекстСохраняемый.Вставить("МокСервис", Парам01);
КонтекстСохраняемый.Вставить("НомерПопытки", 1);
Ванесса.ЗапретитьВыполнениеШагов();
ПодключитьОбработчикОжидания("ПроверитьДоступностьМокСервисов", 1);
КонецПроцедуры
&НаКлиенте
Процедура ПроверитьДоступностьМокСервисов() Экспорт
ШагУпал = Ложь;
Если КонтекстСохраняемый.НомерПопытки <= 10 Тогда
КонтекстСохраняемый.НомерПопытки = КонтекстСохраняемый.НомерПопытки + 1;
Если ВебСервисДоступенНаСервере(КонтекстСохраняемый.МокСервис) Тогда
ОтключитьОбработчикОжидания("ПроверитьДоступностьМокСервисов");
Ванесса.ПродолжитьВыполнениеШагов();
КонецЕсли;
Иначе
ОтключитьОбработчикОжидания("ПроверитьДоступностьМокСервисов");
ШагУпал = Истина;
Ванесса.ПродолжитьВыполнениеШагов(ШагУпал);
КонецЕсли;
КонецПроцедуры
Остановка мок-сервисов
После того как тестируемая программа выполнила обращение к мок-сервису, нужно остановить его работу.
При использовании графического интерфейса SoapUI все просто: для остановки мок-сервиса нужно на красную кнопку Stop the mock service.
При запуске через командную консоль тоже проблем нет — достаточно нажать сочетание клавиш Ctrl+C (или Ctrl+Z).
А в случае, когда мы запускаем процесс через WScript.Shell, все несколько сложнее — штатного способа остановить выполнение его нет.
Например, вот вопрос в официальном сообществе SoapUI, который так и остался без ответа: Stop mock service using mockServiceRunner.
Вопрос достаточно распространенный и разработчики предлагают разные решения:
-
Запоминать PID (идентификатор) процесса и «пристреливать» его после завершения теста. Пример для Linux: Sample script to start/stop soapui mockservicerunner with nohup
-
Сделать обертку над SoapUI, которая после запуска веб-сервиса будет пытаться прочитать внешний файл-флаг. Если файла нет — все ок, работаем дальше. Если файл появился — завершаем работу сервиса. Проект на гитхабе: Wrapper to start SoapUI MockService Runner
-
Написать собственное приложение или скрипт, который будет парсить файл проекта SoapUI, извлекать из него описание сервиса и поднимать собственный веб-сервис.
Пока в публичном доступе готового решения для Windows не найдено, остановимся на простом варианте — будем завершать работу процесса java.exe командой
taskkill /FI "IMAGENAME eq java.exe" /F
это сработает, если больше нет запущенных процессов java.exe (точнее говоря, они все будут завершены в момент выполнения этой команды).
Или можно использовать более продвинутый вариант:
powershell (Get-WmiObject Win32_Process -FILTER "Commandline like '%MyServiceProject.xml%'").Terminate()
скрипт завершит работы всех процессов, у которых в параметрах запуска есть название нашего файла с проектом: «MyServiceProject.xml».
Коллеги, если у кого есть более изящное решение, сообщите :)
Результат
Теперь мы можем составить полный текст нашего сценария «Расчет трафика»:
#language: ru
Функционал: Расчет трафика
Как программист 1С
Я хочу проверить работу механизма расчета трафика
Чтобы убедиться, что доработки его не поломали
Контекст:
Дано Я запускаю сценарий открытия TestClient или подключаю уже существующий
И Я удаляю все элементы справочника "ВебСервисы"
И Я устанавливаю значение константы "ПутьКЗапускателюЗаглушекВебСервисов" равным "c:\Program Files (x86)\SmartBear\SoapUI-5.5.0\bin\mockservicerunner.bat"
И Я создаю fixtures по макету "ТестовоеОкружение"
Сценарий: Расчет трафика и выполнение расчетов
Когда Я запускаю моки из файла "d:\BASE1C\DEMO\MockCalculateService.xml"
И Я ожидаю поднятие мок-сервиса "MockCalculateService"
Тогда В командном интерфейсе я выбираю 'Тестирование' 'Расчет трафика'
Когда открылось окно 'Расчет траффка'
И в поле 'ID' я ввожу текст '10'
И в поле 'Store ID' я ввожу текст '5'
И в поле 'Date' я ввожу текст '2019-02-01'
И в поле 'Offset' я ввожу текст '2'
И в поле 'Logistic ID' я ввожу текст '2'
И в поле 'Main status' я ввожу текст 'true'
И я нажимаю на кнопку 'Рассчитать'
Тогда элемент формы с именем "Результат" стал равен '1 253'
И я выполняю команду "taskkill" с параметрами '/FI "IMAGENAME eq java.exe" /F'
И Я закрываю окно 'Расчет трафика'
Результатом работы служит пройденный без ошибок сценарий:
Мы решили поставленную задачу в минимальном варианте, на основании которого можно реализовать уже более сложную логику.