Одной из наиболее серьезных проблем при разработке автотестов ( особенно функциональных на уровне GUI ) является синхронизация выполнения тестов с работой тестируемого приложения. Иными словами, действия, которые выполняются в автоматическом тесте, должны осуществляться именно в тот момент, когда приложение находится в требуемом для данного действия состоянии. В противном случае мы можем получить картину, когда тест пытается делать клики, вводить текст, в то время как форма, над которой осуществляются данные действия, отсутствует. В результате, наш тест идет проторенными методами, но совсем непонятными путями. Если при этом нет никаких механизмов выравнивания состояния, то подобное может серьезно подкосить выполнение пакета тестов в целом.
Как результат, нужно обеспечить темп работы теста таким образом, чтобы он соответствовал темпу работы тестируемого приложения. Как это сделать?
Наиболее простым вариантом является установка задержек. Практически во всех средствах для автоматизации функционального тестирования есть инструкции, которые просто делают паузу в выполнении. Так, во многих решениях присутствуют функции вида: sleep( nSec ) или wait( nSec ). В TestComplete это делается вызовом BuiltIn.Delay(), в Java для этого есть вызов Process.sleep( ). Все эти решения имеют схожую структуру - единственный параметр, указывающий время, на которое установить паузу.
Преимущества данного решения:
- Простота - использование встроенной функции/метода достаточно простое и понятное
- В некоторых случаях подобные вставки позволяют избежать "заклинивания" выполнения, когда 2 достаточно ресурсоемкие операции выполняются друг за другом ( в частности в SilkTest в некоторых случаях пауза в 1 секунду позволяла избегать заклинивания выполнения команд Агента ).
- Достаточно эффективное решение для операций, которые имеют фиксированное время "опоздания", например, появление окна сообщения, выданного клиентским скриптом на веб-странице. В этом случае пауза нужна просто для того, чтобы четко синхронизировать тест с моментом точного появления окна
Тем не менее, у данного решения есть и недостатки:
- Нерациональное использование времени выполнения - некоторые операции, особенно, обработка различных клиент-серверных запросов, могут занимать различное время, даже для одной и той же команды. Соответственно, паузу целесообразно делать на интервал времени, покрывающий максимальное время ожидания. В результате, если действие выполнилось раньше, то пауза все равно действует фиксированное время, отчего мы теряем до нескольких минут на одном подобном ожидании. Если подобных действий будет много, то суммарная потеря времени выполнения будет составлять вплоть до нескольких часов.
- На разных средах тестируемое приложение может работать с разной скоростью, соответственно, нужно во всех вхождениях инструкции для паузы перестраивать время ожидания.
- Подобные паузы не отражают причин, по которым они проставлены, из-за чего подобный код весьма затруднительно читать. Также в тестовом коде может быть слишком много подобных инструкций, отчего код разрастается весьма ощутимо без видимых на то причин
- Подобные паузы не гарантируют, что тестируемое приложение достигло нужного состояния
Еще одним решением является возможность притормозить выполнение автоматического теста. Одним из преимуществ автоматического выполнения тестов является скорость выполнения инструкций, но зачастую это становится и главным недостатком, так как инструкции начинают выполняться настолько быстро, что встроенные обработчики тестируемого приложения не успевают среагировать, отчего появляется много ошибок, которые вручную не воспроизводятся в принципе. Во многих более-менее развитых системах для автоматизированного функционального тестирования имеются настройки, позволяющие замедлить операции ввода текста и действий мыши. Зачастую это делается путем установки соответствующих настроек. В некоторых решениях есть специальные инструкции, регулирующие скорость выполнения тестов. Например, в библиотеке Watir есть такое поле как Watir:IE:speed , которое принимает значения :slow или :fast, в зависимости от того, какую скорость выполнения мы хотим задать. Selenium RC имеет аналог - метод setSpeed основного клиентского класса. Какие у данного подхода есть преимущества:
- Простота - в данном случае достаточно в одном месте настроить скорость выполнения
- Возможность подстроить тесты под реальный темп работы, который обеспечивает пользователь
- Оптимальный объем програмного кода тестов - нет необходимости повсеместной установки пауз, в коде достаточно проставить только выполняемые действия
Часть недостатков предыдущего способа (простановка пауз) устраняется при этом подходе, но тем не менее часть из них по-прежнему актуальна:
- Нерациональное использование времени выполнения - значительная часть задержек при данном подходе могут оказаться нецелесообразными, а в результате мы можем еще и нивелировать такое преимущество автоматизации тестирования как высокая скорость выполнения. Например, нам нужно заполнить несколько полей, а затем нажать на кнопку. Заполнение полей было бы удобно выполнить в нормальном темпе, когда они заполняются быстро, а не делать паузу в секунду и более. На этом мы теряем много времени
- Практически не стабилизирует работу при выполнении запросов, занимающих достаточно произвольное время выполнения. Фактически, данный подход абсолютно не решает проблему синхронизации операций с достаточно широким интервалом времени выполнения.
- Нет никакой гарантии, что тестируемое приложение достигло нужного состояния
Таким образом, данный подход более-менее жизнеспособен разве что для локальных приложений, в которых как минимум отлажена навигация по окнам.
Оба вышеперечисленных подхода не решают одну проблему - контроль над состоянием приложения. А ведь это очень важный момент, так как если приложение вышло из состояния, на которое расчитан тест, то фактически выполнение теста теряет свой смысл и его надо бы либо прекратить, либо восстановить нужное состояние. Но для этого вначале нужно диагностировать, что тестируемое приложение начало вести себя неестественно. Для решения данной проблемы существуют различные методы, которые позволяют опросить тестируемое приложение. Например, все средства автоматизированного функционального тестирования на уровне GUI имеют в своем арсенале функции/методы, проверяющие существование того или иного объекта. Это, как правило, методы вида Exists (в SilkTest, RFT, Waitr - метод, в TestComplete - это свойство ), win_exists, obj_exists (WinRunner) или что-то типа isElementPresent, как это реализовано в SeleniumRC. Подобные решения проверяют наличие некоторого элемента. Соответственно синхронизация в данном случае реализует следующий алгоритм:
{syntaxhighlighter brush: java;fontsize: 100; first-line: 1; }bObjFound = false;
while( !timeOutExeeded ) {
if( elementPresent( element ) ) {
bObjFound = true;
break;
}
verifyTimeOut();
}
if( !bObjFound ) {
Error( “No object available” );
}{/syntaxhighlighter}
Вышеприведенный псевдо-код работает по принципу зацикливания выполнения теста до тех пор, пока не появится нужный элемент либо пока не выйдет максимальное время ожидания. Максимальное время ожидания - это величина, которая устанавливается в зависимости от скорости выполнения приложения. Зачастую, это оценочное время выполнения типичного запроса при нормальной работе системы. Подобный шаблон изначально встроен для проверки на загрузку страниц. В частности у Watir есть такой метод, как Watir::IE.wait . В SeleniumRC присутствуют методы waitForFrameToLoad, waitForPageToLoad, waitForPopup и т.п.
Поскольку эти решения позволяют контролировать наличие некоторых объектов, то это во многом позволяет контролировать изменение состояния приложения, так как во многом состояние приложения определяется наличием или отсутствием того или иного окна или элемента. Но есть более обобщенный случай, по которому изменение состояния приложения выражается в изменении состояния некоторого объекта. То есть, нам нужно дожидаться, когда некоторый объект примет нужное состояние. В TestComplete имеется метод WaitProperty, который наиболее ярко отражает интерфейс для подобного решения. Этот метод имеет вид:
{syntaxhighlighter brush: java;fontsize: 100; first-line: 1; }obj.WaitProperty( propertyName , propertyValue , timeOut ){/syntaxhighlighter}
Данный метод возвращает True, если свойство, имя которого задано в propertyName принимает значение, заданное в propertyValue до того, как выйдет время ожидания timeOut. Иначе возвращается False. Подобное решение может быть реализовано и в других средствах автотестирования (причем, полезно иметь в арсенале аналоги ). В этом случае синхронизация сводится к проверке перехода системы в нужное состояние ( а точнее переход некоторого контрольного элемента в нужное состояние ).
Безусловно, от этого вырастет объем кода, но при этом:
- автотест выполняется не более чем нужно ( как только нужное событие наступает, выполнение теста идет дальше )
- четко фиксируется переход между состояниями
- Поскольку точка верификации привязана к четко указанному состоянию, то мы можем выдать информативное сообщение об ошибке, если приложение не перешло в нужное состояние
- улучшается читаемость кода, так как по подобным точкам верификации мы можем сопоставить тестовый код с ожидаемым состоянием системы
Это были наиболее типичные методы решения проблемы синхронизации. Использование наиболее эффективных решений позволит вдальнейшем упростить разработку и поддержку тестов, особенно если учесть тот факт, что на этапе разработки порядка 60% ошибок автотестов приходятся на ошибки синхронизации. Поэтому правильный подход к решению проблем синхронизации на ранних стадиях автоматизации тестировнаия позволит значительно сократить затраты на поддержку тестов.