Урок 5. Декларация окон, классов.
Изучив основные конструкции и основные возможности языка 4Test, самое время перейти к рассмотрению работы с оконными объектами, так как SilkTest предназначен непосредственно для работы именно с окнами и элементами управления.
Что такое окна, классы. Иерархия стандартных классов.
Окно, это объект операционной системы, визуально представляющий из себя некоторую прямоугольную область экрана, обладающий опеределнными свойствами и выполняющий опереденные действия. В нашем случае это окна приложений, диалоговые окна, элементы управления ( кнопки, текстовые поля и т.д. ). SilkTest распознает эти элементы как оконные объекты и работает с ними. Данные объекты определяются как элементы типа WINDOW.
Многие элементы управления содержат в себе ряд общих характеристик, которые не являются специфическими для конкретного элемента. Это позволяет разделить все объекты на определенные классы. В 4Test-е единственным видом классов являются оконные классы (winclass-ы). По аналогии с классами в С++, оконные классы имеют определенный набор свойств и методов, они могут наследоваться от некоторого класса, перенимая у него свойства и методы родительского класса. Соответственно формируется определенная иерархия объектов. В иерархии стандартных классов 4Test-а выделяются 4 класса, которые не имеют базовых классов:
  • CursorClass - класс для работы с курсором
  • ClipboardClass - класс, обеспечивающий работу с текстовым буфером обмена
  • AgentClass - класс для работы с Агентом
  • AnyWin - класс, соответствующий любому окно
Первые 3 из перечисленных классов не соответствуют оконным объектам. Их назначение соответствует назначению классов в С++ - группировка некоторых переменных, констант и функций под одним именем. Не более того. Куда более значительный интерес представляет класс AnyWin. Это базовый класс для всех классов, соответствующих окнам. То есть любое окно, элемент управления так или иначе используют класс, наследуемый от AnyWin. От него наследуются такие основные классы как:
  • DesktopWin - соответствует классу окна рабочего стола
  • Control - класс, от которого наследуются основные классы элементов управления ( за редким исключением ).
  • MovableWin - класс, от которого наследуются классы для главных окон (MainWin), дочерних окон многодокументных приложений (ChildWin) и диалоговых окон (DialogBox). Объединяет эти классы галичие методов, позволяющих перемещать/перетаскивать окна, менять их размер и т.п.
  • Menu - соответствует пунктам меню
  • CustomWin - соответствует некоторому нестандартному окну, класс которого не описывается как стандартный
Эти классы являются базовыми уже для конкретных классов, с которыми непосредственно придется работать.
Описание оконных деклараций
Как же окна описываются? Описания окон имеют вид:
 
                       window <class_name> <window_name>
                               [[multi]tag <tag_definition>]
                       
                               [parent <parent_window_name>]
 
                               [ Properties, Child controls , Methods]
Здесь:
  • <class_name> - имя существующего класса
  • <window_name> - имя окна. Оно должно быть уникальным
  • <tag_definition> - строка или набор строк, по которым данное окно находится Агентом
  • <parent_window_name> - имя родительского окна
Поскольку структура окон является иерархической, то внутри объявления окна можно объявлять другие окна и под-окна, при этом не надо указывать ключевое слово window и parent. Данные ключевые слова используются для окон верхнего уровня.
При этом следует обратить внимание на то, что ключевое слово WINDOW соответствует не только объявлению объекта окна, но это также и тип данных. То есть переменная типа WINDOW и объект-окно - это не одно и тоже, хотя бы потому, что значение переменной может быть изменено в ходе выполнения скрипта, чего нельзя сделать для объекта-окна. Поэтому, если за ключевым словом WINDOW следует имя оконного класса и затем имя, то это объявление окна данного класса. Иначе, идентификатор, следующий за данным ключевым словом, является именем переменной типа WINDOW.
Тэги, их виды и способы использования
В оконных декларациях теги играют одну из ключевых ролей. Поэтому на них можно остановиться поподробнее. Как уже было указано выше - это строки, позволяющие уникально идентифицировать окна. В зависимости от информации, на основе которой формируется тэг, тэги разделяются на виды:
  • По Caption - в основе тега лежит текст заголовка окна или текст привязанный к данному объекту ( для элементов управления ). Данный тег представляет собой обычную строку без каких-либо специальных символов. Это наиболее употребимый вид тэга и наиболее рекомендуемый, поскольку он отражает характеристики объекта, которые можно воспринимать визуально.
  • По Prior Static - в основе тэга лежит текст Statiс Text объекта ( или приравненного к нему ), ближайшего к данному объекту. Такой тэг применим для элементов управления и его удобно использовать, если тэг по Caption менее соответствует данному объекту, чем тэг по Prior Static, то есть, если 2-й тэг содержит реально значение текста перед элементом управления, в то время как Caption извлекся из более далекого объекта. Тэг по Prior Static начинается с символа ^ .
  • По индексу - в основе тега лежит порядковый номер данного элемента на форме. Порядок следования формируется по возрастанию сверху-вниз и слева-направо. Строка данного тега начинается с # и содержит порядковый номер. Данный тип тэгов достаточно эффективен, для работы с динамическим содержимым, когда можно уловить определенный числовой ряд. Тем не менее этот вид тэга также применяется в случае, если по Caption или по Prior Static тэги не получается получить, при этом фрейм не меняется динамически.
  • По ID - в основе тега лежит уникальный идентификатор данного объекта. Для разных видов приложений в качестве такого идентификатора выступают разные величины. Для главных окон приложения это зачастую путь к исполняемому файлу, для элементов управления это HWND объекта ( для GUI-приложений ), а для веб-приложений это значение атрибута name данного объекта. В любом случае данный вид тэга достаточно уникален, но наиболее эффективно его можно использовать при декларации окон веб-приложения.
  • По Location - в основе тэга лежат координаты левого верхнего угла объекта. Этот тег имеет формат @( x , y ). Этот вид тэгов используется в основном для контекстных меню, когда единственной определяющей характеристикой является именно местоположение объекта. Иногда этот тэг применим и для нестандартных объектов, которые иначе просто и не видятся. В остальном лучше искать другие тэги.
