2.4.4. Использование функций

Назад Содержание Дальше

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

[+]<Область действия> <Тип возвращаемого значения> Имя_функции ( <аргументы> )      
	[ ]   // тело функции 

 

 

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

 

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

 

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

 

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

 

Аргументы – список величин, которые принимает функция. Представляет собой список элементов вида:

	[режим передачи] тип_данных имя_переменной [null] [optional]

,где

режим передачи – способ передачи аргументов функции ( по ссылке или по значению ). Имеется 3 режима:

 

  • in – параметр передается по значению. Данный режим используется по-умолчанию
  • out – данный параметр используется для вывода значения, то есть в переменную, переданную этим параметром будет записано значение, сформированное в данной функции
  • inout – данный параметр используется для ввода/вывода значений. Фактически это объединение 2-х предыдущих модификаторов

 

тип_данных – тип переменной передаваемой в качестве параметра

 

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

 

null – означает, что данный аргумент может содержать null-значение. Если это ключевое слово не указано и параметр содержит значение null, то в этом случае сгенерируется исключение

 

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

 

По сфере применения функции условно можно разделить на 3 группы:

  • локальные функции – функции, специфичные для конкретного файла. Это как правило, некоторый повторяющийся участок кода, который вынесен отдельно и выполняет действия, специфичные для конкретного файла или даже тесткейса
  • общие функции – функции, которые выполняют некоторые общие действия и не зависящие от тесткейса или другого блока, в котором они применяются
  • специальные функции – функции, формат которых и способ вызова специфицирован языком 4Test. Это функции с ключевыми словами testcase, multitestcase, appstate.

При разработке скриптов, как правило вначале выделяются локальные функции, а затем часть из них (если они выполняют задачи, общие для многих скриптов) делают общими и выносят в отдельный .inc файл. Если операции, проводимые общей функцией, выполняются для какого-то конкретного окна или область действия может быть локализована каким-то окном, то такие функции имеет смысл реализовать в виде метода соответствующего окна или оконного класса. Метод – это тоже функция, только её область видимости ограничена объектом, в котором она определена. Это естественный путь формирования фреймворка.

 

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

Code

	[ ]Print("Step 5: Start Application")
              [ ] apsTestApp( )
	[ ]Print("Main Application window is opened")

 

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

 

Теперь приступим к выделению повторяющихся участков кода. Ключом к поиску таких участков может быть сам сценарий тексткейса. Итак, к числу общих действий можно выделить:

  • Группа
    • Кликните на поле Enabled
    • Кликните на поле Enabled еще раз
  • Нажмите Exit

Также, в коде повсеместно встречается группа [ ]LogError( … ) return для вывода сообщения об ошибке с последующим выходом из тесткейса (опционально).
Все эти участки могут быть выделены в функции, так что рассмотрим их.

 

Вначале рассмотрим группу [ ]LogError( … ) return, так как все остальные, выбранные нами участки используют так или иначе рассматриваемую группу. Более того, данная группа может быть объединена в функциональность общего назначения, так как делать вывод ошибок нужно в любом тесткейсе. Но в данном случае мы не ограничимся лишь выводом самого сообщения об ошибке. Обычно при запусках скриптов важно знать не только, какие ошибки появились, но и где они появились ( в том числе и в какой строке кода). Соответственно, удобно было бы выводить и место ошибки. Таким образом необходимо сделать некоторую “оболочку” для LogError, чтобы выводить текст и место ошибки. Помимо этого, ошибки могут быть критичными и нет. До этого момента, если ошибка была критичной, то после LogError следовал return, что приводило к немедленному выходу из тесткейса. Но, если мы return поместим в новую функцию, то это лишь приведет выход из этой функции (но не из тесткейса), а нужно осуществить выход из тесткейса независимо от того, в каком месте сейчас выполняется скрипт. Для этих целей мы будем генерировать исключения с помощью оператора raise. У данного оператора имеется 2 операнда: код исключения и текст сообщения. В качестве кода исключения возьмем константу E_ABORT ( конечно можно описать свою константу, которая больше соостветствует ситуации, но в данном случае не будем забивать фрейм, когда есть стандартные средства ). Итак, оформим функцию Error, которая выдает информацию об ошибке и генерирует исключение. Изначально код выглядит так:

