About company Our development services Portfolio. Case studies Support Testimonials Contacts Brutka Ltd: web site development and custom software development

Information technology news archive


Test Run: AJAX Test Automation

В течение прошлого года популярность веб-приложений, использующих технологию AJAX (Asynchronous JavaScript And XML), устойчиво росла. Веб-приложения, корректно написанные с применением технологии AJAX, значительно выигрывают в эффективности и удобстве эксплуатации по сравнению с веб-приложениями, не использующими этой технологии. Однако, поскольку веб-приложения AJAX работают асинхронно, традиционные синхронные методы автоматизации тестирования, как правило, к ним неприменимы. В этом месяце я рассказываю о методе, позволяющем реализовать простую автоматизацию тестирования функциональных возможностей веб-приложений AJAX.
Начнем эту колонку с демонстрации двух экранных снимков. Это приложение имитирует картографическую службу, такую как Microsoft® Windows Live™ Local. Когда пользователь щелкает один из элементов управления в виде задающей направление кнопки, приложение выбирает на веб-сервере соответствующую фотокопию карты и отображает новую карту в центральной области экрана. Правда, по снимку экрана не видно, что веб-приложение использует Microsoft ASP.NET AJAX для асинхронной отправки и получения фотокопий карт. Безусловно, разрабатываемое пользователем веб-приложение будет более сложным, но метод, о котором я расскажу, легко обобщается на произвольные сложные приложения и работает для любого, адаптированного к AJAX приложения, независимо от технологии его разработки.
Ручное тестирование приложения, будет затяжным, неэффективным и утомительным занятием. Более перспективным подходом к тестированию является автоматизация теста. Тестовое окружение представляет собой простую страницу HTML, состоящую из двух рамок. Правая рамка содержит веб-приложение без модификаций или инструментальных средств. В левой рамке находится одиночная страница HTML, содержащая код JavaScript, который манипулирует веб-приложением в соседней рамке, используя модель DOM (document object model) Internet Explorer®.
Хотя этот метод тестирования специально предназначен для манипулирования приложениями, использующими асинхронные операции, он также работает и для веб-приложений, использующих традиционный синхронный механизм запросов/ответов по протоколу HTTP. В данной колонке я вкратце опишу тестируемое приложение, так чтобы можно было понять, почему для AJAX-приложений традиционные методы автоматизированного тестирования неэффективны. Затем я подробно опишу тестовое окружение, и объясню, как можно модифицировать и обобщить представленный здесь подход для собственных нужд разработчика. Я полагаю, этот метод окажется полезным дополнением к профессиональному инструментарию разработчиков и тестировщиков.
Тестирование веб-приложения
Я разработал картографическое приложение с применением технологии AJAX, воспользовавшись библиотекой кодов ASP.NET AJAX, которая существенно упрощает создание AJAX-приложений. Вместо реальных изображений карт я использовал девять фиктивных изображений, содержащих цифры от 1 до 9 и имеющих имена от 1.jpg до 9.jpg. (см. в этом выпуске колонку Джеффа Прозайза "Мерзкий код" с примером использования Microsoft TerraServer для получения реальных фотокопий карт.) Мой миниатюрный мир состоит из сетки размером 3 х 3 с цифрами 1, 2 и 3 в первом ряду, цифрами 4, 5 и 6 во втором ряду и цифрами 7, 8 и 9 в третьем ряду. Цифра 5 является центром этого мира, и если пользователь щелкнет кнопочный элемент управления под названием North (Север), находясь на цифре 5, приложение должно отобразить цифру 2.
Если текущая карта находится в верхнем ряду (1,2,3) мира, нажатие кнопки North (Север) эффекта не возымеет. В противном случае я получаю цифру, обозначающую расположение текущей карты, вычитаю из нее 3, определяю соответствующее новое изображение в формате JPEG и обновляю свойство ImageUrl элемента управления Image. Обработчики кнопочных элементов управления South (Юг), East (Восток) и West (Запад) действуют аналогично.
Хочу подчеркнуть, что я намеренно не привожу здесь хорошие примеры из практики программирования ради краткости и удобочитаемости кодов. В реальном веб-приложении логика кода обычно организует доступ к данным и их выбор в серверной части базы данных SQL или хранилища XML и обновление с их помощью состояния приложения. Поскольку метод, представляемый в данной колонке, осуществляет тестирование веб-приложения с помощью модели DOM обозревателя Internet Explorer, то не имеет значения, как определяется состояние приложения - любой пользовательский ввод приводит к изменению состояния приложения, что отражается в его пользовательском интерфейсе, к которому можно получить доступ через DOM Internet Explorer.
Традиционный подход без применения технологии AJAX состоит в отправке веб-серверу запроса по протоколу HTTP с передачей информации запроса в объекте Form или в строке запроса. Картографическое приложение работает и без применения технологии AJAX, но имеет при этом два недостатка. Во-первых, поскольку механизм запросов/ответов по протоколу HTTP является синхронным, то в течение промежутка времени, пока запрос обрабатывается веб-сервером, и до прихода клиенту ответа пользователь не может взаимодействовать с веб-приложением. Во-вторых, в большинстве случаев запрос HTTP инициирует создание ответа в виде целой страницы, так что, когда Internet Explorer получает ответ, в окне перерисовывается вся страница. В случае относительно быстрых запроса и ответа это приводит к раздражающему эффекту мерцания страницы, а в случае медленных - к нечеткости страницы, что еще хуже.
AJAX решает обе этих проблемы. При использовании технологии AJAX запросы отправляются по протоколу XMLHTTP, а не HTTP. Запросы XMLHTTP являются асинхронными. Так что, пока за кадром обрабатывается запрос XMLHTTP, пользователь может продолжать работать с веб-приложением. И когда приходит ответ XMLHTTP, объектная модель документа (DOM) Internet Explorer используется для перерисовки не всей страницы, а только той области веб-страницы, которая содержит новые данные.
Чтобы реализовать преимущества AJAX в моем картографическом приложении для ASP.NET, я воспользовался оболочкой Microsoft ASP.NET AJAX, а не примитивным JavaScript. Она существенно более проста в использовании, поскольку при ее установке предоставляется шаблон веб-узла AJAX Visual Studio®. Выбор этого шаблона добавляет к проекту веб-приложения необходимые ссылки на сборки. Чтобы воспользоваться в моем картографическом приложении функциональными возможностями AJAX, я добавил к своему исходному файлу следующий тег:
<asp:ScriptManager ID="sm" runat="server" />
Затем я в элементе управления UpdatePanel ASP.NET AJAX создал свой элемент управления Image (который будет обновляться после асинхронного запроса-ответа) следующим образом:
<asp:UpdatePanel ID="up1" runat="server">
<ContentTemplate>
<asp:Image ID="Image1" runat="server" ImageUrl="~/5.JPG"
(other attributes omitted) />
</ContentTemplate>
<Triggers>
<asp:AsyncPostbackTrigger ControlID="Button1" EventName="Click" />
<asp:AsyncPostbackTrigger ControlID="Button2" EventName="Click" />
</Triggers>
</asp:UpdatePanel>
И это все, что нужно сделать. Все малоприятные хлопоты, такие как создание объекта XMLHTTP, перехват асинхронного ответа, обработка ошибок, учет различия обозревателей и т.д. оболочка берет на себя.
Обратите внимание, что я разрешаю пересылку асинхронных запросов/ответов AJAX только при событии щелчка для элементов управления Button1 (North) и Button2 (South), а не для всех четырех задающих направление элементов управления. Я сделал это с целью иллюстрации того, что метод тестирования, представляемый в колонке текущего месяца, работает как для асинхронных, так и для синхронных запросов. И если оценить работу этого приложения, скачав его по приложенной к данной статье ссылке, то можно увидеть существенно различающуюся эффективность запросов, выполняемых с кнопок North (Север) и South (Юг) (с использованием технологии AJAX) и с кнопок East (Восток) и West (Запад) (без использования технологии AJAX).
Большинство традиционных методов автоматизации тестирования просто не работают с веб-приложениями, построенными на технологии AJAX. Наиболее фундаментальным способом тестирования функциональных возможностей веб-приложения является отправка на веб-сервер программными средствами запроса HTTP, аналогичного запросу пользователя, перехват ответа HTTP и последующее рассмотрение ответа с целью определения результата "прошел/не прошел". Этот подход не работает применительно к приложениям, построенным на технологии AJAX, поскольку они используют специальные запросы по протоколу XMLHTTP.
Другой традиционный подход состоит в том, чтобы использовать JavaScript для манипуляции моделью DOM обозревателя Internet Explorer с целью отправки запроса на веб-сервер, ждать, пока не будет зафиксировано событие загрузки (сигнализирующее о том, что клиент получил и загрузил ответ), после чего использовать JavaScript и модель DOM обозревателя Internet Explorer для проверки нового состояния веб-страницы и определения результата "прошел/ не прошел". Проблема этого подхода состоит в том, что поскольку AJAX работает асинхронно, нельзя использовать событие загрузки, чтобы сказать, когда ответ был получен клиентом. Однако этот подход можно модифицировать, создав простую автоматизацию тестирования построенных на технологии AJAX приложений. Вместо использования события загрузки для сигнализации о поступлении ответа, можно написать код, который следит за приложением в ожидании некоторого определенного изменения и затем передает управление функции обратного вызова.