Это основные тэги. При этом тэг может использоваться как один, так и несколько. Для этого есть ключевое слово multitag, которое описывает варианты тэга для некоторого элемента. Но в большинстве случаев, достаточно одного тэга, чтобы достаточно точно описать окно.
Еще одной особенностью тега является то, что данная строка содержит не монолитное значение, которое в точности соответствует значению в окне, а это фактически шаблон, по которому ищется окно. Причиной этому является то, что текст, который может служить для формирования тэгов в общем случае варьируется, но при этом могут оставаться некоторые общие части. Соответственно, в тэгах допустимы специальные символы *(один или более произвольных символа) и ?(один любой символ). Эти специальные символы позволяют маскировать определенные фрагменты тэга, не придавая им значение при определении элемента в окне приложения. Что нам это дает? Допустим, у нас есть тэг "Test * tag? with*". Тогда данным тэгом можно заменить следующие тэги:
 
                       "Test my tags with wildcards"
                       "Test   tag with      "              
В первом тэге маскированные элементы выделены жирным шрифтом, а во втором примере замаскированными элементами оказались пробелы.
Но что делать, если один тэг не описывает нормально объект, а возможны варианты из 2 и более тэгов. В этом случае можно использовать multitag, но зачастую можно обойтись обычным tag. В этом случае можно использовать символ |. То есть, запись:
Code
 
   [+] multitag "Caption"
           "#2"
аналогична записи:
Code
 
   [+] tag "Caption|#2"
Таким образом multitag может быть полностью заменен на tag.
Вполне могут возникнуть случаи, когда 2 и более элемента управления, находящихся на одном уровне иерархии, имеют одинаковые тэги. Эта ситуация не является благоприятной, поскольку не позволяет четко определить, какой же именно объект нужно использовать. Для этого можно, конечно, воспользоваться другим типом тэга, но при этом мы потеряем точность описания, ведь тот же тэг по индексу уже не будет соответствовать требуемому объекту, если выше будет добавлен еще один элемент управления того же типа. Это сдвинет индексы. В этом случае можно в конце тэга в квадратных скобках указать номер элемента, который надо искать с таким же тэгом. Иными словами, если у нас есть 2 окна на одном и том же уровне, одного и того же класса у которых тэг, например, "some duplicating tag", то тэг "some duplicating tag[1]" будет соответствовать именно первому окну, а тэг "some duplicating tag[2]" - второму. Эту возможность наиболее часто приходится использовать, если используется маскировка некоторых частей тэга ( в тэге используются символы * или ? ). Специальные символы * и ? фактически расширяют множество допустимых тэгов, соответственно повышается вероятность того, что один и тот же тэг подойдет сразу нескоьким окнам. Вот здесь и нужно использовать данный прием.
Рассмотрим более конкретно применение данной возможности. Откроем тестовое приложение (TestApp), которое идет в поставке с SilkTest и выберем пункт меню File > New. Появится дочернее окно с текстовым полем. Опишем его и подкорректируем имена. В итоге объявление имеет вид:
Code
 
[+] window ChildWin wChild
   [ ] tag "MDI Child Window #1"
   [ ] parent wTestApplication
   [ ] 
   [+] TextField edtText
           [ ] tag "#1"
Но если мы повторно выберем пункт меню File > New, то появится такое же окно, но с заголовком "MDI Child Window #2" и тэг у него будет соответствующим. И так далее. Этих окон может быть очень много. Соответственно бесконечное количество копий делать бессмысленно. Тогда можно воспользоваться тэгом "MDI Child Window #*". Тогда нам неважно, какой номер у окна. Но при этом такой тэг будет неуникальным. Для того, чтобы описать тэг именно для активного окна. В данном случае, это первое окно, тэг которого удовлетворяет данному шаблону. Соответственно, скорректированная декларация окна имеет вид:
Code
 
[+] window ChildWin wChild
   [ ] tag "MDI Child Window #*[1]"
   [ ] parent wTestApplication
   [ ] 
   [+] TextField edtText
           [ ] tag "#1"
Также могут возникнуть случаи, когда класс окна в объявлении может несоответствовать реальному классу, который имеется у окна приложения. Это может быть вызвано различными причинами. Наиболее часто данная вещь полезна при тестировании приложений, основанных на диалогах или для приложений, у которых при запуске появляется вначале диалог. В чем проблема в данном случае. Диалоговое окно описывается классом DialogBox, у которого нету никакого метода, который бы стартовал приложение. Встроенные команды для работы с командной строкой не расчитаны на запуск оконных приложений, для подобных целей надо вытаскивать функции из системных dll, что не очень удобно и не всегда доступно. Но мы знаем, что у класса MainWin есть метод Start, который просто запускает командную строку и ждет появления данного окна. Этот метод удобно задействовать. При этом, это окно по-прежнему должно восприниматься как диалог. Допустим, данный диалог записывается так:
Code
 
[+] window DialogBox dDialog
   [ ] tag "Dialog"
Тогда вышеописанное решение можно реализовать так:
Code
 
[+] window MainWin dDialog
   [ ] tag "[DialogBox]Dialog"
Ключевые моменты подсвечены жирным шрифтом. Как видим, мы данное окно объявили как MainWin, но при этом мы поменяли тег, добавили перед строкой тега [DialogBox]. Это означает, что для данного окна будут доступны все свойства и методы класса MainWin, но при этом в приложении будет выискиваться именно диалоговое окно. Сама возможность осуществления подобное приема в очередной раз подтверждает, что описание объекта и реальная структура окна не являются взаимозависимыми вещами и их можно разделять.
В подтверждение этих слов осталось привести еще одну возможность работы с тегами. Как правило, вручную описывать окна - это задача слишком трудоемкая, требующая немалого воображения и усилий. Поэтому куда более предпочтительно запись оконных деклараций делать автоматически. При этом, во многих случаях оконная иерархия бывает очень сложной и вложенной, хотя с точки зрения логической структуры она намного проще. Это достаточно характерно для веб-приложений, когда таблицы используются просто для форматирования элементов на форме, также много схожих случаев в других приложениях. Соответственно, работать с такой сильно вложенной иерархией объектов достаточно утомительно, особенно, если очевидно, что некоторые окна являются просто промежуточными звеньями. Для целей оптимизации структуры оконной декларации можно в теге указывать составные пути к нужному объекту. Например, у нас есть декларация вида:
Code
 