Code

[+] VOID Error(STRING sErrMsg)
	[ ] LogError(sErrmsg)
	[ ] raise E_ABORT,sErrMsg

 

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

Code

[+] VOID Error(STRING sErrMsg, BOOLEAN bCritical NULL optional)
	[+] if( IsNull(bCritical) )
		[ ] bCritical = TRUE
	[ ] LogError(sErrmsg)
	[+] if( bCritical )
		[ ] raise E_ABORT,sErrMsg

 

Красным цветом помечены изменения, которые мы внесли. Теперь нужно добавить вывод списка вызовов, чтобы в отчете четко выявить, в каком месте произошла ошибка. Для этих целей нам подойдет стандартная функция ExceptPrint, которая печатает список вызовов функций, в ходе которого было вызвано исключение. Но тут возникает проблема – исключение генерируется только если ошибка является критичной. Для того, чтобы исключение было сгенерировано в обоих случаях, а тесткейс прекратился только в случае критичной ошибки, нужно осуществить перехват исключения и генерации нового, в случае критичной ошибки. Исключение перехватывается блоком do … except, а чтобы не писать повторно raise E_ABORT,sErrMsg, можно воспользоваться ключевым словом reraise, что позволяет снова сгенерировать перехваченное исключение. Учитывая данные требования запишем код функции в виде:

Code

[+] VOID Error(STRING sErrMsg, BOOLEAN bCritical NULL optional)
	[ ] LIST OF CALL lcCalls
	[ ] CALL cCall
	[ ] 
	[+] if( IsNull(bCritical) )
		[ ] bCritical = TRUE
	[ ] LogError(sErrMsg)
	[ ] 
	[+] do
		[ ] raise E_ABORT,sErrMsg
	[+] except
		[ ] ExceptPrint()
		[+] if( bCritical )
			[ ] reraise

 

В последнем примере исключение внутри функции не будет только в самом конце, если ошибка отмечена как критичная. Всё, данная функция уже готова к применению. Поскольку эта функция является общей (она не зависит от специфики того или иного тесткейса), то её надо поместить в отдельный файл и добавить соответствующий заголовок, в котором описано действие данной функции. Создадим файл common.inc рядом с файлами фрейма и скрипта со следующим содержимым:

Code

[ ] //*************************************************************************************************
[ ] //* File: common.inc
[ ] //*
[ ] //* Description: This file contains common functionality for ATC development
[ ] //* Author:  
[ ] //* 
[ ] //*************************************************************************************************
[ ] 
[ ] //*************************************************************************************************
[+] //* Function: VOID Error(STRING sErrMsg, BOOLEAN bCritical NULL optional)
	[ ] //* Description: This function prints Error message with list of function calls
	[ ] //*              and raises an exception if error is critical
	[ ] //* Arguments: STRING sErrMsg - error message text
	[ ] //*            BOOLEAN bCritical - optional parameter which defines whether error is critical. 
	[ ] //*                                TRUE by default
	[ ] //* Return values: none
	[ ] //* Generated Exceptions: E_ABORT - error is critical
	[ ] //*************************************************************************************************
[+] VOID Error(STRING sErrMsg, BOOLEAN bCritical NULL optional)
	[+] if( IsNull(bCritical) )
		[ ] bCritical = TRUE
	[ ] LogError(sErrMsg)
	[ ] 
	[+] do
		[ ] raise E_ABORT,sErrMsg
	[+] except
		[ ] ExceptPrint()
		[+] if( bCritical )
			[ ] reraise