Автоматизация тестирования
Система тестового окружения приложения, разработанного с применением технологии AJAX , состоит из трех файлов.. Собственно окружение, определяемое файлом TestHarness.aspx, представляет собой простую веб-страницу, состоящую из двух рамок. В правой рамке содержится тестируемое веб-приложение, построенное на технологии AJAX. В левой рамке содержится страница тестового сценария, снабженная минимальным пользовательским интерфейсом и всем кодом JavaScript, необходимым для манипуляций с тестируемым веб-приложением и его исследования. С целью обеспечения совместимости я решил сделать все три страницы в виде .aspx-файлов, хотя и мог использовать для создания тестового окружения и страниц тестового сценария основные функции HTML. Ниже приведен полный исходный текст страницы TestHarness.aspx:
<html>
<head>
<title>Test Harness for AJAX Web Apps</title>
</head>
<frameset cols="45%,*">
<frame src="http://localhost/AjaxTest/TestScenario001.aspx"
name="leftFrame">
<frame src="http://localhost/AjaxApplication/Default.aspx"
name="rightFrame">
</frameset>
</html>
Рис. 4. Структура тестового окружения (Щелкните изображение, чтобы увеличить его)
Видно, что основная страница тестового окружения фактически представляет собой контейнерный файл. Прогон тестового сценария запускается путем вызова программно-определяемой функции asyncCall, которая заставляет тестируемое приложение отправлять асинхронный запрос XMLHTTP на веб-сервер, а также вызывает программно-определяемую функцию задержки. Пока веб-сервер обрабатывает запрос и возвращает асинхронный ответ, функция задержки работает в цикле, время от времени зондируя состояние веб-приложения. Как только клиентом ответ будет получен, а состояние приложения будет обновлено, функция задержки обнаружит изменение состояния веб-страницы, после чего может быть сделан очередной асинхронный вызов.