[+] window MainWin wMain
   [ ] tag "Main"
   [ ]
   [+] DialogBox dDialog
           [ ] tag "Dialog"
           [ ] 
           [+] TextField edtText
                  [ ] tag "Text"
Конструкция кода, которая вставит некоторый текст в текстовое поле в данном случае имеет вид:
Code
 
[ ] wMain.dDialog.edtText.sValue = "Some Text"
Допустим, из всей этой иерархии нам нужно только текстовое поле edtText, при этом не хочется при работе с данным объектом тянуть промежуточные звенья (тот же dDialog). Тогда иерархию можно упростить так:
Code
 
[+] window MainWin wMain
   [ ] tag "Main"
   [ ]
   [+] TextField edtText
           [ ] tag "[DialogBox]Dialog/Text"
и тогда в коде данному текстовому полю можно ввести текст вот таким образом:
Code
 
[ ] wMain.edtText.sValue = "Some Text"
Как правило, елементы управнения (типа кнопок, текстовых полей, чекбоксов и т.п.) не выносят на самый верхний уровень при описании. Вышеприведенной оптимизации структуры более чем достаточно, но в принципе вполне возможно и такое объявление:
Code
 
[+] window TextField edtText
   [ ] tag "[MainWin]Main/[DialogBox]Dialog/Text"
и это нам позволит реализовать ввод текста в данное поле вот в такой форме:
Code
 
[ ] edtText.sValue = "Some Text"
Таким образом, в тегах можно указывать пути в оконной иерархии. При этом каждое звено пути отделяется символом "/", а также для каждого звена желательно явно указать класс.
По иерархии в тегах можно двигаться как в направлении увеличения вложенности, так и наоборот, вверх по иерархии. Для этого в качестве звена в строке пути тега нужно задать "..". В рассматриваемом в данный момент примере вернемся к исходной декларации, но слегка подкорректируем тег для диалога. Это выглядит вот так:
Code
 
[+] window MainWin wMain
   [ ] tag "Main"
   [ ]
   [+] DialogBox dDialog
           [ ] tag "Dialog/[TextField]Text/.."
           [ ] 
           [+] TextField edtText
                  [ ] tag "Text"
В данном случае мы ничего не поменяли. Мы протянули путь к текстовому полю, а затем перешли на уровень вверх в иерархии объектов. Такую запись тега следует воспринимать как "диалоговое окно с тегом Dialog, у которого есть дочерний элемент - текстовое поле с тегом Text". Когда подобная запись будет полезной? Допустим, у нас есть несколько окон с одинаковыми тегами, но с разным содержимым. Соответственно, и в этом случае нам нужно искать окно с некоторым тегом, содержащее элемент определенного вида. Это опять же позволит логическую структуру окна отделить от физической, что тоже зачастую бывает полезно.
Создание оконных классов.
Когда уже есть достаточное количество описанных оконных деклараций, то вполне может возникнуть ситуация, когда есть несколько различных окон, у которых многие элементы, если не все являются одинаковыми. То есть наблюдается некоторый общий каркас. Подобное явление часто имеет место у веб-приложений, у них зачастую страницы имеют некоторый общий каркас (те же главные меню, шапки и т.п.). В таких случаях имеет смысл группировать общие части этих окон в некоторый оконный класс. Описание класса имеет вид:
 
                       winсlass <class_name> [: <parent_class_name>]
                               [[multi]tag <tag_definition>]
                       
                               [parent <parent_window_name>]
 
                               [ Properties, Child controls , Methods]
Здесь:
  • <class_name> - имя создаваемого класса
  • <parent_class_name> - имя базового класса, чьи свойства, методы и дочерние объекты переносятся на создаваемый класс
  • <tag_definition> - строка или набор строк, по которым данное окно находится Агентом
  • <parent_window_name> - имя родительского окна
То есть структура отличается от описания окна только первой строкой.
Важным моментом для понимания является тот факт, что winclass несколько отличается от класса в его привычном понимании по аналогии с C++, Java, Perl и подобных объектно-ориентированных языков программирования. Winclass не является некоторым типом данных, это скорее тип окон, поэтому все инстансы winclass-a являются объектами типа WINDOW. Как следствие вышеуказанных особенностей, у winclass-a нет конструкторов (по-крайней мере в явном виде), деструкторов.
Итак, как происходит группировка элементов в класс. Например, у нас есть 2 оконные декларации вида:
Code
 
[+] window MainWin wMain
   [ ] tag "Main"
   [ ] 
   [+] DialogBox dDialog
           [ ] tag "Dialog"
           [ ] 
           [+] TextField edtText
                  [ ] tag "Text"
   [ ] 
   [+] Toolbar tbTools
           [ ] tag "Tools"
[ ] 
[+] window MainWin wAnotherMain
   [ ] tag "Another Main"
   [ ] 
   [+] DialogBox dDialog
           [ ] tag "Dialog"
           [ ] 
           [+] TextField edtText
                  [ ] tag "Text"
   [ ] 
   [+] StatusBar sbStatus
           [ ] tag "Status"
[ ] 
Жирным шрифтом выделены общие составляющие. Как видно, оба окна одинаковых классов, а также содержат несколько общих элементов. На основе этих 2-х окон можно создать класс вида:
Code
 
[+] winclass MyMainWin : MainWin
   [ ] 
   [+] DialogBox dDialog
           [ ] tag "Dialog"
           [ ] 
           [+] TextField edtText
                  [ ] tag "Text"
   [ ] 
 
После этого декларации окон могут быть приведены вот к такому виду:
Code
 
[+] window MyMainWin wMain
   [ ] tag "Main"
   [ ] 
   [+] Toolbar tbTools
           [ ] tag "Tools"
[ ] 
[+] window MyMainWin wAnotherMain
   [ ] tag "Another Main"
   [ ] 
   [+] StatusBar sbStatus
           [ ] tag "Status"
