Автоматизированное тестирование графических приложений и сценариев установки
Как мы используем проект openQA в тестировании российской операционной системы ОС «МСВСфера» 9 от вендора «Инферит»
Одна из самых сложных задач в разработке и поддержке операционных систем — тестирование графических приложений и сценариев установки. Процесс усложняется постоянными обновлениями дистрибутива, увеличением количества пакетов, различными редакциями и вариантами установки ОС, а также множеством архитектур (x86_64, aarch64 и т.д).
Большинство компаний выбирает автоматизированное тестирование. Этот способ тестирования сложен в реализации, для запуска процесса на поток требуются различные специалисты — тестировщики, разработчики, devops-инженеры. Одна из главных проблем заключается в том, что тестирование обычно происходит с позиции разработчика: тестируются низкоуровневые функции, библиотеки и утилиты, различные API и т.п. Однако, пользователь взаимодействует не с этим. Для человека, который будет пользоваться операционной системой, важен интерфейс: что он видит на экране, как он с этим взаимодействует?. Почему бы в этом случае не тестировать ОС вручную? Это возможно, но такой подход затратен и может приводить к ошибкам, связанным с человеческим фактором.
В этой статье расскажем о том, как мы используем открытый проект openQA в тестировании российской операционной системы ОС «МСВСфера» 9 от вендора «Инферит».
Иллюстрация сгенерирована Midjourney
Инструменты: как тестировать ОС
Существует огромное количество инструментов для тестирования и автоматизации.
Для автоматизированной установки практически в любом дистрибутиве есть механизмы kickstart, preseed или аналогичные им.
Если мы говорим об оркестрации и построении пайплайнов, то сразу можно вспомнить Jenkins, Gitlab CI, Zuul и другие проекты.
Для тестирования консольных приложений используют Bats, Expect, Bash и т.п.
Для веб-приложений — Selenium, Puppeteer или Playwright.
Для управления конфигурацией и тестирования инфраструктуры используют Ansible, Salt, Puppet, Chef, Testinfra, Serverspec.
Но что делать с графикой?
OpenQA
В работе с операционной системой «МСВСфера» 9 мы используем открытую платформу для тестирования дистрибутивов openQA. Над проектом идёт работа с 2009 года, основной язык разработки — Perl. OpenQA активно используется для тестирования операционных систем Fedora, Debian, openSUSE. А главный архитектор ОС «МСВСфера» был идеологом внедрения этой системы в AlmaLinux OS.
Платформа openQA позволяет тестировать компоненты операционной системы со стороны пользователя: анализировать содержимое экрана, эмулировать ввод с помощью клавиатуры и мышки, записывать звук и сопоставлять его с образцом. Для анализа картинки на экране openQA использует популярные открытые библиотеки OpenCV и Tesseract OCR.
С помощью openQA можно тестировать как графические, так и консольные приложения, операционную систему можно загрузить и в BIOS, и в UEFI режимах. Реализованы полезные функции для отладки: дамп оперативной памяти, снимки дисков, доступ к последовательной консоли и т.п. Так же поддерживаются тестовые конфигурации из нескольких виртуальных машин, что позволяет запускать тесты одновременно на разных конфигурациях (BIOS/UEFI, архитектуры и т.п.).
Архитектура платформы достаточно проста. Есть веб-интерфейс, API интерфейс. И позади — пул воркеров, который через os-autoinst получает набор тестов, выполняет их, и отправляет результаты обратно в openQA.
Архитектура OpenQA
Примеры тестов
В данном примере main.pm — некая точка входа в наш тестовый набор. В json-файле содержится template окружения.
Каталог lib хранит библиотеки, каталог tests — тесты. А в needles лежат эталонные изображения, которые мы используем для тестов.
Структура каталогов OpenQA
Для работы необходимо сконфигурировать OpenQA.
Конфигурационный JSON файл содержит данные о том, какой продукт тестируется, разные версии продукта, архитектура, наборы тестов. Например, тестировать можно минимальную установку KDE. Также в нём содержится информация о том, какие реальные или виртуальные машины будут использоваться для тестов: например, сколько у них оперативной памяти, какая ёмкость диска/дисков, сколько сетевых машин.
Пример конфигурационного файла OpenQA (msvsphere-templates-bios.json):
{
"JobTemplates": [
{
"test_suite_name": "install_minimal.default",
"prio": 10,
"group_name": "MSVSphere x86_64",
"machine_name": "x86_64-bios",
"arch": "x86_64",
"flavor": "dvd-iso",
"distri": "msvsphere",
"version": "*"
}
],
"Products": [
{
"name": "msvsphere-dvd-x86_64",
"flavor": "dvd-iso",
"distri": "msvsphere",
"arch": "x86_64",
"version": "*",
"settings": []
}
],
"TestSuites": [
{
"name": "install_minimal.default",
"description": "Minimal installation using default partitioning settings",
"settings": [
{"key": "ISO_SKIP_CHECK", "value": "0"},
{"key": "TEST_TYPE", "value": "FULL"},
{"key": "PACKAGE_SET", "value": "minimal"},
{"key": "ROOT_PASSWORD", "value": "1nFeR!T-32167"},
{"key": "USER1_FULLNAME", "value": "Test user 1"},
{"key": "USER1_NAME", "value": "user1"},
{"key": "USER1_PASSWORD", "value": "1nFeR!T-32167"}
]
}
],
"Machines": [
{
"name": "x86_64-bios",
"backend": "qemu",
"settings": [
{"key": "HDDMODEL", "value": "scsi-hd"},
{"key": "HDDSIZEGB", "value": "15"},
{"key": "PART_TABLE_TYPE", "value": "mbr"},
{"key": "QEMUCPU", "value": "host"},
{"key": "QEMUCPUS", "value": "2"},
{"key": "QEMURAM", "value": "2048"},
{"key": "QEMU_VIDEO_DEVICE", "value": "virtio-vga"},
{"key": "QEMU_VIRTIO_RNG", "value": "1"},
{"key": "XRES", "value": "1024"},
{"key": "YRES", "value": "768"},
{"key": "WORKER_CLASS","value": "qemu_x86_64"}
]
}
]
}
Конфигурирование производится командой вида
# openqa-load-templates --clean /var/lib/openqa/share/tests/msvsphere/msvsphere-templates-bios.json
где msvsphere-templates-bios.json — конфигурационный файл, приведенный выше.
Main.pm (см. рис. 1) — раннер, который работает по принципу shell-скрипта, то есть с переменными окружения. Они задаются либо при запуске тестового набора, либо в конфигурационном файле (секция TestSuites→settings). В зависимости от объявления и содержимого этих переменных, раннер строит свою логику. Указывая разные значения этих переменных можно менять алгоритм проводимых тестов.
Рис. 1. Пример раннера main.pm
В приведенном примере если переменная ISO не равна 0 раннер последовательно запускает 5 тестовых скриптов (функции autotest: loadtest).
Рассмотрим терминальный тест (рис. 2) .
Рис. 2. Терминальный тест
Метод run получает указатель на объект некоего тестового класса. У openQA есть стандартные тестовые методы, мы можем использовать, или добавлять новые.
В данном случае был использован метод open_root_console. В рамках теста происходит логин под root в консоль:
платформа смотрит на содержимое экрана,
находит ввод логина и пароля,
вводит их,
начинает выполнять тест.
В этом примере мы получаем список установленных в системе файлов, записываем их в файл и загружаем к себе в хранилище. В конце мы завершаем тест, перезапускаем систему для следующего задания с помощью отправки ctrl-d и закрываем root-консоль.
Следующий пример — тест работы с графикой (рис. 3).
Рис. 3. Тест работы с графикой
Он немного сложнее.
OpenQA использует так называемые шаблоны (needle в терминах OpenQA). Детальнее о шаблонах — ниже. Одним из основных метаданных является тег.
Допустим, у нас есть экран входа в систему (GDM или LIGHTDM). Мы можем для каждого из экранов сделать эталонное изображение, протегать его одним и тем же тегом. Эта логика позволяет абстрагироваться от того, с какой версией продукта мы работаем и какой дизайн используется.
В данном тесте мы ждём (функция assert_and_click, длительность ожидания 120 секунд) появления приветственного экрана дистрибутива Anaconda. Система анализирует содержимое экрана, нажимает «ok», переходит в меню настройки root-пароля и вводит его. Сам пароль не захардкожен в код теста, а берётся из переменной окружения ROOT_PASSWORD.
Затем мы ждём 10 секунд, пока изменится содержимое экрана.
Эталонные снимки needles в openQA
Needle в openQA — это эталонный снимок экрана (далее — шаблон) в формате PNG и JSON метаданные к нему. Т.е. каждый шаблон в файловой системе — это 2 файла с одинаковым именем и расширениями .png и .json соответственно.
Шаблоны создаются встроенным в openQA редактором. Редактор — часть веб-интерфейса OpenQA, доступен только после авторизации.
Этот редактор позволяет:
добавлять зоны различного типа внутри шаблона: их координаты и границы, настраивать процент совпадения — то есть насколько изображение должно совпадать с эталоном, чтобы тест считался успешным.
задавать «точку клика» — т.е. точку, куда в тесте специальной функцией будет произведен клик мышью.
задать теги — некие имена, которые используются в тестах для работы с шаблонами. Так, в тесте на рис. 3 есть вызов функции assert_and_click («anaconda_main_menu»); Здесь «anaconda_main_menu» — тег шаблона, с которым должна работать функция. Тегов может быть несколько.
задавать имя шаблона. Оно используется как имя .png/.json файлов шаблона.
На рис. 3 и рис. 4 приведены примеры различных зон в шаблоне.
Рис. 3
Зелёные зоны сопоставляют изображение с образцом. Если процент совпадения сошёлся — тест успешный.
Красным отмечена зона игнорирования — та, что не будет учитываться в тесте. Например, это могут быть системные часы или содержание веб-сайта, которое мы не контролируем.
В жёлтой зоне осуществляется распознавание текста. Платформа распознаёт текст, оцифровывает его на эталонном изображении и на скрине, который мы тестируем, и сопоставляет текст.
Рис. 4
Так в интерфейсе выглядит минималистичная тестовая задача.
Приведём несколько примеров json-файлов шаблонов и рассмотрим их.
Рис. 5. Шаблон с одной зоной сопоставления
Это самый простой шаблон — одна зона сопоставления, один тег.
Такие шаблоны можно использовать с функцией assert_screen, она просто выполняет «сверку» указанных зон изображения, находящегося на «экране» с шаблоном.
Никакие действия, приводящие к смене состояния тестируемой системы не выполняются.
Рис. 5. Шаблон с одной зоной сопоставления и с «точкой клика»
Этот шаблон имеет одну зону сопоставления, точку клика и один тег.
Такие шаблоны можно использовать как с функцией assert_screen, так и с assert_and_click. assert_and_click — выполняет 2 действия — сверяет зоны с шаблоном как assert_screen и, при совпадении, выполняет клик мышью в указанной точке.
Рис. 7. Шаблон с несколькими зонами сопоставления и с «точкой клика»
Этот шаблон отличается от двух предыдущих только тем, что заставляет функции assert_screen/assert_and_click выполнять сверку не одной зоны, а двух. Первая — с точкой клика.
Рис. 8. Шаблон с несколькими зонами разного типа и с несколькими тегами
Здесь функции assert_screen/assert_and_click будут сопоставлять одну зону (с типом match) и будут игнорировать зону с типом exclude. Такой вариант следует использовать когда внутри match зоны есть участок, который сравнивать не нужно (часы, таймер и т.п.).
Рис. 9. Шаблон с несколькими зонами сопоставления и несколькими тегами.
Полный аналог шаблона на рис. 7, но без точки клика. Т.е. функцию assert_and_click использовать нельзя.
При работе с шаблонами необходимо учитывать следующие особенности и возможности:
Ни одна из зон в шаблоне не должна выходить за границы экрана машины, на которой работает тест. Так конфигурационный файл OpenQA, приведенный выше, сообщает, что экран имеет разрешение 1024×768 (параметры XRES и YRES). При этом, если в каком-либо шаблоне указать зону, например { «xpos»: 1000, «ypos»: 700, «width»: 50, «height»: 40, «type»: «match» }, то функция типа assert_screen сфейлит тест с ошибкой, что указанный шаблон не найден на том основании, что зона по координате X выходит за пределы экрана — 1000 + 50 = 1050 > 1024. Никакого реального сопоставления зон в этом случае происходить не будет. Утверждать найдена ли эта зона в текущем изображении на экране или нет нельзя. Об этой особенности всегда необходимо помнить при ручной правке json файлов шаблонов.
Как видно из примеров шаблонов выше, они могут иметь несколько тегов. Функция assert_screen и прочие выполняют поиск по всем шаблонам, имеющим указанный тег. Остальные теги можно использовать для определения, какой именно шаблон сработал. Для такого определения служит функция match_has_tag.
Шаблоны можно раскладывать по разным каталогам внутри каталога needles. Конкретное расположение того или иного шаблона ни на что не влияет, при запуске тестов OpenQA сканирует все каталоги, считывает находящиеся там шаблоны в единый список и далее использует его. Это позволяет, например, разделить шаблоны на группы. Например, «bootloader», «anaconda» и т.п.
После завершения тестов (неважно, с каким результатом) в веб-интерфейсе мы можем просматривать их результаты в виде превью для каждого needle. При нажатии на превью открывается окно, в котором можно увидеть результаты теста — рис 9.
Рис. 9.
Работа с образами дисков
Как все знают, при обычной работе с компьютерами установка ОС происходит достаточно редко. Т.е. мы устанавливаем ОС на диск и работаем с ней, не переустанавливая её, порой много лет. Потому что установка — нередко сложный и длительный процесс, включающий в себя не только установку как таковую, но и ряд действий по её настройке: добавление нужных драйверов устройств, конфигурирование каких-то компонентов системы, установка нужного ПО и т.д.
То же самое можно сказать и про тестирование — часто требуется протестировать не только установщик (анаконду) и «голую»/свежеустановленную ОС, а уже настроенную и с каким-то установленным софтом.
Чтобы достичь такого в OpenQA, необходимо:
После установки системы (и какой-то дополнительной настройки) нужно сохранить образ жёсткого диска, на который выполнялась установка.
Запускать тесты нужно на виртуальной машине, в конфигурации которой в качестве жёсткого диска указан образ, созданный в п.1.
Сохранение образа диска
П. 1 решается очень просто — OpenQA сам может сохранять образы дисков в файлы. Для этого нужно всего лишь запустить тесты с одним из двух дополнительных аргументов PUBLISH_HDD_XX либо STORE_HDD_XX. Например:
Вариант 1:
# openqa-cli api -X POST isos ISO=MSVSphere-9.2-x86_64-dvd.iso DISTRI=msvsphere VERSION=9.2 FLAVOR=dvd-iso ARCH=x86_64 BUILD=-msvsphere-9.2-$(date +%Y%m%d%H%M) ISO_PREVIEW_VERSION=1 TEST_TYPE=FULL ISO_SKIP_CHECK=1 PUBLISH_HDD_1=vm-%TEST_TYPE%-%MACHINE%.qcow2
Вариант 2:
# openqa-cli api -X POST isos ISO=MSVSphere-9.2-x86_64-dvd.iso DISTRI=msvsphere VERSION=9.2 FLAVOR=dvd-iso ARCH=x86_64 BUILD=-msvsphere-9.2-$(date +%Y%m%d%H%M) ISO_PREVIEW_VERSION=1 TEST_TYPE=FULL ISO_SKIP_CHECK=1 STORE_HDD_1=vm-%TEST_TYPE%-%MACHINE%.qcow2
Указание любого из этих аргументов приводит к тому, что после успешного завершения джобы OpenQA (установка + тесты если они есть) жёсткий диск сохраняется в файл.
Отличие STORE_HDD_XX от PUBLISH_HDD_XX одно — STORE_HDD_XX добавляет к имени файла номер билда джобы, PUBLISH_HDD_XX этого не делает, имя файла в точности такое как задано. STORE_HDD_XX удобно использовать для получения временных образов, например, для каких-либо исследований и проверок.
Так, при использовании PUBLISH_HDD_XX будет файл
# ls -l /var/lib/openqa/share/factory/hdd
-rw-r--r-- 1 geekotest geekotest 1029497856 сен 12 08:33 vm-FULL-x86_64-bios.qcow2
Для STORE_HDD_XX:
# ls -l /var/lib/openqa/share/factory/hdd
-rw-r--r-- 1 geekotest geekotest 971757568 сен 11 16:58 00000528-vm-FULL-x86_64-bios.qcow2
В самих тестах ничего делать не нужно, сохранение образа происходит автоматически. Однако, современные ОС очень сложны, они состоят из множества различных внутренних процессов, подавляющее большинство которых запускается и работает «сами по себе», независимо от действий пользователя (а OpenQA является по сути пользователем). Для получения консистентного образа диска, виртуальную машину, по завершении установки и послеустановочных тестов, необходимо корректно выключить (также как и обычный компьютер). Если этого не сделать, есть довольно большая вероятность получить бракованный образ, система из которого либо вообще не запустится, либо будет работать некорректно.
Чтобы этого не происходило, в конце установки/тестирования необходимо выключить машину вручную, например, исполнением стандартной linux команды poweroff.
При использовании образов следует иметь в виду, что:
образы всегда сохраняются в каталоге /var/lib/openqa/share/factory/hdd на сервере OpenQA, изменить это нельзя, разработчик тестов может задать только имя файла образа.
образы лучше создавать в формате .qcow, т.к. используемый в OpenQA менеджер виртуальных машин QEMU рекомендует его, особенно при использовании шифрования разделов LUKS.
опция PUBLISH_HDD_XX всегда перезаписывает имеющийся файл образа (если таковой имеется).
опция STORE_HDD_XX никогда не перезаписывает имеющиеся образы, т.к. номер билда джобы уникален. Поэтому ненужные файлы образов необходимо удалять вручную, т.к. они достаточно большие (минимум несколько ГБ) и могут засорить диск сервера и привести к его неработоспособности.
OpenQA имеет архитектуру мастер-сервер + ноды. Т.е. виртуальные машины с тестами запускаются на нодах, там же проходят сами тесты. .qcow образы сохраняются и там. Поэтому нужно контролировать свободное место и на нодах. Либо использовать nfs-монтирования к мастер-серверу.
Запуск тестовых VM из образа диска
Чтобы тестовая машина запустилась из образа, необходимо указать ряд дополнительных параметров в конфигурации машины в конфигурационном файле OpenQA — рис. 10.
Рис. 10. Пример конфигурации тестовой VM, запускающейся из образа диска
Здесь не нужно задавать размер HDD и его тип (параметры HDDSIZEGB и HDDMODEL соответственно), зато необходимо задать параметр BOOTFROM=c (признак того, что машина должна запуститься именно с HDD/образа) и HDD_1 — имя файла образа.
Переменные окружения
Как уже говорилось выше, для хранения глобальных параметров тестов и для передачи их извне OpenQA использует переменные окружения.
По сути это — аргументы тестов. Они читаются скриптами тестов функциями типа check_var/get_var и согласно их значениям тесты могут менять своё поведение.
Если по логике работы тестов обязательно наличие какого-либо параметра, то его лучше внести в конфигурационный файл OpenQA, чтобы не нужно было задавать его вручную при каждом запуске тестов — рис. 11.
Рис. 11. Пример задания параметров тестов в конфигурационном файле OpenQA
Здесь задаются 7 переменных окружения и их значения по умолчанию. При запуске их значения автоматически передаются в тесты. Если нужно изменить значение какого-либо параметра, его нужно указать в командной строке при запуске тестов. Например:
# openqa-cli api -X POST isos ISO=MSVSphere-9.2-x86_64-arm.iso DISTRI=msvsphere VERSION=9.2 FLAVOR=dvd-iso ARCH=x86_64 BUILD=-msvsphere-9.2-$(date +%Y%m%d%H%M) ISO_PREVIEW_VERSION=1 TEST_TYPE=ARM_SETUP ISO_SKIP_CHECK=1
Здесь перезаписываются параметры TEST_TYPE и ISO_SKIP_CHECK. Соответственно в тесты попадут значения, указанные в командной строке при запуске, а не прописанные в конфиге OpenQA.
Достоинства и недостатки OpenQA
Подводя итоги начнём с минусов.
В первую очередь, отметим высокий порог вхождения: необходимо потратить довольно много времени. К примеру, на изучение библиотеки и тестов проекта Fedora потребуется несколько недель из-за сложного кода. Также возможны сложности с поиском Perl разработчиков, но эту проблему можно решить, обучив кого-то писать только тесты для openQA.
Из плюсов, мы получаем широкие возможности по применению openQA. Это гибкий инструмент, позволяющий тестировать всё на единой платформе. Никаких проблем не возникает ни с документацией, ни с примерами — это зрелый проект, над которым работают с 2009 года. И его уже используют лидеры индустрии.