Testland
Browse all skills & agents

qt-test-framework

Authors and runs Qt Test - the first-party C++ unit + GUI test framework that ships with Qt 6 (via the `QtTest` module header). Covers the `QTEST_MAIN` / `QTEST_APPLESS_MAIN` / `QTEST_GUILESS_MAIN` entry-point macros, the `QObject` private-slot test pattern, `QVERIFY` / `QCOMPARE` / `QFETCH` assertions, GUI event simulation (`QTest::mouseClick`, `QTest::keyClick`, `QTest::touchEvent`), `QSignalSpy` for signal introspection, `QBENCHMARK` for performance regression, and the `-o file,junitxml` CI output. Use for in-process testing of Qt widgets, QObject signal/slot chains, and Qt Quick / QML application logic; for out-of-process Qt-app driving, use an OS-native accessibility driver instead.

Install with skills.sh (any agent)

npx skills add testland/qa --skill qt-test-framework
View source

qt-test-framework

Overview

Qt Test is a framework for unit testing Qt-based applications and libraries (qtover (opens in new window)), providing standard unit-testing primitives plus Qt-specific extensions for GUI event simulation, data-driven tests, and benchmarking. The QTest namespace (opens in new window) carries the verification macros (QVERIFY, QCOMPARE), data handling (QFETCH, QFETCH_GLOBAL), and entry points (QTEST_MAIN, QTEST_GUILESS_MAIN, QTEST_APPLESS_MAIN).

Qt Test is in-process - the test executable links the Qt application code and emits events directly into the QObject event queue. It does not go through the OS accessibility tree (per desktop-test-strategy-reference), so it cannot drive a Qt app from a separate process. For out-of-process Qt driving see winappdriver (Windows via UIA after QAccessible is enabled), xctest-mac-desktop (macOS), and at-spi-linux (Linux).

When to use

  • Pure-logic unit testing of QObject-based classes - view models, data models, signal/slot graphs.
  • GUI tests of QWidget or Qt Quick views where the test executable can link the app code (in-process), avoiding the brittleness of out-of-process accessibility-tree drivers.
  • Benchmark + performance-regression checks via QBENCHMARK (qtns (opens in new window)).
  • QSignalSpy-based assertions on signal emission order / arguments (qtidx (opens in new window)).

Step 1 - Add Qt Test to the build

CMake (Qt 6, the canonical Qt build system per Qt 6 docs):

find_package(Qt6 6.5 REQUIRED COMPONENTS Test Widgets)

qt_add_executable(test_calculator tst_calculator.cpp Calculator.cpp)
target_link_libraries(test_calculator PRIVATE Qt6::Test Qt6::Widgets)
add_test(NAME test_calculator COMMAND test_calculator)

qt_add_executable runs moc automatically, which Qt Test slot discovery depends on.

Step 2 - Author a test class

The canonical shape per qtover (opens in new window) - a QObject subclass with private slots as test functions:

#include <QtTest/QtTest>
#include "Calculator.h"

class TestCalculator : public QObject {
    Q_OBJECT

private slots:
    // initTestCase() / cleanupTestCase() run once per class
    void initTestCase();
    void cleanupTestCase();
    // init() / cleanup() run around each test function
    void init();
    void cleanup();

    void addsTwoIntegers();
    void emitsResultChangedSignal();
    void rejectsDivisionByZero();
};

void TestCalculator::initTestCase() {
    qDebug() << "Starting test suite";
}

void TestCalculator::addsTwoIntegers() {
    Calculator c;
    QCOMPARE(c.add(2, 3), 5);     // strict equality assertion
    QVERIFY(c.lastError().isEmpty());
}

QTEST_MAIN(TestCalculator)        // generates main() + QApplication
#include "tst_calculator.moc"     // include moc output

The four lifecycle slots are recognised by name (qtover (opens in new window)): initTestCase (once before any test), cleanupTestCase (once after), init (per test), cleanup (per test).

Verify: build the target and run ./test_calculator -functions; confirm it lists your private-slot test functions before authoring data-driven or GUI cases. If none appear, the slots are not under private slots: or the #include "tst_*.moc" line is missing - fix and rebuild.

Step 3 - Pick the right entry-point macro

Per qtns (opens in new window), three entry-point macros choose what application class the harness instantiates:

MacroInstantiatesUse for
QTEST_MAINQApplicationWidget GUI tests
QTEST_GUILESS_MAINQCoreApplicationConsole / non-GUI logic tests
QTEST_APPLESS_MAINnoneTests of code that itself instantiates its own application object

Per qtover (opens in new window), if the test class defines a static public void initMain() method, "it is called by the QTEST_MAIN macros before the QApplication object is instantiated" - that's the hook for setting platform-specific environment variables before Qt's event loop starts.