Как видно запись самих окон несколько сократилась. Чем более мощный каркас мы вынесем в оконный класс, тем большую оптимизацию описания окон мы получим. Это имеет немаловажное значение для больших приложений, когда небольшие изменения интерфейса могут повлечь значительные корректировки фрейма. Также SilkTest плохо работает с файлами, у которых количество строк кода приближается к отметке 30 тысяч.
Использование переменных внутри класса, окна. Создание и использование методов.
Окна и оконные классы помимо структурированного описания оконных объектов являются еще и областями видимости. Это позволяет в описания окон или классов добавлять объявления переменных и методов. Добраться до них можно через конкретный инстанс класса. Например, переменную внутри класса можно объявить так:
Code
 
[-] window MainWin wMain
   [ ] tag "Main"
   [ ] 
   [ ] STRING sTest = "Test"
   [ ] INTEGER iValue 
То есть обычное объявление функций, с той разницей, что они располагаются внутри оконного объекта и обращение к ним осуществляется, как к элементу данного окна. В нашем примере это может быть реализовано, например, так:
Code
 
[ ] wMain.sTest = "Some line"
[ ] wMain.iValue = 0
[ ] STRING sValue = wMain.sTest
Аналогично с созданием методов. Они создаются по тому же принципу, что и функции за исключением того, что методы объявляются внутри окна или класса. Например:
Code
 
[-] window MainWin wMain
   [ ] tag "Main"
   [ ] 
   [ ] STRING sTest
   [ ] INTEGER iValue 
   [ ]
   [+] VOID Init()
           [ ] sTest = "Test"
           [ ] iValue = 0
Метод Init в коде может быть вызван так:
Code
 
[ ] wMain.Init()
По аналогии с С++ для окон и классов определено ключевое слово this, которое позволит явно указать, что обращаться надо именно к текущему инстансу окна, класса. то есть в предыдущем примере с инициализацией внутренних переменных можно явно указать, что мы инициализируем переменные этого же окна. Вот так это выглядит:
Code
 
[-] window MainWin wMain
   [ ] tag "Main"
   [ ] 
   [ ] STRING sTest
   [ ] INTEGER iValue 
   [ ]
   [+] VOID Init()
           [ ] this.sTest = "Test"
           [ ] this.iValue = 0
Если нужно, чтобы некоторый метод был доступен только в пределах некоторого окна или класса, то этот метод определяется внутри нужного объекта с ключевым словом private. Например:
Code
 
[-] window MainWin wMain
   [ ] tag "Main"
   [ ] 
   [ ] STRING sTest
   [ ] INTEGER iValue 
   [ ]
   [+] private VOID _init()
           [ ] this.sTest = "Test"
           [ ] this.iValue = 0
В случае такого описания конструкция вида:
Code
 
[ ] wMain._init()
уже должна выдать ошибку, хотя в некоторых версиях этого не происходит. Единственное ,приватный метод не виден в списке автозаполнения.
property
Каждый раз, когда необходимо изменить данные в элементе управления (ввести текст в поле ввода, установить или снять флажок с чекбокса, выбрать элемент из списка и т.д.) имеется выбор между использованием стандартных свойств или методов. Например, для ввода текста в поле ввода можно воспользоваться двумя стандартными средствами этого элемента управления:
  • использовать метод SetText ()
  • использовать свойство sValue
Например, следующие две строки выполнят одно и то же: занесут заданную строку "Text" в поле ввода Field, которое находится в окне Dialog:
  • Dialog.Field.SetText ("Text")
  • Dialog.Field.sValue = "Text"
Кроме того, можно воспользоваться общим для всех окон методом TypeKeys:
Dialog.Field.TypeKeys ("Text")
Точно также можно работать и с другими элементами управления. Например, для элемента RadioList можно использовать метод Select() или одно из свойств: sValue, iValue.
На самом деле встроенными являются лишь методы, а свойства (iValue, sValue и т.д.) определены средствами SilkTest-а в файле winclass.inc.
Точно так же можно описывать свои свойства, которые могут пригодиться в работе.
У свойств могут быть 2 метода: Set() и Get(). Первый предназначен для установки значения, второй - для его (значения) извлечения.
Например, имеем следующее описание объекта в диалоговом окне:
Code
 
[-] window DialogBox dDialog 
   [ ] tag "#1"
   [-] TextField edtField
           [ ] tag "#1"
Если мы хотим ввести какие-либо данные в это поле, мы можем воспользоваться одним из описанных выше способов. А можно немного сократить запись, используя свойства.
В качестве примера напишем свойство sField, при обращении к которому можно изменять и считывать значение из этого поля.
Выглядеть оно будет так:
Code
 
[-] property sField
   [-] STRING Get ()
           [ ] return this.edtField.sValue
   [-] VOID Set (STRING sValue)
           [ ] this.edtField.sValue = sValue
Поместить объявление этого свойства необходимо на одном уровне с полем edtField.
Теперь работа с этим полем немного упростилась:
  • чтобы ввести значение, необходимо написать dDialog.sField = "Text" (сравните со стандартным dDialog.edtField.sValue = "Text" или dDialog.edtField.SetText("Text") )
  • чтобы извлечь значение, необходимо написать STRING sData = dDialog.sField (сравните со стандартным STRING sData = dDialog.edtField.sValue или STRING sData = dDialog.edtField.GetText() ).
Это очень простой пример, однако могут встречаться и случаи сложнее.
Пример из практики: в приложении было текстовое поле с несколькими символами в нем, каждый из которых обозначал какую-то настройку. Каждый из символов влиял на функциональность всего приложения. Извлечь их было очень просто, стандартными методами SilkTest-а, а вот записать строку в это поле невозможно: строка была доступна только для чтения, а для изменения строки в этом поле служило диалоговое окно с кучей CheckBox'ов в нем. Если CheckBox включен - соответствующий символ появляется в строке. Это дополнительное окно открывалось по нажатию на кнопку рядом с полем ввода. Естественно, что каждый раз писать код для открытия этого окна и изменения значений CheckBox'ов не имеет смысла. Поэтому было описано новое свойство. Метод Get() для него был простой: использовался стандартный метод GetText(); а вот метод Set() был посложнее: он принимал строку символов, нажимал на кнопку, в диалоговом окне заполнял требуемые данные, после чего закрывал окно. Написать этот метод Set() было ненамного сложнее, чем написать эти же действия для какого-то конкретного случая, а экономия времени в дальнейшем велика: вместо кучи действий использовалось всего одно присваивание.
Некоторые особенности наследования оконных классов
Как уже было указано выше, оконные классы немного отличаются по своей сути от классов в С++, Java и т.п. Это накладывает свои особенности. Перегрузить метод оконного класса нельзя, но при этом методы могут быть переопределены в классах-наследниках и даже инстансах класса. Например, у нас есть такие объявления:
Code
 