[ ] 

 

Обратите внимание на комментарии. Тут присутствует заголовок файла, а также заголовок функции.

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

 

Теперь осталось подключить данный файл, для чего в файле, где находится код нашего тесткейса ( TestApp.t ), в блоке подключения файлов допишем строку:

Code

[ ] use ".\common.inc"

 

Теперь внедрим данную функцию в наш код. Проблема возникает в том, что кода уже написано немало (около 350 строк) и последовательно заменять все вхождения LogError – уже довольно-таки трудоемкая задача. В данном случае удобно поступить так:

  • Выбираем меню Edit > Replace
  • В поле Find What вводим LogError, а в поле Replace With вводим Error и жмем на кнопку Replace All.
  • В поле Find What вводим return, а поле Replace With оставляем пустым и жмем на кнопку Replace All
  • По мере необходимости находим все вызовы функции Error и дописываем второй параметр FALSE, если данная ошибка не является критичной.

Мы внедрили данную функцию, поэтому в дальнейшем, при написании тесткейса мы будем ее использовать.

 

Идем дальше.Для пары действий “Кликните на поле Enabled – Кликните на поле Enabled еще раз” есть 2 варьируемых элемента (которые меняются). Это главное окно и элемент управления, который надо проверять на активность. Все остальные элементы и действия идентичны. Данная функциональность является специфической для данного тесткейса, поэтому её достаточно сделать private и разместить в одном файле с тесткейсам в отдельном блоке, отведенном для локальных функций (см. пункт 2.1). Функция имеет вид:

Code