Step 4 - Data-driven tests

Per qtns (opens in new window), QFETCH retrieves test data values; data is declared in a sibling _data() slot:

private slots:
    void addsTwoIntegers_data();
    void addsTwoIntegers();

void TestCalculator::addsTwoIntegers_data() {
    QTest::addColumn<int>("a");
    QTest::addColumn<int>("b");
    QTest::addColumn<int>("expected");

    QTest::newRow("zeros")       << 0 << 0  << 0;
    QTest::newRow("positives")   << 2 << 3  << 5;
    QTest::newRow("negatives")   << -2 << -3 << -5;
    QTest::newRow("overflow")    << INT_MAX << 1 << INT_MAX + 1;  // documents UB
}

void TestCalculator::addsTwoIntegers() {
    QFETCH(int, a);
    QFETCH(int, b);
    QFETCH(int, expected);
    Calculator c;
    QCOMPARE(c.add(a, b), expected);
}

Per qtover (opens in new window): "A test can be executed multiple times with different test data." Each newRow runs the test function once.

Step 5 - GUI event simulation

The QTest namespace provides keyboard (keyClick / keyClicks), mouse (mouseClick / mousePress), touch (touchEvent), and wheel event helpers (qtns (opens in new window)); the full function-family table is in references/qt-gui-signal-benchmark.md.

void TestLoginWidget::successfulLogin() {
    LoginWidget w;
    w.show();
    QVERIFY(QTest::qWaitForWindowExposed(&w));

    QTest::keyClicks(w.usernameField(), "alice");
    QTest::keyClicks(w.passwordField(), "s3cret");
    QTest::mouseClick(w.submitButton(), Qt::LeftButton);

    QTRY_VERIFY(w.isLoggedIn());   // polls until true or times out
    QCOMPARE(w.currentUser(), QStringLiteral("alice"));
}

QTRY_VERIFY / QTRY_COMPARE (qtns (opens in new window)) poll the predicate with a default 5-second timeout - the right primitive for waiting on async signal/slot completion without ad-hoc QTest::qWait sleeps.

Step 6 - Signal introspection with QSignalSpy

Per qtidx (opens in new window), QSignalSpy enables "easy introspection for Qt's signals and slots":

void TestCalculator::emitsResultChangedSignal() {
    Calculator c;
    QSignalSpy spy(&c, &Calculator::resultChanged);

    c.add(2, 3);

    QCOMPARE(spy.count(), 1);
    const QList<QVariant> args = spy.takeFirst();
    QCOMPARE(args.at(0).toInt(), 5);
}

This is the canonical pattern for asserting on signal emission order, count, and argument values - far more robust than connecting test-internal slots and counting invocations by hand.

Step 7 - Benchmarks

QBENCHMARK executes a block repeatedly to measure performance, reporting CPU time, walltime, or instructions-retired per the active back-end; use QBENCHMARK_ONCE for a single run (qtns (opens in new window), qtover (opens in new window)). Example slot: references/qt-gui-signal-benchmark.md.

Step 8 - Run

Per qtover (opens in new window), a Qt Test executable accepts the following command-line options:

# List all test functions
./test_calculator -functions

# Extended verbose - shows each QCOMPARE / QVERIFY
./test_calculator -v2

# Run a specific test function
./test_calculator addsTwoIntegers

# Run a specific data row
./test_calculator addsTwoIntegers:negatives

# Write JUnit XML for CI ingestion
./test_calculator -o results.xml,junitxml

The -o filename,format flag per qtover (opens in new window) supports formats: "txt, csv, junitxml, xml, lightxml, teamcity, or tap".

For multi-binary suites, ctest (driven by add_test from Step 1) runs the per-test executables and aggregates outcomes.

Step 9 - Parsing results

./test_calculator -o results-junit.xml,junitxml

The JUnit XML output feeds junit-xml-analysis for cross-platform aggregation alongside other JUnit-emitting test runners.

Step 10 - CI integration

Run the ctest suite across an Ubuntu / Windows / macOS matrix; Linux needs QT_QPA_PLATFORM=offscreen (the headless Qt platform plugin) for GUI-touching executables with no X / Wayland session. Full workflow: references/qt-gui-signal-benchmark.md.

Anti-patterns