[+] winclass BaseClass
   [ ] 
   [+] VOID Method()
           [ ] Print( "Base Class method" )
[ ]
[+] winclass DerivedClass : BaseClass
   [ ] 
   [+] VOID Method()
           [ ] Print( "Derived class method" )
[ ] 
[+] window BaseClass wBaseWin
   [ ] 
[+] window DerivedClass wDerivedWin
   [ ] 
[ ]
[+] window DerivedClass wDerivedWinRedef
   [ ] 
   [+] VOID Method()
           [ ] Print( "Redefined Derived class method" )
Жирным шрифтом подсвечено 3 переопределения метода Method для классов BaseClass, DerivedClass и окна wDerivedWinRedef. Соответственно ,если мы напишем код вида:
Code
 
   [ ] wBaseWin.Method()
   [ ] wDerivedWin.Method()
   [ ] wDerivedWinRedef.Method()
то вывод будет иметь вид:
 
[ ] Base Class method
[ ] Derived class method
[ ] Redefined Derived class method
То есть сработает ближайший переопределенный метод.
Аналогично методам срабатывают и переопределения таких атрибутов как tag и parent. То есть, если у некоторого окна не определен тег и не задано родительское окно, то эти атрибуты берутся у класса, к которому относится данное окно.
Часто могут возникнуть случаи, когда некоторый (даже стандартный) класс не содержит некоторого полезного функционала, применимого ко всем объектам данного класса. Например, у текстовых полей нет стандартной проверки на read-only. Безусловно, можно отнаследовать новый класс от данного стандартного и там определить все, что нужно. Тем не менее, в 4Test-е есть более удобная возможность для этого. Оконные класс можно пронаследовать сам от себя. Рассмотрим такой случай: у окон класса HtmlColumn первый текст в колонке воспринимается как имя колонки. Но есть случаи, когда колонки в таблице идут без заголовков, просто табулированное представление данных. В этом случае, если мы попытаемся извлечь содержимое данной колонки, но первого элемента там не окажется. Поэтому удобно определить некоторое свойство, которое могло бы позволить извлечь текст и из первой строки. Пример реализации этого:
Code
 
   [-] winclass HtmlColumn : HtmlColumn
           [ ] 
           [+] property lsValueEx
                  [+] LIST OF STRING Get()
                          [ ] LIST OF STRING lsData = {this.sCaption}
                          [ ] STRING sValue
                          [+] for each sValue in this.GetRowRangeText()
                                 [ ] ListAppend( lsData , sValue)
                          [ ] return lsData
При такой записи свойство lsValueEx класса HtmlColumn будет доступно во всех файлах, которые подключают текущий файл, в котором переопределен данный класс. Данная возмо
Динамическое обращение к окнам
Часто могут возникать случаи, когда некоторые окна не имеют фиксированного тега, но есть некоторый алгоритм, по которому мы можем его получить, или количество таких элементов может быть произвольным. Например, ссылки на странице результатов поиска в Google могут иметь самые различные атрибуты в зависимости от текста страницы, которая была найдена, строки запроса и т.п. Также и количество этих объектов зависит от многих параметров. Такие объекты во фрейм заносить или где-то статически фиксировать бессмысленно. Но обращаться к ним как-то надо.
Решение данной проблемы заключается в следующем: если вызвать функцию или метод, имя которого совпадает с именем некоторого оконного класса и в качестве параметра передать некоторую строку, то эта функция или метод вернет окно-инстанс данного класса с тегом, указанным строкой-параметром. А теперь еще раз и уже на конкретном примере. Допустим, у нас есть оконная декларация вида:
Code
 
[+] window MainWin wMain
   [ ] tag "Main"
   [ ] 
   [+] DialogBox dDialog
           [ ] tag "Dialog"
           [ ] 
           [+] TextField edtText
                  [ ] tag "Text"
и допустим, в окне wMain.dDialog есть еще текстовый элемент, который принимает значение, которое мы ввели в поле wMain.dDialog.edtText. То есть мы знаем, что это некоторое окно класса StaticText, у которого тег примет значение, совпадающее с текстом, хранящимся в wMain.dDialog.edtText. Тогда этот текстовый элемент можно получить вот такой конструкцией:
Code
 
[ ] STRING sTag    = wMain.dDialog.edtText.sValue
[ ] WINDOW wStatic = wMain.dDialog.StaticText( sTag )
или, если одной строкой:
Code
 
[ ] WINDOW wStatic = wMain.dDialog.StaticText( wMain.dDialog.edtText.sValue )
Другой достаточно распространенный пример: необходимо получить список всех окон некоторого заданного класса. Например, все текстовые поля класса TextField у некоторого окна wSomeWin. Для решения этой задачи стоит вспомнить про теги, которые основаны на индексе объекта (см. выше). По такому тегу можно найти все объекты. Как это делается:
Code
 
[ ] LIST OF WINDOW lwFields = {}
[ ] INTEGER i = 1 
[ ]
[+] while( wSomeWin.TextField( "#{i}" ).Exists() )
   [ ] ListAppend( lwFields , wSomeWin.TextField( "#{i}" ) )
   [ ] i++
Более того, если вернуться к оконной декларации для предпоследнего примера, то обращение к окну wMain.dDialog.edtText может быть реализовано вот такими способами:
Code
 
