...

суббота, 20 февраля 2016 г.

[Из песочницы] Тестирование плагинов для Apache Cordova

Я собираюсь рассказать об одной из тем, касающихся Apache Cordova, которая практически не освещена в рунете — как тестировать свой плагин для Apache Cordova.

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

Немного теории


Итак, каким образом осуществляется тестирование плагинов для Apache Cordova. Прежде всего, архитектура тестов состоит из двух частей:
  1. Собственно тесты, использующие ту или иную библиотеку для тестирования.
  2. Так называемая test harness, или часть кода ответственная за запуск тестов и генерацию результатов теста.

В случае с плагинами в качестве библиотеки используется BDD-фреймворк Jasmine — довольно популярный в JavaScript мире. Соответственно будущие тесты могут выглядеть примерно так (отрывок взят из тестов для cordova-plugin-device — одного из плагинов, поддерживаемых сообществом Apache Cordova):
it("should exist", function() {
  expect(window.device).toBeDefined();
});

it("should contain a platform specification that is a string", function() {
  expect(window.device.platform).toBeDefined();
  expect((new String(window.device.platform)).length > 0).toBe(true);
});

it("should contain a version specification that is a string", function() {
  expect(window.device.version).toBeDefined();
  expect((new String(window.device.version)).length > 0).toBe(true);
});

Если с кодом тестов все достаточно ясно и понятно, то для того чтобы их запустить, необходимо проделать дополнительные манипуляции.

Самым простым способом здесь будет использование cordova-plugin-test-framework — еще одного плагина, который добавляет в приложение интерфейс для запуска тестов в виде отдельной страницы с элементами управления для запуска, остановки и выбора тестов для запуска, а так же осуществляет загрузку всех объявленных тестов во время работы приложения и их запуск.

Вот как это выглядит в работе:

Кроме того test-framework предоставляет возможность быстро создать т.н. ручные тесты — создать несколько кнопок на отдельной странице с описанием действий связанных с нажатием на каждую кнопку и ожидаемого поведения приложения.

README плагина описывает, как он работает и как нужно добавлять тесты, чтобы test-framework подхватил их, однако содержит некоторые неточности, поэтому вкратце приведу основные тезисы здесь.

  1. Для объявления тестов их нужно выделить в отдельный плагин. Расположение плагина не критично, но часто он находится внутри подпапки /tests основного плагина.
  2. Сами тесты должны находиться в модуле с именем, оканчивающимся на tests, (например <js-module src="tests/tests.js" name="my.plugin.tests">)
  3. Модуль с тестами должен экспортировать две функции:
exports.defineAutoTests = function() { };
exports.defineManualTests = function(contentEl, createActionButton) {};

которые будут выполнены при загрузке модуля. Название функций говорит само за себя.
  1. Полезно так же будет добавить cordova-plugin-test-framework — а возможно еще и тестируемый плагин — в качестве зависимости для ваших тестов, чтобы они устанавливались автоматически вместе с тестами. Это делается добавлением следующих элементов в tests/plugin.xml:
<dependency id="cordova-plugin-test-framework"/>
<dependency id="my-awesome-plugin" url=".." />

Создаем каркас плагина


Итак, после того, как все подготовлено для написания, каркас плагина должен выглядеть примерно следующим образом:

plugin.xml будет выглядеть так:

<plugin xmlns="http://ift.tt/1cqrl5D"
    id="my-awesome-tests" version="0.0.1">
    <name>My Awesome Plugin Tests</name>
    <dependency id="cordova-plugin-test-framework" />
    <dependency id="my-awesome-plugin" url=".." />
    <js-module src="tests.js" name="tests" />
</plugin>

tests.js:
exports.defineAutoTests = function() {
    // To be done
};
exports.defineManualTests = function(content, createActionButton) {
    // To be done
};

Это можно сделать вручную в вашем $EDITOR, а можно использовать plugman — еще один инструмент для работы с плагинами Apache Cordova:
npm install -g plugman
plugman create --name "My awesome plugin tests" --plugin_id my-awesome-plugin-tests --plugin_version 0.0.1 --path=./tests

чтобы сгенерировать каркас плагина и отредактировать файлы вручную.

Пишем автоматические тесты


Теперь перейдем непосредственно к написанию тестов. Те кто уже знакомы с BDD и jasmine могут пропустить этот раздел, т.к. ничего нового здесь не будет, и перейти к следующему.

Примеры далее в этой статье основаны на простом плагине, написанном за 10 минут в демонстрационных целях. Код плагина можно найти на GitHub.

Итак, сначала создаем каркас тестового плагина, как описано в предыдущем разделе и начинаем наполнять его тестами.

exports.defineAutoTests = function() {
    describe('plugin', function() {
        it('should be exposed as cordova.plugins.MyAwesomePlugin object', function() {
            expect(cordova.plugins.MyAwesomePlugin).toBeDefined();
            expect(cordova.require('my-awesome-plugin.MyAwesomePlugin'))
                .toBe(cordova.plugins.MyAwesomePlugin);
        });

        it('should have corresponding methods defined', function() {
            ['coolMethod', 'logCoolMessage'].forEach(function(methodName) {
                expect(cordova.plugins.MyAwesomePlugin[methodName]).toBeDefined();
                expect(cordova.plugins.MyAwesomePlugin[methodName]).toEqual(jasmine.any(Function));
            });
        });
    });
};

