TestComplete ( JScript ): Описание оконных деклараций через классы-обертки

Автоматизированное тестирование на уровне GUI содержит в себе много сюрпризов, связанных с работой с оконными объектами. Причем сюрприз заключается в том, что наиболее изящное решение проблемы с поддержкой описаний оконных объектов в актуальном состоянии для каждого отдельно взятого средства может быть свое. Но это наиболее удачное решение. Тем не менее, есть ряд механизмов, имеющих аналоги в различных средствах. Одним из примеров является Mapping оконных деклараций, позволяющий некоторым оконным объектам ставить в соответствие некоторый псевдоним. Подобное решение в TestComplete обладает одним ключевым недостатком: при изменении иерархии оконного объекта приходится переделывать маппинг всех дочерних объектов. Альтернативой этому стало введение с 6-й версии TestComplete такой функциональности как Alias, которая позволяла регулировать иерархию псевдонимов. Но у такого решения есть другой недостаток: низкая скорость. Соответственно, нужны механизмы, позволяющие оперативно реагировать на изменение GUI, при этом корректировки желательно минимизировать. Давайте рассмотрим конкретный пример.

У нас есть некоторое диалоговое окно, к которому в коде можно обратиться так:

``` Sys.Process( PRODUCT_NAME ).Window("TForm", "New Text Style", 1); ```

Здесь PRODUCT_NAME - это некоторая константа, содержащая в себе имя процесса. В вышеуказанном диалоге есть текстовое поле Name , к которому обратиться можно так:

``` Sys.Process( PRODUCT_NAME ).Window("TForm", "New Text Style", 1).Window( "TEdit" , "*" , 1 ); ```

а также 2 кнопки: OK, Cancel, у которых описания имеют вид соответственно:

``` Sys.Process( PRODUCT_NAME ).Window("TForm", "New Text Style", 1).Window( "TButton", "*", 1 ) Sys.Process( PRODUCT_NAME ).Window("TForm", "New Text Style", 1).Window( "TButton", "*", 2 ) ```

Как видно из описаний, объекты не очень компактно описаны, более того, достаточно нестабильно, поскольку используются индексы. Часто может возникнуть ситуация, когда имена объектов в разной среде разные. Самый простой пример: системные окна сообщений для разных локализаций операционной системы имеют кнопки с текстом на локальном языке. Если заказчик из англоязычной страны, то проблем не возникнет. Но если же автотесты создаются для продукта, который идет и для неанглоязычных потребителей, то тут надо уменьшить привязку к тексту объектов, что было сделано для объектов выше. Теперь, давайте рассмотрим некоторый типичный тестовый сценарий, использующий эти объекты. Допустим нам надо подождать появления диалога в течение 5 секунд. Если он появился, то вводим текст ( неважно какой ) в текстовое поле Name и жмем кнопку OK. В чистом виде это выглядит так:

``` if( Sys.Process("Inventory").WaitWindow("TForm", "New Text Style", 1 , 5000 ).Exists ) { Log.Error( "No Text Style dialog available" ); } else { Sys.Process( PRODUCT_NAME ).Window("TForm", "New Text Style", 1).Window( "TEdit" , "*" , 1 ).wText = "Some Text"; Sys.Process( PRODUCT_NAME ).Window("TForm", "New Text Style", 1).Window( "TButton", "*", 1 ).Click(); } ```

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

``` function NewTextStyleDlg() { this.Get = function () { return Sys.Process( PRODUCT_NAME ).Window("TForm", "New Text Style", 1); } } ```

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

``` var dNewTextStyle = new NewTextStyleDlg();

if( Sys.Process( PRODUCT_NAME ).WaitWindow(“TForm”, “New Text Style”, 1 , 5000 ).Exists ) {
Log.Error( “No Text Style dialog available” );
}
else {
dNewTextStyle.Get().Window( “TEdit” , “" , 1 ).wText = “Some Text”;
dNewTextStyle.Get().Window( “TButton”, "
”, 1 ).Click();
}

</p><p><!--break-->Что дальше? У нас есть инструкция по проверке на существование, которая все еще явно обращается к свойствам диалогового окна. Соответственно, мы можем дополнить класс-обертку методом, проверяющим на существование. Модифицированный класс выглядит так:</p><p>

function NewTextStyleDlg()
{
this.Get = function ()
{
return Sys.Process( PRODUCT_NAME ).Window(“TForm”, “New Text Style”, 1);
}

this.Exists = function ( iWT )
{
if( iWT == undefined )
{
iWT = 5000;
}

Sys.Refresh();
return Sys.Process( PRODUCT_NAME ).WaitWindow("TForm", "New Text Style", 1,iWT).Exists; 

}
}

</p><p>В результате внесенных изменений, тестовый код может быть переписан в виде:</p><p>

var dNewTextStyle = new NewTextStyleDlg();

if( dNewTextStyle.Exists() ) {
Log.Error( “No Text Style dialog available” );
}
else {
dNewTextStyle.Get().Window( “TEdit” , “" , 1 ).wText = “Some Text”;
dNewTextStyle.Get().Window( “TButton”, "
”, 1 ).Click();
}

</p><p>Осталось только обернуть непосредственно объекты. Есть множество возможных вариантов, но мы остановимся на создании методов, которые возвращают сами объекты. Нам нужны методы, которые открывают доступ к текстовому полю Name, а также к кнопке OK и Cancel. Реализуется это так:</p><p>

function NewTextStyleDlg()
{
this.Get = function ()
{
return Sys.Process( PRODUCT_NAME ).Window(“TForm”, “New Text Style”, 1);
}

this.Exists = function ( iWT )
{
if( iWT == undefined )
{
iWT = 5000;
}

Sys.Refresh();
return Sys.Process( PRODUCT_NAME ).WaitWindow("TForm", "New Text Style", 1,iWT).Exists; 

}

this.edtName = function () {
return this.Get().Window( “TEdit” , “*” , 1 );
}

this.btnOK = function () {
return this.Get().Window( “TButton”, “*”, 1 );
}

this.btnCancel = function () {
return this.Get().Window( “TButton”, “*”, 2 );
}
}

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

var dNewTextStyle = new NewTextStyleDlg();

if( dNewTextStyle.Exists() ) {
Log.Error( “No Text Style dialog available” );
}
else {
dNewTextStyle.edtName().wText = “Some Text”;
dNewTextStyle.btnOK().Click();
}

</p><p>Вот теперь наш тестовый код содержит обращения исключительно к интерфейсам используемых оконных объектов, а не к самим объектам. Соответственно, если произойдет модификация структуры диалога, но при этом сами кнопки и текстовое поле останутся ( только поменяется оконная иерархия ), то для восстановления работоспособности тестового кода нам надо будет подкорректировать только класс-обертку. А это точечные изменения.</p>
2 лайка