Содержание функции asyncCall
Сердцем моего метода автоматизации тестирования AJAX-приложений являются две работающих вместе программно-определяемых функции: asyncCall и функция запаздывания. Ниже описан заложенный в функцию asyncCall метод:
function asyncCall(action, checkFunc, arg, callback, pollTime)
{
numTries = 0;
action();
window.setTimeout("delay(" + checkFunc + ", " + "'" + arg +
"'" + ", " + callback + ", " + pollTime + ")", pollTime);
}
В функцию передается пять параметров. Первый параметр, action, представляет собой указатель функции на некую подпрограмму, использующую модель DOM обозревателя Internet Explorer для запуска действия, инициирующего асинхронный запрос XMLHTTP. Второй параметр, checkFunc, является указателем функции на подпрограмму, возвращающую значение true (истина), когда состояние веб-приложения указывает на факт завершения выполнения асинхронного ответа. Третий параметр, arg, является аргументом, передаваемым функции checkFunc. Четвертый параметр, callback, является указателем функции на подпрограмму, которая должна быть запущена, как только асинхронный ответ будет выполнен. И последний параметр, pollTime, указывает в миллисекундах, каким должен быть временной промежуток между вызовами в функции задержки.
Сначала внутри функции asyncCall устанавливается равным нулю значение глобального счетчика numTries. Эта переменная используется для отслеживания количества вызовов функции задержки, чтобы после заданного количества попыток можно было выйти и проверить, пришел ли асинхронный ответ. Затем вызывается аргумент action, который, в свою очередь, инициирует асинхронный запрос. Нужно отметить, что круглые скобки, стоящие рядом с именем функции, являются синтаксическим механизмом вызова функции.
Далее наступает время ухищрений: вызывается встроенная функция window.setTimeout. Как можно видеть, setTimeout принимает два аргумента. Первый аргумент представляет собой оператор JavaScript, который выполняется только один раз. Второй аргумент – время в миллисекундах для задержки выполнения первого аргумента. Если внимательно изучить код asyncCall, то можно увидеть, что первый аргумент функции setTimeout разрешает следующее:
delay(checkFunc, 'arg', callback, pollTime)
Вторым аргументом попросту является pollTime. После задержки на pollTime миллисекунд запускается программно-определяемая функция задержки.
Вызов функции asyncCall выглядит примерно так:
asyncCall(clickNorth, imgIsTwo, "2", clickSouth, 200);
Этот вызов может интерпретироваться, как "запуск функции clickNorth, затем переход к циклу задержки, вызывающему функцию imgIsTwo с аргументом '2' каждые 200 миллисекунд, и, когда функция imgIsTwo возвратит, наконец, значение true (истина), передача управления функции clickSouth".
Наличие одинарных кавычек по обеим сторонам arg в определении метода asyncCall необходимо. Без одинарных кавычек в определении asyncCall параметр передавался бы по ссылке, как показано далее:
asyncCall(doThis, findX, "X", doThat, 200);
Такой вызов генерировал бы сообщение об ошибке, говорящее, что переменная X не определена. С одинарными кавычками параметр передается по значению, и это как раз то, что необходимо в данной ситуации.
Существует интересный альтернативный способ вызова функции window.setTimeout внутри asyncCall. Обычно это записывают примерно так:
window.setTimeout("delay(" + checkFunc + ", " + "'" + arg +
"'" + ", " + callback + ", " + pollTime + ")", pollTime);
Вместо этого можно использовать свойство безымянной функции JavaScript и записать так:
window.setTimeout(
function(){delay(checkFunc, arg, callback, pollTime);},
pollTime);
Поскольку символы кавычек и конкатенация строк удаляются, эта программа выглядит несколько чище, чем ее небезымянная альтернатива. Она также работает несколько эффективнее, потому что небезымянная альтернатива требует, чтобы обозреватель создавал новую среду для обработки сценария. Однако без одинарных кавычек, выделяющих аргумент arg для checkFunc, не очевидно, что он передается по значению (хотя простой комментарий мог бы предотвратить любое возможное недоразумение).