Anti-patternWhy it failsFix
Public slots as test functionsmoc treats them as Qt signals/slots; harness ignores themPrivate slots (qtover (opens in new window))
Forgetting #include "tst_xxx.moc" for single-file testsLinker error - moc output not bundledInclude the generated moc file at the bottom of the .cpp (Step 2)
QTest::qWait(2000) between actionsFlaky; slow on fast machines, racy on slowQTRY_VERIFY / QTRY_COMPARE with predicate polling (qtns (opens in new window))
One mega-test slot exercising many flowsFirst failure stops the chain; coverage attribution lostOne slot per behaviour; share setup via init() (qtover (opens in new window))
Test depends on QTimer::singleShot(0, …) cascadeEvent-loop ordering variesDrive the event loop with QCoreApplication::processEvents() or QTRY_* predicates
Using QTEST_MAIN for headless CITries to instantiate QApplication without a displayQTEST_GUILESS_MAIN for non-widget tests; QT_QPA_PLATFORM=offscreen for widget tests (Step 10)
QSignalSpy connected after the actionMisses emissions; count is wrongConstruct QSignalSpy before the action that triggers the signal (Step 6)
Benchmarks mixed with correctness tests in the same slotIteration count masks regressionsSeparate _benchmark() slots; gate on regression in CI

Limitations

  • In-process only. Qt Test links the application code into the test binary; it cannot drive a separately-packaged Qt app - for that, use an OS-native accessibility driver.
  • No binary compatibility guarantee. Per qtidx (opens in new window), "no binary compatibility guarantee" is offered for Qt Test; recompile when the Qt version changes.
  • QApplication singleton constraint. Multiple QTEST_MAIN test binaries cannot share a process - each test binary runs as its own executable (which is why ctest exists).
  • GUI tests need a display platform. Headless Linux runners require QT_QPA_PLATFORM=offscreen or xvfb-run for widget- level tests.
  • No async-await ergonomics. Qt Test predates coroutines; async flows use QSignalSpy + QTRY_VERIFY or explicit event-loop driving - clunkier than Playwright's await-everywhere model.
  • Localised string assertions in QCOMPARE are brittle - prefer asserting on QString identifiers (object names) or enum values rather than displayed text.

References

qt-test-framework - GUI events, benchmarks, and CI

View source (opens in new window)

qt-test-framework - GUI events, benchmarks, and CI

Reference detail kept out of the SKILL spine. See SKILL.md (opens in new window) for the core author -> run -> parse flow (including the runnable GUI and QSignalSpy examples).

Sources: Qt Qt Test overview (opens in new window), QTest namespace (opens in new window).

GUI event-simulation functions (qtns (opens in new window))

FamilyFunctions
KeyboardkeyClick, keyPress, keyRelease, keyEvent, keySequence, keyClicks
MousemouseClick, mousePress, mouseRelease, mouseMove, mouseDClick
TouchtouchEvent, createTouchDevice
WheelwheelEvent (Qt 6.8+)

QTRY_VERIFY / QTRY_COMPARE poll a predicate with a default 5-second timeout - the primitive for waiting on async signal/slot completion without ad-hoc QTest::qWait sleeps.

Benchmarks

QBENCHMARK executes a code block repeatedly to measure performance; Qt Test reports CPU time, walltime, or instructions-retired depending on the active back-end (qtover (opens in new window)). For a single-run measurement use QBENCHMARK_ONCE (qtns (opens in new window)).

void TestCalculator::benchmarkLargeSum() {
    Calculator c;
    QBENCHMARK {
        for (int i = 0; i < 1000; ++i) {
            c.add(i, i);
        }
    }
}

Keep benchmark slots separate from correctness slots so iteration counts do not mask regressions; gate on regression in CI.

CI integration

QT_QPA_PLATFORM=offscreen is the headless Qt platform plugin - required for GUI-touching Qt Test executables on hosted Linux runners with no X / Wayland session.

# .github/workflows/qttest.yml
jobs:
  test:
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v5
      - name: Install Qt
        uses: jurplel/install-qt-action@v4
        with: { version: '6.7.0' }
      - name: Configure
        run: cmake -B build -DCMAKE_BUILD_TYPE=Release
      - name: Build
        run: cmake --build build --parallel
      - name: Run tests (Linux with offscreen)
        if: runner.os == 'Linux'
        env:
          QT_QPA_PLATFORM: offscreen
        run: ctest --test-dir build --output-on-failure --output-junit junit.xml
      - name: Run tests (Windows/macOS)
        if: runner.os != 'Linux'
        run: ctest --test-dir build --output-on-failure --output-junit junit.xml
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: junit-${{ matrix.os }}
          path: build/junit.xml

Related skills

appium-windows-driver

Authors and runs Appium 2.x tests against the Windows driver, the actively-maintained Node.js proxy in front of Microsoft's WinAppDriver: `appium driver install windows`, capabilities (`platformName: windows`, `appium:automationName: windows`, `appium:app`, `appium:appTopLevelWindow`, `appium:appArguments`), Windows gestures (`windows: scroll` / `clickAndDrag` / `keys`), PowerShell prerun/postrun hooks, and CI. Use when the stack already uses Appium for iOS / Android / Mac2 and Windows fits the existing client + capability model; to drive WinAppDriver directly from a Selenium-style client use winappdriver, and for a C#-only FlaUI client use flaui-tests.

