Думаю, во многих командах, так или иначе связанных с разработкой Flex-приложений, рано или поздно возникает вопрос об автоматизированном тестировании продукта. А так, как наша команда занимается разработкой AIR-клиента для online-покера, совершенно закономерно, такой вопрос возник и у нас.
Сначала он прорабатывался исключительно QA-командой, ими были рассмотрены некоторые инструменты, включая FlexMonkey. В частности, не была оставлена без внимания данная статья на Хабре.
Цикл тестирования включает в себя добавление стола через админку, процесс регистрации на сайте с последующим скачиванием, установкой и запуском клиента. Для этого на Java были написаны Selenium Test case-ы. Что делать дальше было непонятно, так как стандартный FlexMonkey плагин для Selenium — FlexMonkium — умеет работать только с Flex-приложениями, работающими в браузере, во Flash-плагине, потому что написан на JS и взаимодействует с Flash-кой через ExternalInterface, который отсутствует в AIR Runtime. В свою очередь, стандартная консоль FlexMonkey взаимодействует с любым Flex-приложением, включая AIR, через чисто Flash-овую технологию LocalConnection, Java-реализации которой до этого не существовало. Теперь она существует.
Было принято решение писать клиент к FlexMonkey на Java, проведен эстимейт и я взялся за работу. Сразу оговорюсь, что 19 июля сего года, уже после завершения основных работ над нашей библиотекой, вышло новое поколение фреймворка FlexMonkey (теперь он называется MonkeyTalk) и в нем, после беглого ознакомления, проблемы с «кросс-технологичностью» вроде бы были устранены, путем организации коннекта клиент-агент через сокеты, но я доволен приобретенным опытом и думаю, что мы будем и дальше развивать этот продукт на базе старой архитектуры от GorillaLogic.
Пролог
Итак, в первую очередь нужно было разобраться, что же такое LocalConnection, и как с ним работать. Во всей Сети есть только один источник, в котором описаны, пусть и не в полной мере, внутренности LocalConnection, и информация там немного устаревшая. Вот эта статья.
Что мы можем понять из данной статьи? Что LocalConnection — это Memory File Mapping объект, размером 65535 байт, имеющий в системе имя MacromediaFMOmega, эксклюзивный доступ к которому обеспечивается путем захвата мютекса с именем MacromediaMutexOmega. Вот примерная карта данной области памяти:
Как видно из карты, в данном протоколе во всю используется AMF кодирование обеих версий. AMF (Action Message Format) — протокол бинарного представления данных, разработанный Adobe. Спецификация на AMF0 доступна здесь, на AMF3 здесь.
В общем случае процесс получения сообщения выглядит так:
- 1. Регистрируем листенер, путем добавления его имени в цепочку
- 2. Захватываем мютекс
- 3. Получаем file mapping и проверяем получателя
- 4. Если мы получатель, читаем сообщение и обнуляем timestamp и длину, таким образом мы маркируем сообщение как прочитанное
- 5. Повторяем бесконечно, начиная с шага 2
Процесс записи сообщения выглядит так:
- 1. Захватываем мютекс
- 2. Получаем file mapping и проверяем, существует ли получатель, которому мы должны отправить сообщение
- 3. Записываем сообщение
- 4. Отпускаем file mapping и мютекс
Java part
Так как в стандартной библиотеке Java отстуствуют нормальные средства для работы с Memory File Mapping, было принято решение писать нативную JNI-библиотеку для работы с данной технологией. Библиотека была написана на C++ в Visual Studio Express 2010 и собрана под 32-х битную архитектуру. Она содержит методы для создания/захвата/отпускания мютекса, создания/получения/отпускания file mapping объекта и записи/чтения в/из него, и враппер над WinAPI функцией GetTickCount(), которая нужна для получения timestamp-а.
Далее был написан Java-класс LocalConnection, который полностью повоторяет интерфейс своего Flash-ового собрата, за исключением событий. У него есть метод setClient(), который принимает экземпляр класса, реализующего интерфейс LocalConnectionSink, в котором определен метод onInvoke(String method, Object… args), собственно принимающий входящие вызовы по имени целевого метода с его параметрами. Метод send() повторяет таковой из Flash-а, принимает имя коннекшна, имя метода, который нужно вызвать, его параметры и помещает все это в очередь на отправку.
В самом классе работает отдельный поток, который в цикле добавляет листенер/читает сообщение, проверяет получателя/записывает сообщение. При поступлении нового сообщения — дергает метод onInvoke() клиента. В качестве сериализатора/десериализатора в/из AMF используется соответствующая библиотека из BlazeDS. Все достаточно просто.
Далее нам нужно было наладить сообщение с automation-агентом FlexMonkey в нашем приложении. Он подключается к проекту путем добавления SWC-библиотеки и входной точкой в нем служит класс MonkeyLink, собственно так же называется и сам протокол.На стороне агента он регистрирует endpoint с именем "_agent", клиент, в свою очередь должен зарегистрироваться под именем "_flexMonkey". Этот класс содержит несколько public-методов. Метод «ping» служит для симметричного пингования клиента/агента раз в полсекунды. Во время его вызова устанавливается флаг isConnected, который свидетельствует о том, что противоположная сторона еще жива и принимает сообщения. По таймеру, раз в 5 секунд, этот флаг сбрасывается.
Так же этот класс содержит ряд методов, которые принимают экземпляры наследников класса MonkeyRunnable. Это команды, представляющие собой экшены, которые мы видим на панели инструментов классической консоли FlexMonkey.
Исходя из этого, был разработан Java-аналог этого класса, и Java-аналоги команд FlexMonkey из ActionScript. Это такие команды, как SetProperty, CallFunction, VerifyProperty, UIEvent и т.д. Данный класс содержит метод playCommand(), который принимает экземпляр команды с нужными параметрами, сериализует ее и отправляет агенту посредством LocalConnection.
Так же данный класс содержит 2 дополнительных потока — первый раз в полсекунды отправляет ping агенту, а второй раз в 5 секунд сбрасывает флаг isConnected.
Над ним сделана обертка в виде класса FlexMonkeyAutomator, который предоставляет в распоряжение QA-инженера простой API для синхронного вызова экшенов на агенте. Также можно указывать количество попыток вызова экшена и задержку между ними. В общем случае сеанс работы с тестируемым приложением выглядит так:
MonkeyLink monkeyLink = new MonkeyLink();
if (monkeyLink.startLink(2000)) { // Соединяемся с агентом, таймаут 2 секунды
FlexMonkeyAutomator flexMonkeyAutomator = new FlexMonkeyAutomator(monkeyLink);
flexMonkeyAutomator.setProperty(...
flexMonkeyAutomator.storeValue(...
flexMonkeyAutomator.uiEvent(...
flexMonkeyAutomator.verifyProperty(...
flexMonkeyAutomator.callFunction(...
monkeyLink.disconnect();
}
Все методы FlexMonkeyAutomator содержат перегруженные версии, принимающие таймаут последним параметром. Это полезно, в частности в таком случае: в последнем экшене мы нажимаем на кнопку выхода из приложения, приложение закрывается и не успевает отправить результат экшена, в данном случае обычная версия метода вызова экшена никогда не вернет управление и наш тест-скрипт не завешится, а вот версия с таймаутом завершится благополучно.
Все исходники данной библиотеки доступны на BitBucket. Прошу не воспринимать всерьез проект JMonkeyLinkTest, который содержит тестовое Swing-приложение с обилием говнокода — оно предназначено исключительно для ситуативного тестирования отдельных фич библиотеки, а основное тестирование проводят наши QA-и на боевых скриптах.
PS: Совсем забыл. Не смотря на то, что при серализации команд на стороне Java, я присваиваю им FQDN, соответствующий их FQDN в ActionScript, они почуму-то все равно десериализуются в обычный Object, поэтому в тестируемом приложении их нужно регистрировать через registerClassAlias(), вот так:
registerClassAlias("com.gorillalogic.flexmonkey.monkeyCommands.CallFunctionMonkeyCommand", CallFunctionMonkeyCommand);
Ну, и надеюсь, данный продукт принесет кому-то пользу, а также будет полезен тем, кто хочет построить взаимодействие между Java- и Flash/Flex-кодом в своем проекте посредством LocalConnection. Спасибо за внимание.
Автор: Skyggedans