Содержание функции задержки
Теперь рассмотрим работающую в паре с asyncCall функцию задержки:
function delay(checkFunc, arg, callback, pollTime)
{
++numTries;
if (numTries > maxTries) finish();
else if (checkFunc(arg)) callback();
else window.setTimeout("delay(" + checkFunc + ", " + "'" + arg +
"'" + ", " + callback + ")", pollTime);
}
В функцию задержки передаются четыре аргумента, которые в точности соответствуют четырем последним аргументам, передававшимся при вызове функции asyncCall.
Сначала внутри функции задержки запускается глобальный счетчик numTries. Напомню, что это нужно для отслеживания количества вызовов функции задержки или, говоря иными словами, для проверки, сколько раз состояние веб-приложения оценивалось на предмет получения им асинхронного ответа. Если значение этого глобального счетчика превышает значение глобальной константы maxTries, значит, либо веб-приложение блокируется на сервере, либо ответ XMLHTTP является некорректным, поэтому управление передается функции завершения, где происходит выяснение причины этого превышения.
Если задержка не заблокировалась, то для проверки, истинно ли условие, указывающее на корректный ответ, вызывается функция checkFunc. Если функция checkFunc возвращает значение true (истина), то для продолжения выполнения сценария тестирования вызывается функция callback. Если же функция checkFunc возвращает значение false, необходимо продолжать ожидание, поэтому снова вызывается встроенная функция setTimeout, которая обеспечит ожидание в течение pollTime миллисекунд, а затем снова вызовет задержку.
Функция задержки не является полностью рекурсивной, не вызывая себя напрямую, однако она соотносится с самой собой, поскольку косвенно вызывает себя через функцию setTimeout. Чистый эффект от ее работы – это ожидание, которое передает управление либо функции завершения (при превышении максимально допустимого числа циклов задержки), либо функции обратного вызова (если состояние веб-приложения характеризуется значением true (истина)). Данный подход кажется очевидным в ретроспективе, но не слишком очевиден, пока решение не получено. Конечно, существуют и другие подходы, однако описанный здесь подход на практике оказался достаточно простым и эффективным.
Точно так же, как в функции asyncCall, вместо вызова функции window.setTimeout, использующей поименованный аргумент, можно следующим образом использовать свойство безымянной функции:
window.setTimeout(
function(){delay(checkFunc, arg, callback);},
pollTime);

