Введение

Squish - это платный инструмент для автоматического тестирования пользовательского интерфейса. Есть Squish для Qt, Squish для Windows, для веба, для Java и iOS.

Во всех случаях тестовые сценарии - это скрипты на питоне или других скриптовых языках.

Далее речь пойдет только про Squish для Qt и про питон.

Squish не требует модифицировать или перестраивать тестируемое приложение. Он встраивается в работающий процесс на этапе выполнения. Однако, возможность изменения кода все же пригодится, чтобы присвоить объектам имена, которые будут использоваться в тестовом скрипте.

Тестовый скрипт может запускаться как на той же машине, что и тестируемое приложение, так и на другой.

Squish IDE

IDE позволяет записывать действия в тестируемом приложении, узнать имя объекта, наведя на него мышь, и, конечно же, редактировать и запускать тесты.

Test Center

Версия 7 содержит Squish Test Center. Он управляет запуском тестов и созданием отчетов. К сожалению, и Squish IDE, и Test Center сложно использовать, если один тест требует взаимодействия устройств, из которых только часть использует Qt, а у остальных свои тестовые интерфейсы.

Поэтому далее речь пойдет о том, как работать без этих инструментов.

Для Squish при таком раскладе отводится лишь роль программного интерфейса, чтобы дергать тестируемые объекты. Это только часть его функциональности.

Как работает Squish

Утверждать, что Squish - это инструмент для тестирования, значит не раскрывать суть этого продукта. С технической точки зрения Squish для Qt - это протокол удаленного вызова процедур. Полезно думать о нем именно с этой точки зрения, чтобы оценить его возможности и применимость для ваших задач.

Squish дает доступ к методам и свойствам, доступным через систему метаобъектов Qt. Можно вызывать статические методы и методы объектов, полученных через другие вызовы.

Лицензии

Первые версии использовали лицензионный ключ, который лежит в файле.

Версия 6.7.0 могла быть установлена с таким ключом, а могла использовать сервер лицензий.

Если вам только еще предстоит купить Squish, такого выбора у вас уже нет, и тестируемое устройство нуждается в соединении с сервером.

Сервер лицензий вы устанавливаете сами. Он генерирует для себя Machine ID, по всей видимости, привязанный к железу, и вы просите прислать вам ключ для этого Machine ID.

Необходимые приготовления

Установка

В тестировании участвуют три компонента, которые могут находиться на разных машинах и под разными ОС: тестовый скрипт, сервер Squish и тестируемое приложение. На всех этих машинах должен быть установлен Squish. Причем, на машине с приложением нужен Squish, слинкованный с теми же версиями библиотек Qt, что и приложение.

Запуск сервера

Во взаимодействии скрипта с тестируемым приложением всегда участвует сервер Squish. Сервер может запустить приложение на локальной машине по команде от скрипта или подключаться к уже запущенному процессу. В последнем случае сервер и приложение могут работать на разных машинах.

Тестируемое приложение должно быть добавлено в конфигурацию сервера. При этом ему присваивается имя. Это делается только через командную строку или редактирование файла конфигурации. Через сеть это сделать нельзя.

Если вы используете Squish IDE, и тестируемое приложение установлено локально, IDE по одному нажатию кнопки запустит и сервер, и приложение, и тест. Если же IDE вам не подходит, придется самостоятельно автоматизировать эти действия.

Запуск приложения из сервера

Вот так это делается через командную строку:

squishserver --config addAUT example ~/examples/first

Мы добавили бинарник first под именем example. В скрипте для его обозначения используется только имя example.

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

Запуск тестируемого приложения сервером Squish
Запуск тестируемого приложения сервером Squish

Присоединение к процессу

А вот так добавляется процесс для подключения через сокеты:

squishserver --config addAttachableAUT example testmachine:9999

На машине testmachine приложение должно быть запущено следующей командой:

startaut --port=9999 ./first

Присоединение сервера Squish к тестируемому приложению
Присоединение сервера Squish к тестируемому приложению

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

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

Тестовый скрипт

Первое, что надо сделать в скрипте - это запустить приложение или подключиться к уже запущенному.

