
Думаю, во многих командах, так или иначе связанных с разработкой 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. Спасибо за внимание.
