Введение
Как и любой другой аспект качества, доступность нуждается в систематической проверке — не только при первоначальной разработке, но и при каждом значимом изменении интерфейса, чтобы не допустить регрессии. В статье разберём конкретные инструменты для тестирования доступности Qt-приложений на разных платформах, и составим практический чек-лист, применимый при ручном тестировании.
Концепция
Тестирование доступности проводится на нескольких уровнях: автоматизированные проверки структуры доступности (есть ли у элементов имена, роли, состояния — это можно частично автоматизировать), ручное тестирование с реальным screen reader (NVDA на Windows — бесплатный и широко используемый, VoiceOver встроен в macOS, Orca — стандартный screen reader для GNOME на Linux), и тестирование исключительно клавиатурной навигации без использования мыши вовсе. Полностью автоматизированная проверка доступности невозможна — многие критичные аспекты (логичность порядка озвучивания, осмысленность текстовых описаний) требуют человеческой оценки.
Пример кода: Тестирование доступности приложений
// Инструмент инспекции дерева доступности приложения для отладки —
// выводит структуру в консоль, аналогично Accessibility Inspector на macOS
#include <QAccessible>
#include <QAccessibleInterface>
#include <QDebug>
void dumpAccessibilityTree(QAccessibleInterface *iface, int depth = 0)
{
if (!iface || !iface->isValid()) return;
const QString indent(depth * 2, ' ');
qDebug().noquote() << indent
<< "Роль:" << static_cast<int>(iface->role())
<< "| Имя:" << iface->text(QAccessible::Name)
<< "| Значение:" << iface->text(QAccessible::Value)
<< "| Фокусируемый:" << iface->state().focusable
<< "| Невалидный:" << iface->state().invalid;
for (int i = 0; i < iface->childCount(); ++i) {
dumpAccessibilityTree(iface->child(i), depth + 1);
}
}
void inspectApplicationAccessibility(QWidget *rootWidget)
{
QAccessibleInterface *rootInterface = QAccessible::queryAccessibleInterface(rootWidget);
qDebug() << "=== Дерево доступности приложения ===";
dumpAccessibilityTree(rootInterface);
}
// Автоматизированная проверка базовых требований доступности как часть тестов
#include <QtTest/QtTest>
#include <QAccessible>
class TestAccessibilityBasics : public QObject
{
Q_OBJECT
private slots:
void testAllInteractiveWidgetsHaveAccessibleName()
{
MainWindow window;
QAccessibleInterface *rootInterface = QAccessible::queryAccessibleInterface(&window);
QStringList widgetsWithoutName;
checkNamesRecursively(rootInterface, widgetsWithoutName);
QVERIFY2(widgetsWithoutName.isEmpty(),
qPrintable("Виджеты без accessibleName: " + widgetsWithoutName.join(", ")));
}
private:
void checkNamesRecursively(QAccessibleInterface *iface, QStringList &problems)
{
if (!iface || !iface->isValid()) return;
const bool isInteractive = iface->role() == QAccessible::Button
|| iface->role() == QAccessible::CheckBox
|| iface->role() == QAccessible::EditableText;
if (isInteractive && iface->text(QAccessible::Name).isEmpty()) {
problems.append(QString("Объект с ролью %1 без имени").arg(static_cast<int>(iface->role())));
}
for (int i = 0; i < iface->childCount(); ++i) {
checkNamesRecursively(iface->child(i), problems);
}
}
};
QTEST_MAIN(TestAccessibilityBasics)
#include "test_accessibilitybasics.moc"
Чек-лист ручного тестирования доступности (для каждого нового экрана/диалога):
[ ] Все интерактивные элементы достижимы через Tab без использования мыши
[ ] Порядок обхода Tab соответствует логичному визуальному порядку
[ ] Каждый элемент имеет осмысленное, понятное имя при озвучивании screen reader
[ ] Динамические изменения (ошибки, обновления статуса) озвучиваются автоматически
[ ] Действие можно выполнить и завершить без единого использования мыши
[ ] Цветовая индикация состояния дублируется текстом или иконкой
[ ] Коэффициент контрастности текста/фона соответствует WCAG AA (см. статью 204)
[ ] Увеличение масштаба текста системой не ломает раскладку интерфейса
[ ] При закрытии диалога фокус корректно возвращается на вызвавший его элемент
Пояснения к коду
dumpAccessibilityTree() рекурсивно обходит дерево объектов доступности через QAccessibleInterface::childCount()/child(), выводя в консоль ключевые атрибуты каждого узла — это практичный, быстрый способ визуально проверить, что реально «видит» подсистема доступности, без необходимости запускать полноценный screen reader для каждой мелкой проверки во время итеративной разработки.
TestAccessibilityBasics показывает пример автоматизированной, хотя и не исчерпывающей проверки — она ловит самый базовый класс проблем (полностью отсутствующее имя у интерактивного элемента), но не может проверить осмысленность этого имени для реального пользователя («Кнопка 1» формально не пустое имя, но абсолютно неинформативно) — такая автоматизированная проверка дополняет, но не заменяет ручное тестирование по чек-листу, приведённому в третьем блоке примера.
Подводные камни
- Восприятие автоматизированных проверок (как
TestAccessibilityBasics) как достаточного доказательства полной доступности приложения. Такие тесты эффективно ловят явные пробелы (отсутствие имени вовсе), но совершенно не способны оценить качество и осмысленность предоставленной информации, логичность порядка навигации или реальное удобство использования с конкретным screen reader — они снижают регрессию совсем базовых проблем, но не заменяют периодическое ручное тестирование. - Тестирование только с одним screen reader на одной платформе. Разные screen reader (NVDA, JAWS, VoiceOver, Orca) по-разному интерпретируют одни и те же события и атрибуты доступности, и приложение, прекрасно работающее с NVDA на Windows, может иметь специфичные проблемы при использовании VoiceOver на macOS — для кросс-платформенного приложения желательно протестировать хотя бы по одному циклу с основным screen reader каждой целевой платформы.
- Игнорирование тестирования доступности до момента, когда интерфейс уже полностью спроектирован и реализован. Исправление фундаментальных проблем доступности (неверная структура фокуса, отсутствие декларативной поддержки в самой архитектуре кастомных компонентов) обходится значительно дороже на позднем этапе разработки, чем встраивание этих требований с самого начала проектирования компонентов — чек-лист из примера эффективнее применять для каждого нового экрана сразу после его реализации, а не как финальную проверку перед релизом всего продукта.
- Тестирование исключительно силами разработчиков без привлечения реальных пользователей вспомогательных технологий. Разработчик, тестирующий собственное приложение с screen reader, неизбежно знает, что и где искать, и может не заметить проблемы, очевидные для пользователя, который видит интерфейс впервые и не имеет этого контекста — там, где возможно, привлечение реальных пользователей с инвалидностью для тестирования даёт качественно иной, более достоверный уровень обратной связи, чем самотестирование команды разработки.