Разработка тестовой страницы
Теперь, когда у нас в распоряжении имеются функция asyncCall и функция задержки, можно начинать разработку страницы тестового сценария.Раздел <body> страницы тестового сценария, состоит только из заголовка, текстовой области с "комментариями" в виде идентификаторов для отображения комментариев и кнопочного элемента управления для запуска автоматического тестирования. Раздел <head> содержит весь код JavaScript. Для задания максимального количества запусков функции задержки и проверки некоего условия, говорящего о выполнении асинхронного ответа, декларируется и инициализируется глобальная постоянная maxTries. Глобальная переменная numTries отслеживает количество вызовов функции задержки. Глобальная константа polling назначает для функции задержки время задержки между последовательными вызовами.
В зависимости от сложности тестируемого приложения, построенного на технологии AJAX, может возникнуть необходимость модификации значений maxTries и polling. В данном случае 10 вызовов с задержкой 200 миллисекунд между вызовами занимают всего лишь 2 секунды, что может быть недостаточно долго для обработки веб-сервером запроса XMLHTTP и возврата ответа клиенту.
Тестовый сценарий начинается с вызова функции runTest:
function runTest()
{
try
{
logRemark("Test Scenario #001");
logRemark("Starting test run\n");
step1(); // starting at 5, go N to 2
}
catch(ex) { logRemark("Fatal error: " + ex); }
}
}
Все, что при этом происходит, это отображение пары сообщений в <textarea> основной части тестового окружения с помощью программно-определяемой функции logRemark, и передача управления функции step1. Код введен в простой блок try/catch, так что любые исключения, которые отбрасываются во время тестового прогона будут "всплывать" и пер

2007-03-20

 

More IT news, prices and information