import squishtest

# Если запускаем приложение
ctx = squishtest.startApplication('example', 'localhost', 4322)

# Если подключаемся к уже запущенному приложению
ctx = squishtest.attachToApplication('example', 'localhost', 4322)

# Устанавливаем контекст в качестве текущего
squishtest.setApplicationContext(ctx)

Если скрипт и сервер на разных машинах, то вместо localhost надо указать адрес сервера. 4322 - порт по умолчанию. Его можно изменить опцией --port при запуске сервера.

Присвоение имен

При тестировании пользовательского интерфейса надо как-то находить нужные элементы управления. Squish позволяет идентифицировать их по различным свойствам: тип, текст, элемент-родитель. Можно в коде приложения присвоить объекту имя и в тесте использовать его. Имя - это тоже свойство. Можно указать любое количество свойств одновременно.

Весь этот набор свойств в виде словаря называется real name или multi-property name. Свойства, которые могут входить в real name - это свойства объекта, которые есть его метаобъекте. Кроме того, есть искусственные свойства. Их список находится в Knowledge Base. Динамические свойства, к сожалению, не поддерживаются.

Object Map

Object map сопоставляет real name и symbolic name - имя, которое разработчик теста присваивает элементу управления. Можно использовать Squish IDE, чтобы добавить объект из работающего в данный момент приложения. Можно описывать объекты вручную.

Script-Based Object Map - это файл на том же языке, что и тестовый скрипт. Скрипт его импортирует. Пример:

loginDialog = {"type": "Dialog", "caption": "Login"}
okButton    = {"type": "Button", "text": "OK", "container": loginDialog, "visible": True}

В случае Script-Based Object Map’а symbolic name - это имя переменной, содержащей real name.

Никто не заставляет использовать Object Map. Главное - так или иначе передать real name в API.

Явные имена

Среди прочих свойств, которые могут быть частью реального имени, есть name. Оно соответствует свойству objectName класса QObject. Если есть возможность изменить тестируемый код ради его тестируемости, то можно явно присвоить всем виджетам уникальные имена. Тогда Object Map будет выглядеть очень просто:

okButton = {“name”: “okButton”}

Squish и питон

Соглашения об именах

Имена методов в Squish, как и в Qt, используют camelCase, а не стиль_рекомендованный_для_питона. Придется с этим жить.

Подсветка синтаксиса

squishtest - это библиотека, реализованная в бинарном файле. Набор классов, который она содержит, определяется на этапе выполнения и зависит от тестируемого приложения. Поэтому текстовые редакторы с подсветкой синтаксиса питона не находят эту библиотеку и выделяют каждое ее использование как ошибку.

Прокси-объекты

Библиотека squishtest содержит все классы тестируемого приложения и позволяет вызывать их статические методы. Пример:

objs = squishtest.QApplication.topLevelObjects()

Мы получаем список окон приложения. Этот же список можно получить через API:

objs = squishtest.object.topLevelObjects()

Объекты, которые нам возвращаются - это прокси-объекты для объектов в тестируемом приложении. Для одного объекта в приложении таких прокси может быть несколько. Когда Squish должен вернуть нам какой-то объект, он не знает, какие прокси у нас уже есть, и создает новую. Поэтому нельзя доверять операции сравнения:

if label1.parentWidget() == text_box1.parentWidget():
  print('Это условие никогда не выполнится. И не надейтесь')

Потоки

Squish не поддерживает многопоточность в питоне. Используйте async/await, если нужно.

Squish и suid/sgid

В линуксе, если на бинарник установлен бит suid и/или sgid, то использовать с ним Squish не получится. Если бы такое было возможно, это было бы легальной возможностью выполнять свой код от имени другого пользователя.

Реализация

Для внедрения в процесс Squish использует документированный механизм - LD_PRELOAD. В документации этого механизма сказано, что он не работает с suid/sgid по описанной выше причине.

Что делать