[ ] WINDOW wWin
[ ] 
[ ] wWin = wMain.dDialog.edtText
[ ] wWin = wMain.dDialog.TextField( "Text" )
[ ] wWin = wMain.DialogBox( "Dialog" ).TextField( "Text" )
[ ] wWin = MainWin( "Main" ).DialogBox( "Dialog" ).TextField( "Text" )
То есть само описание оконных деклараций - в принципе вещь необязательная, но все-таки рекомендуемая, так как при изменениях в интерфейсе пользователя лучше сделать правку в одном месте в оконной декларации, чем проходиться по всему коду и править все обращения к данному окну.
Маппинг классов
Class Map (Карта Классов) - это весьма полезный инструмент, который позволяет указать SilkTest-у, как работать с нестандартными классами в том случае, если эти классы соответствуют стандартным, но называются иначе. Особенно часто такая ситуация встречается в случае Delphi-приложений, в которых практически все классы определяются SilkTest-ом как нестандартные.
Чтобы это сделать откроем Class Map (Options - Class Map или Record - Window Declaration - Class Map), в поле Custom Class введем имя класса, который нам необходимо описать как стандартный, а в выпадающем списке справа Standard Class выберем стандартный класс, к которому мы хотим присоединить наш нестандартный класс.
Например, если на форме имеется элемент ListBox, то SilkTest будет его определять как CustomWin TListBox и, как следствие, работать с ним будет невозможно. Однако если в Class Map'e определить этот класс как ListBox, то все операции, которые можно производить с обычным ListView, станут доступны и для класса TListView. Однако так поступить можно не со всеми элементами (например, с классом GridControl это не поможет).
Обратите внимание: делать описание окон с помощью Record Window Declaration надо после того, как нестандартный класс примаплен к стандартному. Для проверки того, что вы сделали все правильно, откройте выберите пункт меню Record - Window Declaration, наведите курсор мыши на примапленный элемент и убедитесь, что в окне record Window Declaration его класс определяется не как CustomWin, а как класс, к которому вы его примапили. После этого можно записывать объявления окон.
Отдельного внимания заслуживает пункт Ignore в списке стандартных классов Class Map'a. Этот пункт предназначен для игнорирования какого-либо класса, или группы классов (в именах Custom Class'ов можно использовать символы групповой замены). После добавления такого соответствия добавленный класс будет проигнорирован и не все элементы данного класса не попадут в описание окна.
В этом случае элементы данного класса не будут отображаться в списке элементов окна Window Declaration, однако их отображение можно включить, если в этом окне нажать кнопку Options, а затем включить опцию "Show ignored windows". При включенной этой опции в колонке Identifier окна Window Declaration будет отображаться значение (Ignored).
Использовать данную возможность следует осторожно, так как можно "заигнорить" какой-нибудь важный класс. Если это просто элементы окна, то лучше оставить их как есть, а если элементы этого класса встречаются в середине иерархии объектов, то лучше воспользоваться корректировкой тэгов и переместить все дочерние объекты на уровень вверх (подробнее об этом в см. выше в разделе, посвященном тегам и работе с ними).
Пример описания окна и его дальнейшее усовершенствование
Рассмотрев основные особенности описания окон, их атрибутов, можно рассмотреть весь процесс описания окон на конкретном примере. Опишем вот такое окно:
 
Forgot your password?
HTML-код данной страницы имеет вид:
Code
 
 
<html>
<head>
<title>ATSG Manager|Login</title>
<link rel=STYLESHEET href="style.css">
<script>
</script>
</head>
 
<body>
<center>
<form name="login" action="" method=POST>
<table border=0>
   <tr align=right>
           <td><label>Login:</label></td>
           <td><input type="text" name="Login" value="" maxlength=20 size=15></td>
   </tr>
   
   <tr align=right>
           <td><label>Password:</label></td>
           <td><input type="password" name="Password" value="" maxlength=20 size=15></td>
   </tr>
 
   <tr>
           <td colspan=2>
                  <input type="checkbox" name="cbRemember"><small><label>Remember me each visit</label></small>
           </td>
   </tr>
 
   <tr>
           <td colspan=2>
                  <small><a href="">Forgot your password?</a></small>
           </td>
   </tr>
 
   <tr>
           <td align=center colspan=2>
                  <input type="button" name="btnLogin" value="Login" class=button
                  >
                  <input type="reset" name="btnReset" value="Reset" class=button>
           </td>
   </tr>
</table>
   <input type="submit" style="visibility:hidden" name="btnSubmit">
   <input type="hidden" name="Action" value="Login">
</form>
</center>
</body>
</html>
 
Создание оконных деклараций осуществляется в несколько стадий:
1.       Запись
2.       Очистка от ненужных элементов
3.       Оптимизация структуры деклараций
4.       Задание информативных имен объектам в соответствии со стандартами
5.       Добавление property
1. Запись
Запись осуществляется путем выбора меню Record > Window Declarations. Появляется диалог записи действий. Теперь осталось навести на требуемое окно (в случае веб-приложений нужно навести на область самой страницы, а не окна браузера). Через некоторое время список объектов в окне записи заполнится оконными декларациями. Когда мы там зафиксировали нужные объекты, то осталось нажать на Ctrl + Alt, чтобы приостановить запись деклараций. Затем жмем Copy to Clipboard и сгенерированная декларация окна уже в буфере обмена. Нам осталось только вставить в нужное место этот участок.
В качестве примера рассмотрим окно Логин в приложении ATSG. Вышеуказанная последовательность действий дала вот такой код:
Code
 
[+] window BrowserChild ATSGManagerLogin
   [ ] tag "ATSG Manager?Login"
   [ ] parent Browser
   [ ] 
   [+] HtmlTable HtmlTable1
           [ ] tag "#1"
           [+] HtmlColumn Login
                  [ ] tag "Login:"
                  [+] HtmlText Password
                          [ ] tag "Password:"
                  [+] HtmlCheckBox RememberMeEachVisit1
                          [ ] tag "Remember me each visit"
                  [+] HtmlText RememberMeEachVisit2
                          [ ] tag "Remember me each visit"
                  [+] HtmlLink ForgotYourPassword
                          [ ] tag "Forgot your password?"
                  [+] HtmlPushButton Login
                          [ ] tag "Login"
                  [+] HtmlPushButton Reset
                          [ ] tag "Reset"
           [+] HtmlColumn HtmlColumn2
                  [ ] tag "#2"
                  [+] HtmlTextField Login
                          [ ] tag "Login:"
                  [+] HtmlTextField Password
                          [ ] tag "Password:"
[ ] 
Очевидно, что эта запись еще далека от совершенства, поэтому постепенно переходим к следующему этапу работы с оконными декларациями.
2. Очистка от ненужных элементов
Автоматическая запись деклараций генерирует много объектов, которые нам зачастую не нужны. Это как правило обычные тексты (лейблы), которые сами по себе не несут функционала, их содержимое не варьируется, но при этом в декларации окна эти объекты занимают драгоценные строчки кода. В этом случае нам можно от них просто избавиться. В предыдущем примере красным цветом помечены как раз ненужные тексты. Убрав их, мы получим нечто вида:
Code
 