at-spi-linux

Authors Linux desktop UI tests via AT-SPI - the DBus-based Assistive Technology Service Provider Interface implemented by `at-spi2-core` (registry daemon + `libatspi` C library + ATK GTK bridge). Covers enabling toolkit accessibility (`gsettings set org.gnome.desktop.interface toolkit-accessibility true`), driving GTK + Qt apps through Python `dogtail` (object-oriented + procedural APIs), inspecting the tree with Accerciser, scripting via `pyatspi`, and CI integration on headless Linux runners with `Xvfb` + `dbus-launch`. Use for Linux-side desktop tests of GTK applications, Qt apps with QAccessible enabled, and Electron apps on Linux.

desktop-test-strategy-reference

Pure-reference catalog of desktop GUI test strategies across Windows, macOS, and Linux. Defines the three accessibility-tree backends (Microsoft UI Automation on Windows, Apple Accessibility / XCTest on macOS, AT-SPI on Linux), the wrapper-tools that drive each backend (WinAppDriver, Appium-Windows, XCUIApplication, AT-SPI clients), the cross-toolkit Electron + Qt paths, and a per-OS decision matrix with accessibility-first locator strategy. Deep operational detail (per-OS asynchronous-wait hierarchies, parallel-test policy, foreground-lock / UAC / TCC / AT-SPI elevation hazards, and the high-DPI / per-monitor test matrix) lives in references/. Use when choosing how to test or automate a desktop GUI application (desktop app testing, GUI automation, automate desktop UI) on Windows, macOS, or Linux - the strategic reference before picking a desktop test stack, ahead of the per-tool implementation skills.

electron-playwright

Authors Playwright `_electron` tests for packaged Electron desktop apps - launches the app via `electron.launch({ args })`, returns an `ElectronApplication` handle, drives renderer windows as Playwright `Page` objects, and probes the main process via `electronApp.evaluate(({ app, BrowserWindow }) => …)`. Distinct from ordinary browser page automation: this wraps the `_electron` API for launching packaged Electron apps and probing main + renderer processes. Use for end-to-end tests of Electron apps where main-process state, IPC, and renderer DOM must all be asserted from one suite.

electron-spectron

Legacy reference for Spectron - Electron's original ChromeDriver-based testing framework, officially deprecated 2022-02-01 at v19.0.0. Documents what Spectron was, the architectural reason it became unmaintainable, the migration path to Playwright `_electron`, and the residual support contract for projects still on Spectron. Use only when auditing a legacy suite or planning a migration off Spectron - for new work use Playwright's `_electron` API.

flaui-tests

Authors and runs FlaUI-based Windows UI tests - the .NET-native wrapper around Microsoft UI Automation (UIA2 + UIA3). Covers the `FlaUI.Core` / `FlaUI.UIA2` / `FlaUI.UIA3` NuGet packages, `Application.Launch` / `Application.Attach` lifecycles, `ConditionFactory` + `FindFirstDescendant` locator patterns, `Retry` waits, and xUnit / NUnit / MSTest harness integration. Use when the test stack is C# / .NET-first and the team wants idiomatic in-process UIA calls rather than the HTTP/JSON wire protocol of `winappdriver` or the Appium proxy layer of `appium-windows-driver`.

winappdriver

Authors and runs Windows UI tests against WinAppDriver, Microsoft's W3C-WebDriver service for UWP / WPF / WinForms / Win32 apps: installing + launching `WinAppDriver.exe` on `127.0.0.1:4723`, declaring `app` / `platformName` / `appArguments` / `appTopLevelWindow` capabilities, finding elements by `AccessibilityId` / `Name` / `ClassName`, and Windows-runner CI. Use when driving a native Windows app from a Selenium-style client (C#, Java, Python, Ruby, JS); for the actively-maintained Appium 2.x wrapper over the same server use appium-windows-driver, for a C#-only FlaUI client use flaui-tests, and to choose among Windows desktop drivers first use desktop-test-strategy-reference.

xctest-mac-desktop

Authors and runs XCTest UI + unit tests for macOS desktop apps - the Apple-first-party test framework that ships with Xcode. Covers the `XCTestCase` subclass + `test*` method-naming convention, `XCUIApplication` / `XCUIElement` / `XCUIElementQuery` for UI tests, accessibility-identifier-based locators (the stable replacement for label-based queries), `XCTAssert*` macros, `measureBlock:` for performance regressions, and `xcodebuild test` for CI execution. Use when the macOS app is built with Xcode and the test target is in-tree alongside the app - for cross-OS sharing see Appium Mac2 driver as a separate path.