Допустим, в вашем продукте на линуксе есть компоненты, работающие от имени разных пользователей, и для их запуска с нужными правами используется suid, и эти компоненты реализуют пользовательский интерфейс на Qt, который надо тестировать. Звучит как невероятное стечение обстоятельств, да? А вот мы столкнулись с этим, поэтому пришлось исследовать этот вопрос.

Часто бывает, что есть два способа решения проблемы: простой и правильный. Мы разберем три: простой, правильный и самый правильный.

Простой способ

Убрать все suid и sgid биты и выдать одному пользователю права на все файлы продукта.

То, что это уничтожит безопасность, неважно, потому что машина с запущенным Squish не может считаться безопасной в любом случае.

Важно то, что так мы не тестируем настройку прав доступа. Т.е. может случиться, что в продакшене вылезет ошибка доступа, которая не воспроизводится в автоматических тестах.

Правильный способ

Для тех, что хочет с помощью тестов GUI проверить заодно и права доступа в файловой системе.

Всё описанное ниже выполнялось на CentOS 7.

Чтобы запустить процесс со Squish от имени другого пользователя, надо:

  • сначала поменять uid и gid, причем, не только эффективные, но и реальные. LD_PRELOAD не работает, если эффективные id отличаются от реальных. Неважно, что эта переменная выставляется уже после смены id

  • выставить в LD_LIBRARY_PATH путь до библиотек Squish

  • запустать startaut

Как менять uid/gid:

  • suid/sgid не работают со скриптами. Вариант с патчингом ядра не рассматриваем. Надо писать обертку на C.

  • менять реальный uid кто-попало не может. Для этого нужен следующий маневр:

    • обертка принадлежит руту и имеет suid и sgid. Это даст нулевые эффективные uid и gid

    • первым делом обертка вызывает setuid(0) и setgid(0). Это поменяет реальные id

    • затем вызываем setuid() и setgid() со значениями, которые нам нужны. Так как мы рут, поменяются и эффективные, и реальные значения

    • выставляем LD_LIBRARY_PATH и вызываем startaut

Вот так выглядит код обертки (имена и пути изменены, любые совпадения случайны)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>

int main()
{
    setuid(0);
    setgid(0);

    char autName[] = "/product/bin/suidprocess";

    struct stat info;
    int rv = stat(autName, &info);

    if (rv)
    {
        fprintf(stderr, "Cannot stat. Err: %d\n", rv);
        return rv;
    }

    setuid(info.st_uid);
    setgid(info.st_gid);

    setenv("LD_LIBRARY_PATH", "/setup_path/squish-for-qt-6.6.2/lib", 1);

    char exeName[] = "/setup_path/squish-for-qt-6.6.2/bin/startaut";

    char *args[] = { exeName, (char*) "--port=5567", autName, NULL };
    return execvp(exeName, args);
}

Самый правильный способ

Переделать архитектуру приложения, чтобы GUI работал без suid/sgid.

Не зря GTK отказывается работать с suid’ом. И для скриптов suid не поддерживается тоже не случайно. Очень сложно обеспечить безопасность этого механизма.

В идеале вообще избавиться от suid/sgid. Я говорю уже не про Squish, а про безопасность продукта.

Для вызова привилегированного кода есть стандартный и пригодный для всех операционных систем подход - сервисы.

От набора свойств до внешнего вида

Тестирование рендеринга

Через Qt, а, следовательно, и через Squish легко проверить, что QLabel имеет определенный text(). Но не так-то просто проверить, как этот текст выглядит на экране.

Qt широко используется, и поэтому нет нужды лишний раз его тестировать. Но что делать со своими виджетами, которые сами себя рендерят?

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

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

Различный API для классов

Мы на своем опыте знаем случаи, когда с каким-то типом элементов управления приходится работать не так, как с остальными. В итоге там, где с точки зрения пользователя действие или проверяемое условие одно и то же, в скрипте это делается иногда совершенно непохожим образом. Отчасти это связано с устройством QT, а часть различий заключена в самом Squish.

Мы создаем инструмент, которым пользуются тестировщики, незнакомые с программированием. В нем доступны действия, которые можно применить к разным элементам управления. Реализации этих действий часто содержат условия на класс объекта (o.metaObject().className()).