[+] window BrowserChild ATSGManagerLogin
   [ ] tag "ATSG Manager?Login"
   [ ] parent Browser
   [ ] 
   [+] HtmlTable HtmlTable1
           [ ] tag "#1"
           [+] HtmlColumn Login
                  [ ] tag "Login:"
                  [+] HtmlCheckBox RememberMeEachVisit1
                          [ ] tag "Remember me each visit"
                  [+] HtmlLink ForgotYourPassword
                          [ ] tag "Forgot your password?"
                  [+] HtmlPushButton Login
                          [ ] tag "Login"
                  [+] HtmlPushButton Reset
                          [ ] tag "Reset"
           [+] HtmlColumn HtmlColumn2
                  [ ] tag "#2"
                  [+] HtmlTextField Login
                          [ ] tag "Login:"
                  [+] HtmlTextField Password
                          [ ] tag "Password:"
[ ] 
Это уже значительно лучше, так как теперь у нас в декларации только те объекты, с которыми надо будет реально работать. Когда это достигнуто, можно переходить на следующую стадию.
3. Оптимизация структуры деклараций
Зачастую автоматическая декларация тянет за собой всю иерархию объектов и из-за этого обращение к некоторому объекту бывает слишком длинным. например, внесем некоторый текст в поле для Логина. Это на данный момент выглядит так:
Code
 
[+] main()
   [ ] Browser.SetActive()
   [ ] ATSGManagerLogin.HtmlTable1.HtmlColumn2.Login.sValue = "Text" 
Как видно, обращение слишком длинное и содержит неудобные промежуточные звенья, как HtmlTable1 и HtmlColumn2. Для того, чтобы обойти эту проблему, обратим внимание на ключевое слово tag, которое следует под каждым объектом в оконной декларации. Строка, следующая за словом tag содержит идентификатор, по которому некоторый оконный объект ищется реально в окнах. То есть присутствует разделение между именем окна в декларации и указателем на реальное окно, с которым проводить ассоциацию. Иными словами само объявление окна определяет как мы будем обращаться к окну в коде, а тэг уже определяет где искать реальное окно. То есть имя окна мы можем задавать любое, какое взбредет нам в голову, а вот тэг менять надо крайне осторожно.
Так как же можно оптимизировать иерархию? В предыдущем примере выделим фрагмент, с которым мы будем работать:
Code
 
[+] window BrowserChild ATSGManagerLogin
   [ ] tag "ATSG Manager?Login"
   [ ] parent Browser
   [ ] 
   [+] HtmlTable HtmlTable1
           [ ] tag "#1"
           ...................................................
           [+] HtmlColumn HtmlColumn2
                  [ ] tag "#2"
                  [+] HtmlTextField Login
                          [ ] tag "Login:"
                  ............................................
[ ] 
Итак, вот она вся иерархия поля Login. В СилкТесте тэг - это путь, по которому будет искаться некоторое окно. Соответственно, этот путь должен соблюдаться всегда. И в СилкТесте есть возможность поместить весь путь в один тэг. Вышеприведенный фрагмент можно описать так:
Code
 
[+] window BrowserChild ATSGManagerLogin
   [ ] tag "ATSG Manager?Login"
   [ ] parent Browser
   ....................................................
   [ ] 
   [+] HtmlTextField Login
           [ ] tag "[HtmlTable]#1/[HtmlColumn]#2/Login:"
   ....................................................
[ ] 
Обратим внимание на тэг [HtmlTable]#1/[HtmlColumn]#2/Login:. Оператор "/" в теге разделяет уровни иерархии. Каждое звено иерархии имеет вид [<имя класса>]<тег>, это означает, что на данном этапе будет искаться окно с указанным классом и указанным тегом.
Code
 
[+] window BrowserChild ATSGManagerLogin
   [ ] tag "ATSG Manager?Login"
   [ ] parent Browser
   [ ] 
   [+] HtmlCheckBox RememberMeEachVisit1
           [ ] tag "[HtmlTable]#1/[HtmlColumn]Login:/Remember me each visit"
   [+] HtmlLink ForgotYourPassword
           [ ] tag "[HtmlTable]#1/[HtmlColumn]Login:/Forgot your password?"
   [+] HtmlPushButton Login
           [ ] tag "[HtmlTable]#1/[HtmlColumn]Login:/Login"
   [+] HtmlPushButton Reset
           [ ] tag "[HtmlTable]#1/[HtmlColumn]Login:/Reset"
   [ ] 
   [+] HtmlTextField Login
           [ ] tag "[HtmlTable]#1/[HtmlColumn]#2/Login:"
   [+] HtmlTextField Password
           [ ] tag "[HtmlTable]#1/[HtmlColumn]#2/Password:"
   [ ] 
   [ ] 
[ ] 
Данная модификация значительно упрощает структуру, делает ее близкой к логической структуре (у нас есть просто окно и в нем уже кнопки, ссылки, текстовые поля). То есть иерархия объектов во фрейме напрямую не привязана к реальной иерархии окон на форме. Такую жесткую привязку обеспечивают теги. Поэтому нужно понимать такую важную вещь, что структура фрейма используется при написании кода, а теги ищут по этой структуре соответствующие окна. Это очень удобно, так как мы можем подобрать удобную структуру фрейма, которую будет удобно использовать при написании програмного кода.
4. Задание информативных имен объектам в соответствии со стандартами
Очередным усовершенствованием будет придание всем элементам фрейма понятных, простых для использования и понимания имен. Поскольку, как уже говорилось выше, имена объектов никак не влияют на распознавание реальных окон тестируемого приложения, то ничто не мешает нам подстроить имена наиболее удобным способом. Например, имя окна ATSGManagerLogin можно сократить до ATSGLogin. Это не ухудшит понимание назначения данного окна, при этом имя короче и меньше букв надо будет набирать при написании кода.
Также полезно именам объектов добавить префиксы, соответствующие тем или иным классам окон. Во-первых, это уменьшить вероятность дублирования имен объектов на одном и том же уровне иерархии (обратите внимание, у нас есть текстовое поле Login и кнопка с таким же именем). Во-вторых, это повысит читаемость кода, так как мы по имени объекта уже можем определить, что же это за объект, чтоб потом визуально поставить соответствие объекта фрейма реальному окну. И в-третьих, задание префикса объектам упрощает работу с автозаполнением. Ведь когда мы пишем код, мы как раз в первую очередь представляем, с каким классом объектов надо работать. Соответственно, при активном автозаполнении, нам достаточно набрать префикс, чтоб сразу прыгнуть на перечень объектов нужного класса.
Итого, на данном этапе мы оптимизируем имена и добавляем им префиксы, характерные для определенных классов. В результате получаем вот такое описание:
Code
 