[+] // local functions
	[ ] //*************************************************************************************************
	[+] //* Function: VOID ClickEnabledAndCheck(WINDOW wMain, WINDOW wCtrl)
		[ ] //* Description: Local function for ATC TestApp_Menu Clicks on Enabled checkbox twice and 
		[ ] //*              checks that required control is disabled at first time and enabled at 
		[ ] //*              second time
		[ ] //* Arguments: WINDOW wMain - main dialog window to click Enabled checkbox in 
		[ ] //*            WINDOW wCtrl - control to check enabled/disabled state
		[ ] //* Return values: none
		[ ] //*************************************************************************************************
	[+] private VOID ClickEnabledAndCheck(WINDOW wMain, WINDOW wCtrl)
			[+] with wMain
				[ ] .cbEnabled.Click()
				[+] if( wCtrl.bEnabled )
					[ ] Error("""{wCtrl.sCaption}"" field is still enabled")
				[ ] 
				[ ] .cbEnabled.Click()
				[+] if( !wCtrl.bEnabled )
					[ ] Error("""{wCtrl}"" field is not enabled",FALSE)
					[ ] .cbEnabled.bValue = TRUE
		[ ] 

 

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

Code

		............................................................
			[ ] .cbEnabled.Click()
			[+] if( .cbTheCheckBox.bEnabled )
				[ ] LogError("""The Check box"" field is still enabled")
			[ ] 
			[ ] .cbEnabled.Click()
			[+] if( !.cbTheCheckBox.bEnabled )
				[ ] LogError("""The Check box"" field is not enabled")
				[ ] dCheckBox.cbEnabled.bValue = TRUE
		............................................................

 

меняется на

Code

		............................................................
			[ ] ClickEnabledAndCheck(dCheckBox,.cbTheCheckBox)
		............................................................

 

Запись заметно сократилась, данную функцию можно использовать и в других шагах для других окон.

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

 

 

И наконец, рассмотрим последний фрагмент кода, который можно вынести в функцию. Это нажатие на кнопку Exit и проверка на закрытие окна. В данном случае мы работаем только с диалоговым окном. Следует обратить внимание на то, что все нужные нам диалоговые окна, содержащие кнопку Exit во фрейме описаны одним оконным классом TestApp_Controls. Исходя из этих наблюдений, можно реализовать необходимые действия в виде метода данного класса. Откроем TestApp.inc, раскроем объявление оконного класса TestApp_Controls и добавим метод Exit. Оконный класс примет вид:

Code

	[+] winclass TestApp_Controls : DialogBox
		[ ] parent wTestApp
		[+] PushButton btnExit
			[ ] tag "Exit"
		[+] PushButton btnPopup
			[ ] tag "Popup"
		[+] CheckBox cbEnabled
			[ ] tag "Enabled"
		[ ] 
		[ ] //*************************************************************************************************
		[+] //* Method: BOOLEAN Exit()
			[ ] //* Description: Clicks on Exit button and returns result of this operation
			[ ] //* Arguments: none
			[ ] //* Return values: TRUE - window was closed, FALSE - otherwise
			[+] //*************************************************************************************************
		[+] BOOLEAN Exit()
			[ ] this.btnExit.Click()
			[ ] return !this.bExists

 

Итак, данный метод работает для всех экземпляров класса TestApp_Controls и возвращает TRUE, если диалоговое окно было закрыто. В нашем тесткейсе есть пока что 3 участка, где этот метод можно применить: шаги 6, 7, 8. Замена выглядит примерно следующим образом: заменяем код

Code

	................................................................................................
	[-] Print("Step 6: For each menuitem of menu Controls perform the following operations: {chr(10)}"+
          "        - Select Item{chr(10)}"+
          "        - Click on Exit button")
		[ ] 
	................................................................................................
			[ ] @("d{sValue}").btnExit.Click()
			[+] if( @("d{sValue}").Exists() )
				[ ] Error("Dialog ""{sValue}"" wasn't closed")
		[ ] 
	[ ] Print("- Appropriate dialog appears{chr(10)}"+
          "- Dialog is closed")
	................................................................................................
	[+] Print("Step 7: {chr(10)}"+...)
		[ ] wTestApp.Control.TextField.Pick()
		[+] with dTextField
	................................................................................................
			[ ] .btnExit.Click()
			[+] if(.Exists() )
				[ ] LogError("Text Field dialog wasn't closed")
	[ ] Print("- Text Field window is opened{chr(10)}"+...)
	................................................................................................
	[+] Print("Step 8: {chr(10)}"+...)
		[ ] wTestApp.Control.CheckBox.Pick()
		[+] with dCheckBox
	................................................................................................
			[ ] .btnExit.Click()
			[+] if(.Exists() )
				[ ] LogError("CheckBox dialog wasn't closed")
	[ ] Print("- Check Box window is opened{chr(10)}"+...)
	................................................................................................

 

на следующий код

Code

	................................................................................................
	[-] Print("Step 6: For each menuitem of menu Controls perform the following operations: {chr(10)}"+
          "        - Select Item{chr(10)}"+
          "        - Click on Exit button")
		[ ] 
	................................................................................................
			[+] if( !@("d{sValue}").Exit() )
				[ ] Error("Dialog ""{sValue}"" wasn't closed")
		[ ] 
	[ ] Print("- Appropriate dialog appears{chr(10)}"+
          "- Dialog is closed")
	................................................................................................
	[+] Print("Step 7: {chr(10)}"+...)
		[ ] wTestApp.Control.TextField.Pick()
		[+] with dTextField
	................................................................................................
			[+] if(.Exit() )
				[ ] Error("Text Field dialog wasn't closed")
	[ ] Print("- Text Field window is opened{chr(10)}"+...)
	................................................................................................
	[+] Print("Step 8: {chr(10)}"+...)
		[ ] wTestApp.Control.CheckBox.Pick()
		[+] with dCheckBox
	................................................................................................
			[+] if(.Exit() )
				[ ] Error("CheckBox dialog wasn't closed")
	[ ] Print("- Check Box window is opened{chr(10)}"+...)
	................................................................................................

 

Участки замены помечены красным.

 

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


Назад Содержание Дальше