Примеры особенностей классов QT

  • Текст выбранного элемента в QComboBox читается через свойство currentText, в то время как у большинства элементов текст - это свойство text.

  • Цвет фона для QComboBox и QPushButton определяется цветовой ролью Button, а для всех остальных элементов - backgroundRole() данного объекта

  • Не надо путать отношения QObject’ов children()/parent() и отношения QWidget’ов (parentWidget()). Первое связано с управлением памятью: когда удаляется родитель, вместе с ним удаляются потомки. Второе - это нахождение одних виджетов внутри других визуально. Эти отношения совпадают на практике часто, но не всегда. Например, у элементов с прокруткой (QAbstractScrollArea) вьюпорт не является потомком, т.е. отсутствует в списке children(), однако является потомком в визуальном дереве.

Вспомогательное API

Симуляция ввода

Squish предоставляет такие методы, как mouseClick() и keyPress(), которые являются надстройками над Qt. Чтобы симулировать событие мыши или клавиатуры, достаточно одного только Qt. Иногда так и приходится поступать.

Например, методы keyPress(), type() и nativeType() используют строковые представления для клавиши и зажатых при ее нажатии Ctrl, Alt или Shift. Это удобно, если писать эти строки явно в скрипте. Однако, преобразовывать комбинацию скан-кода и модификатора в такую строку кажется ненужными затратами, если можно пряно создать QKeyEvent.

Поиск объекта

Найти нужный виджет можно как через waitForObject(), так и обходом дерева через children() и parent()/parentWidget().

Доступ к элементам списков и таблиц

waitForObjectItem() первым параметром получает либо уже найденный объект, либо real name, как для waitForObject().

Второй параметр задает элемент. Элемент задается для каждого типа по-своему.

Для таблицы надо указать строку и столбец через дробь. Например, 0/0 - это верхняя левая ячейка, 0/2 - первая строка и третий столбец.

Для меню и списков указывается текст элемента или его начало. Если есть несколько элементов с одинаковым текстом, то можно через символ подчеркивания указать порядковый номер среди таких элементов. Например, если в списке есть “лес” и “лестница”, то “лес_1” найдет “лес”, а “лес_2” - лестницу. Номер - это по порядку в списке, а не в алфавите. Если сначала идет “лестница”, то она найдется по запросу “лес” или “лес_1”.

Хотя бы одну букву от текста указать надо. Т.е. “_2” не работает. Не получится пройтись по всему списку, не зная текстов заранее.

Для доступа к элементам QComboBox’а необходимо, чтобы выпадающий список был раскрыт.

Squish и метаобъекты

Squish дает доступ ко всему, что доступно через систему метаобъектов. Это, прежде всего, классы, являющиеся частью Qt. Также это могут быть и ваши классы.

Метаобъекты - это такой reflection для C++. Заголовочный файл обрабатывается метакоппилятором (meta object compiler, MOC), и на выходе получается cpp-файл, который должен быть скомпилирован и слинкован вместе с реализацией класса.

Беда в том, что в метаобъект включается не каждый метод. Чтобы метод появился в метаобъекте, он должен быть помечен макросом Q_INVOKABLE. Еще в Qt есть свойства, а в C++ такого понятия нет. Свойства объявляются макросом Q_PROPERTY, и с ними тоже можно работать через Squish.

Помимо пользовательского интерфейса

Squish предоставляет класс RemoteSystem через который можно читать и писать файлы и выполнять команды операционной системы.

Выводы

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

Достоинства

Не надо перекомпилировать тестируемое приложение.

Есть служба поддержки.

Недостатки

Надо устанавливать на целевую систему и менять права доступа к файлам. Это создает сложности для формальной верификации.

Лицензии проверяемые через сеть, создают неудобство.

Комментарии (1)


  1. PereslavlFoto
    26.10.2022 18:10
    +1

    Совсем недавно мы читали, как PayPal объявил о поддержке стандарта FIDO, а теперь читаем про Squish.


    Время обернулось вспять, призраки идут ровным рядом.