[+] window BrowserChild wATSGLogin
   [ ] tag "ATSG Manager?Login"
   [ ] parent Browser
   [ ] 
   [+] HtmlCheckBox cbRemember
           [ ] tag "[HtmlTable]#1/[HtmlColumn]Login:/Remember me each visit"
   [+] HtmlLink lnkForgotYourPassword
           [ ] tag "[HtmlTable]#1/[HtmlColumn]Login:/Forgot your password?"
   [+] HtmlPushButton btnLogin
           [ ] tag "[HtmlTable]#1/[HtmlColumn]Login:/Login"
   [+] HtmlPushButton btnReset
           [ ] tag "[HtmlTable]#1/[HtmlColumn]Login:/Reset"
   [ ] 
   [+] HtmlTextField edtLogin
           [ ] tag "[HtmlTable]#1/[HtmlColumn]#2/Login:"
   [+] HtmlTextField edtPassword
           [ ] tag "[HtmlTable]#1/[HtmlColumn]#2/Password:"
   [ ] 
   [ ] 
[ ] 
В принципе, эта форма уже пригодна для использования. Остались только мелкие навороты.
5. Добавление property
И этими мелкими наворотами вполне могут выступать property, которые можно связать со всеми полями ввода. Это упростит ввод/чтение текста, определение состояния объектов. Обычно property удобно навешивать на текстовые поля, выпадающие списки, чекбоксы, радио-листы, а также на некоторые тексты, если в них есть полезная нам динамическая информация. Ну, и напоследок осталось навесить окну некоторый заголовок. В конечном итоге окно имеет вид:
Code
 
[ ] //*************************************************************************************************
[+] //* Window: BrowserChild wATSGLogin
   [ ] //* Description: ATSG Manager login window
   [ ] //* Access:      Navigate to ATSG Manager URL             
   [ ] //*************************************************************************************************
[+] window BrowserChild wATSGLogin
   [ ] tag "ATSG Manager?Login"
   [ ] parent Browser
   [ ] 
   [+] HtmlCheckBox cbRemember
           [ ] tag "[HtmlTable]#1/[HtmlColumn]Login:/Remember me each visit"
   [ ] 
   [+] HtmlTextField edtLogin
           [ ] tag "[HtmlTable]#1/[HtmlColumn]#2/Login:"
   [+] HtmlTextField edtPassword
           [ ] tag "[HtmlTable]#1/[HtmlColumn]#2/Password:"
   [ ] 
   [+] HtmlLink lnkForgotYourPassword
           [ ] tag "[HtmlTable]#1/[HtmlColumn]Login:/Forgot your password?"
   [+] HtmlPushButton btnLogin
           [ ] tag "[HtmlTable]#1/[HtmlColumn]Login:/Login"
   [+] HtmlPushButton btnReset
           [ ] tag "[HtmlTable]#1/[HtmlColumn]Login:/Reset"
   [ ] 
   [+] property bRemember
           [+] BOOLEAN Get()
                   [ ] return this.cbRemember.bValue
           [+] VOID Set( BOOLEAN bValue )
                  [ ] this.cbRemember.bValue = bValue
           [ ] 
   [+] property sLogin
           [+] STRING Get()
                  [ ] return this.edtLogin.sValue
           [+] VOID Set( STRING sValue )
                  [ ] this.edtLogin.sValue = sValue
   [+] property sPassword
           [+] STRING Get()
                  [ ] return this.edtPassword.sValue
           [+] VOID Set( STRING sValue )
                  [ ] this.edtPassword.sValue = sValue
   [ ] 
Понятно, что процедура навешивания property достаточно трудоемкая и настолько же рутинная, скучная. Тем не менее, для стандартных элементов управления это однотипная операция, соответственно, вполне реально сделать некоторый "генератор", который прицепит свойства автоматически. Но в конечном итоге мы получим достаточно удобную оконную декларацию, которая предоставляет достаточно много различных возможностей, пригодна для реализации некоторых подходов, которые могут позитивно сказаться на эффективности написания кода.
Основные функции и методы по работе с окнами и классами
Есть определенный набор функций и методов для работы с окнами, которые полезно знать. Нижеприведенный перечень неполон, здесь перечислены самые частоиспользуемые функции и методы. Более детально и в большем объему это все можно найти в справке к SilkTest-у, поэтому ниже будет только перечисление с небольшим описанием, чтоб было хотя бы представление о том, что такие средства существуют.
Функции
  • ClassOf - возвращает класс окна, исходя из его оконной декларации
  • WindowChildren - возвращает список окон, являющихся дочерними окну, передаваемому параметром. Результирующий список содержит непосредственные дочерние окна исходя из оконной декларации
  • WindowIsOfClass - проверяет, является ли окно, переданное первым параметром, экземпляром класса, переданного вторым параметром
  • WindowName - возвращает имя окна исходя из его оконной декларации
  • WindowParent - возвращает родительское окно исходя из оконной декларации
  • WindowTag - возвращает тег окна, который определен в оконной декларации
Методы
  • Exists - ждет появления окна втечение заданного времени
  • Click - осуществляет одинарный клик на окне
  • DoubleClick - осуществляет двойной клик на окне
  • TypeKeys - производит нажатие клавиш на заданном окне
  • GetClass - возвращает класс окна
  • GetNativeClass - возвращает фактический класс окна
  • GetTag - возвращает тег окна
  • GetName - возвращается фактическое имя окна
  • GetCaption - возвращает текст окна (для главных окон это текст заголовка)
  • GetParent - возвращает фактическое родительское окно