Пока этого достаточно. Теперь создадим тестовое приложение и запустим наши тесты. Для этого выполняем следующие команды в папке с тестируемым плагином:
cordova create my-sample-app && cd my-sample-app
cordova platform add android --save
cordova plugin add .. ../tests --save

и правим элемент <content src="index.html" /> в файле config.xml внутри нашего тестового приложения — меняем значение на cdvtests/index.html. Это необходимо чтобы вместо основной стартовой страницы при запуске приложения открылась страница плагина и загрузился test-framework.

Теперь запускаем приложение:

cordova run

и видим страницу плагина. Нажимаем "Autotests" и практически сразу видим отчет jasmine об успешном завершении тестов.

Добавляем ручные тесты


Теперь, понимая как работает test-framework, можно без особых проблем написать любое количество автоматических тестов для вашего плагина.

С ручными тестами все работает немного по-другому. test-framework предполагает, что каждому тесту полагается отдельная кнопка в интерфейсе и общее для всех тестов поле в которое можно вывести свои результаты. Это параметры createActionButton и content соответственно для функции defineManualTests.

createActionButton это функция, которая создает кнопку, добавляет ее в DOM внутрь указанного элемента и выполняет указанную функцию при нажатии кнопки. Параметры функции выглядят так:

`function createActionButton('Текст кнопки', выполняемая_функция, 'ИД_элемента') {...}`

content это контейнер на странице, внутри которого можно поместить информацию о тесте и описать процесс верификации для тестировщика.

Для примера добавим один ручной тест:

exports.defineManualTests = function(contentEl, createActionButton) {

    var show_preferences_test =
        '<h3>Press "Open preferences" button to show preferences pane</h3>' +
        '<div id="open_prefs"></div>' +
        'Expected result: A "Preferences"  box should appear';

    contentEl.innerHTML = '<div id="info"></div>' + show_preferences_test;

    var log = document.getElementById('info');
    var logMessage = function (message) {
        var logLine = document.createElement('div');
        logLine.innerHTML = message;
        log.appendChild(logLine);
    };

    createActionButton('Open preferences', function() {
        log.innerHTML = ''; // cleanup log area
        plugins.appPreferences.show(function(result) {
            logMessage(result);
        }, function(error) {
            logMessage(error);
        });
    }, "open_prefs");
};

Теперь нам нужно пересобрать приложение с обновленным плагином. Для этого просто удалим плагин из приложения и попробуем запустить приложение.
cordova plugin rm my-awesome-plugin-tests && cordova run

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

Запускаем тесты на CI


Пересобирать приложение для того чтобы запустить тесты еще раз — довольно рутинное занятие, которое конечно хочется делать автоматически. Вместо того чтобы изобретать собственный велосипед, воспользуемся готовым решением — cordova-paramedic. Это приложение написано специально для того чтобы автоматизировать запуск тестов для плагинов в т.ч. и для CI. Вот что оно делает:
  • создает новое приложение во временной папке
  • добавляет в приложение необходимы для запуска тестов плагины
  • изменяет стартовую страницу приложения
  • запускает локальный сервер для коммуникации с приложением и обработки результатов
  • выводит результат запуска тестов на консоль
  • завершается с соответствующим кодом в зависимости от того, прошли ли все тесты успешно или нет.

Для того чтобы начать использовать paramedic, установим его и добавим в package.json нашего плагина:
npm install cordova-paramedic --save-dev

и добавим пару команд для запуска тестов для iOS и Android.
"scripts": {
    ...
    "test-android": "cordova-paramedic --platform android --plugin ./ --verbose",
    "test-ios": "cordova-paramedic --platform ios --plugin ./ --verbose"
},

Все. Теперь вместо того чтобы собирать приложение вручную, править его config.xml, запускать и потом смотреть как появляются точки и крестики пройденных и упавших тестов просто запускаем соответствующий скрипт, например для Android:
npm run test-android

И через некоторое время получаем результаты тестов на консоль:
...
Results: ran 2 specs with 0 failures
result code is : 0
{"mobilespec":{"specs":2,"failures":0,"results":[]},"platform":"Desktop","version":"5.1.0","timestamp":1455530481,"model":"none"}

Теперь становится очень просто запускать тесты на CI-машине. Возьмем для примера Travis CI и Circle CI:

.travis.yml

language: objective-c
install:
  - npm install
script:
  - npm run test-ios

circle.yml
test:
  pre:
    - emulator -avd circleci-android21 -no-audio -no-window:
        background: true
        parallel: true
    - circle-android wait-for-boot
  override:
    - npm run test-android

Затем идем в панель управления соответствующего сервиса, включаем интеграцию с GitHub и наслаждаемся:

Заключение


Итак, теперь мы имеем плагин покрытый автоматическими тестами, умеем создавать и запускать тестовое приложение буквально одной-двумя строчками в терминале, и знаем состояние кода, обновляющееся per-commit.

Напоследок еще раз приведу ссылку на демонстрационный плагин: http://ift.tt/1VuAOwP. Посмотреть этапы добавления тестов можно по истории коммитов.

Удачного тестирования!

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Комментариев нет:

Отправить комментарий