Розробка для Sailfish OS: Тестування QML-компонентів

Доброго дня! Дана стаття є продовженням циклу статей, присвячених розробки для мобільної платформи Sailfish OS. В цей раз ми розповімо про те, як організувати тестування QML-компонентів додатків, написаних для мобільних пристроїв. Розглянемо всі етапи від написання коду до запуску тестів на реальному пристрої.

Досліджуване додаток

В якості прикладу будемо розглядати просте додаток-лічильник. Воно містить поле, в якому відображається поточне значення лічильника. Якщо натиснути на кнопку «add», то значення лічильника збільшується на одиницю. У вытягиваемом меню є пункт для скидання значення лічильника до нуля.

Налаштування

Для написання тестів використовується фреймворк QtTest, а конкретно QML-об'єкт типу TestCase. З його допомогою можна проводити дії натискання на екран і перевіряти очікувані значення на відповідність реальним. Слід зауважити, що на момент написання статті Sailfish використовує Qt SDK версії 5.2, тому доступні не всі методи, перераховані в документації.

Для того, щоб використовувати фреймворк QtTest в додатку, необхідно додати в *.yaml файл залежність для складання під PkgConfigBR:
Qt5Test
. Далі необхідно прописати настановний шлях для тестів *.pro файлі наступним чином:
tests.files = tests/*
tests.path = /usr/share/counter-application/tests
INSTALLS += tests
OTHER_FILES += tests/*

В даному прикладі в змінну tests.files записується адреса директорії з тестами в проекті, а в tests.path — шлях, по якому ці тести будуть встановлені на пристрій.

Реалізація тестів

Файли тестів повинні починатися з префікса tst_. Пишуться тести мовою QML, де кореневим елементом є об'єкт типу TestCase, усередині якого оголошуються функції. Ті функції, які починаються з префікса test_, вважаються тестами і будуть запущені фреймворком. Створимо для прикладу простий тест і помістимо його в файл tst_counter.qml:
import QtQuick 2.0
import Sailfish.Silica 1.0
import QtTest 1.0

TestCase {
function test_addition() {
compare(2 + 2, 4);
}
}

Для тестування QML-компонентів додатка потрібно використовувати його основний елемент, який визначено у файлі qml/ім'я-проекту. Важливо, що можна звертатися тільки до елементів, визначених у файлі з іменами у форматі CamelCase. Тому створюється допоміжний файл з ім'ям відповідного формату (qml/ИмяПроекта), в який переноситься весь вміст qml/ім'я-проекту. А для того, щоб додаток, як і колись використовувалася, у вихідний файл вставляється елемент ИмяПроекта. У нашому випадку вміст файлу counter-application.qml переносимо в новий файл CounterApplication.qml. У файлі counter-application.qml ми залишаємо наступне:
CounterApplication { }

Тепер нам необхідно налаштувати TestCase для запуску тестів після завантаження програми. Розглянемо властивості цього об'єкта:
  • completed: bool — встановлюється значення true, після завершення набору тестів
  • name: string — назва набору тестів для виведення звіту
  • optional: bool — якщо встановлено прапорець true, то тест пропускається (за замовчуванням false)
  • running: bool — містить значення true, якщо набір тестів виконується
  • when: bool — необхідно встановити значення true, для запуску набору тестів (за замовчуванням true)
  • windowShown: bool — містить значення true, якщо компонент, що містить TestCase був відображений
Для того, щоб тестувати QML-компоненти нашого додатка нам необхідно помістити TestCase всередині об'єкта, що описує додаток. Раніше ми виділили об'єкт в окремий файл і можемо використовувати його в інших файлах. Ми повинні скористатися властивостями when windowShown, щоб запускати тести тільки, коли вікно програми відобразилося. Також встановимо ім'я для набору тестів у властивість name. Для наших тестів це виглядає так:
CounterApplication {
TestCase {
name: "Counter tests"
when: windowShown
function test_addition() {
compare(2 + 2, 4);
}
}
}

Тепер об'єкт CounterApplication доступний в тестах і ми можемо взаємодіяти з ним і з відображеними їм видами.

Фреймворк QtTest надає методи для взаємодії зі стандартними Qt компонентами. На жаль, компоненти з Sailfish Silica не є стандартними, тому нам потрібно самим писати методи для роботи з ними. Для вирішення цієї задачі ми розширюємо клас TestCase, в який ми додамо методи для взаємодії з компонентами Sailfish. Ми створюємо файл SailfishTestCase.qml, в якому кореневим елементом є об'єкт TestCase. Всередині даного TestCase ми додаємо методи, які хочемо використовувати всередині наших тестів. Надалі у файлах з тестами ми використовуємо замість об'єкта TestCase об'єкт SailfishTestCase і користуємося доданими методами.

Для початку нам необхідно знайти якийсь елемент, який відображається виглядом. У QML для доступу до елементів виду використовується властивість id, але воно недоступне. Тому для елементів, які необхідно шукати у відображеному вигляді, ми встановлюємо значення властивості objectName і шукаємо елементи, використовуючи його. Для пошуку можна організувати рекурсивний спуск в глибину з перевіркою значення властивості об'єкта на рівність шуканого. Був реалізований метод, який дозволяє знайти елемент, у якого якесь властивість має задане значення:
function findElementWithProperty(parent, propertyKey, propertyValue, exact, root) {
if (parent.visible) {
if (exact && parent[propertyKey] === propertyValue) return parent
if (!exact && parent[propertyKey] !== undefined &&
parent[propertyKey].search(propertyValue) !== -1) {
return parent
}
}
if (parent.children !== undefined && parent.visible) {
for (var i = 0; i < parent.children.length; i++) {
var element = findElementWithProperty(parent.children[i], propertyKey,
propertyValue, exact, false);
if (element !== undefined) return element;
}
}
if (root) {
fail("Element property with key '" + propertyKey + "' and value '" +
propertyValue + "' not found");
} else {
return undefined;
}
}

Параметрами цього методу є:
  1. parent — елемент, з якого необхідно починати пошук
  2. propertyKey — властивість, значення якого перевіряється
  3. propertyValue — значення властивості, яке необхідно знайти
  4. exact — true, якщо необхідна повна відповідність шуканого значення знайденого, у противному випадку значення шукається як підрядок
  5. root — true, якщо поточний елемент є стартовим
Для пошуку елемента objectName був реалізований додатковий метод, так як цей вид пошуку найбільш затребуваний:
function findElementWithObjectName(root, name) {
return findElementWithProperty(root, "objectName", name, true, true);
}

Яскравим прикладом нестандартного компонента Qt служить вытягиваемое меню, яке широко використовується в Sailfish додатках. Серед методів TestCase не існує такого, який дозволив би одним викликом вибрати елемент такого меню, тому корисною виявилася наступна реалізація даного поведінки:

function openPullDownMenu(element) {
var x = element.width / 2;
var startY = element.height / 10;
mousePress(element, x, startY);
for (var i = 1; i <= 5; i++) {
mouseMove(element, x, startY * i);
}
mouseRelease(element, x, startY * i);
}

function clickElement(element) {
mouseClick(element, element.width / 2, element.height / 2);
wait(1000);
}

function clickPullDownElement(parent, name) {
openPullDownMenu(parent);
clickElement(findElementWithObjectName(parent, name));
}

Метод openPullDownMenu(element) дозволяє імітувати витягування меню так, як це робив би користувач: спочатку здійснюється натискання на екран, а потім покажчик ведеться вниз для відкриття меню і відпускається. Параметром є об'єкт, що містить це саме меню.
Також корисний метод clickElement(element), що дозволяє натиснути на вказаний елемент і почекати секунду завершення дії, ініційованого натисканням.
Комбінуючи описані вище методи ми створюємо метод clickPullDownElement(parent, name), який і дозволяє відкрити меню, яке міститься в переданому методом елементі parent, і натиснути на елемент, у якого значення властивості objectName дорівнює значенню параметра name.

З допомогою цих методів ми можемо написати тести для нашого застосування. Перший буде збільшувати значення лічильника два рази і перевіряти, що значення збільшилося. Другий — збільшить значення лічильника і скине його, потім перевірить, що воно стало рівним 0.
CounterApplication {
SailfishTestCase {
name: "Counter tests"
when: windowShown

function test_counterAdd() {
var button = findElementWithObjectName(pageStack.currentPage, "addButton");
clickElement(button);
clickElement(button);
compare(findElementWithObjectName(pageStack.currentPage, "countText").text, "2");
}

function test_counterReset() {
var button = findElementWithObjectName(pageStack.currentPage, "addButton");
clickElement(button);
clickElement(button);
clickPullDownElement(pageStack.currentPage, "resetItem");
compare(findElementWithObjectName(pageStack.currentPage, "countText").text, "0");
}
}
}

Програма не закривається між запусками тестів і не очищає дані. Відповідальність за преднастройку і очищення даних до і після виконання тестів цілком лежить на розробнику. В TestCase є два методи, які викликаються до і після виконання кожного тесту: init(), cleanup(). Дані методи повинні використовуватися для повернення стану програми в початкове. Також існують методи initTestCase() cleanupTestCase(), що викликаються один раз перед виконанням всіх тестів і після відповідно.

У нашому прикладі необхідно обнулити значення лічильника після виконання кожного тесту, для цього додамо наступну реалізацію методу cleanup():
CounterApplication {
SailfishTestCase {
name: "Counter tests"
when: windowShown

...

function cleanup() {
clickPullDownElement(pageStack.currentPage, "resetItem");
}
}
}

Після завершення кожного тесту буде натиснута кнопка «reset» з вытягиваемого меню.

Збірка і запуск тестів

Перед тим, як запускати тести, необхідно зібрати і розгорнути додаток на пристрої (підійде як фізичний пристрій так і емулятор). Цей процес описаний в одній з попередніх статей циклу. Для того, щоб мати можливість запускати тести на пристрої, необхідно встановити два пакети з допомогою команд:
pkcon install qt5-qtdeclarative-import-qttest
pkcon install qt5-qtdeclarative-devel-tools

Таким чином ми встановлюємо на пристрій фреймворк QtTest, що дозволить нам запускати написані нами тести.

Для запуску тестів ми використаємо утиліту qmltestrunner, якій в якості параметра передаємо шлях до файлів з тестами. Виглядає це наступним чином:
/usr/lib/qt5/bin/qmltestrunner -input /usr/share/counter-application/tests/

В результаті ми бачимо наступне:
********* Start testing of qmltestrunner *********
Config: Using QtTest library 5.2.2, Qt 5.2.2
PASS : qmltestrunner::Counter tests::initTestCase()
PASS : qmltestrunner::Counter tests::test_counterAdd()
PASS : qmltestrunner::Counter tests::test_counterReset()
PASS : qmltestrunner::Counter tests::cleanupTestCase()
Totals: 4 passed, 0 failed, 0 skipped
********* Finished testing of qmltestrunner *********

У висновку крім доданих нами двох тестів test_counterAdd() test_counterReset() відображаються виклики методів initTestCase() cleanupTestCase().

Висновок

В результаті був розглянутий спосіб написання тестів для тестування QML-компонент в додатках для платформи Sailfish OS. В якості прикладу було розглянуто просте додаток-лічильник, код якого (разом з тестами) доступні на GitHub.

Технічні питання також можна обговорити на каналі російськомовного співтовариства Sailfish OS в Telegram або групі Вконтакті.

Автор: Сергій Аверкієв
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.