Native automated testing for GNUstep / Gershwin: every category, the options, and the gaps

What native mechanism covers each layer of testing — unit, integration, functional, UI, acceptance/BDD, system/smoke, and regression — for a GNUstep/AppKit stack. Specifically: what UnitKit and the GNUstep Testing framework cover (unit/integration), what StepTalk and Distributed Objects cover (functional/integration driving), where UI/acceptance testing has no native answer (GNUstep has no working accessibility layer), and how the current gershwin-on-nextbsd screenshot gate fits (system/smoke). Ends with a proposed native Gherkin/BDD framework and a full-pyramid strategy that runs in GitHub Actions and locally.

TESTING LANDSCAPE UNIT · INTEGRATION · FUNCTIONAL UI · BDD · SYSTEM GNUSTEP / APPKIT CI + LOCAL DISTRIBUTED OBJECTS · NO D-BUS

Output of a multi-agent investigation into GNUstep-native testing plus a live read of the Gershwin ISO test scripts. Every capability claim is grounded in a cited source (§11) or actual repo code. Terminology note: there is no "UIKit" in GNUstep — UIKit is Apple's iOS framework; GNUstep's GUI framework is AppKit (libs-gui). The similarly-named UnitKit is Étoilé's unit-test framework (§2) — a different thing entirely.

1The test-category map — what covers what

First, a plain taxonomy (your categories, lightly standardized — they were close):

CategoryQuestion it answersScope
UnitDoes this one class/method behave?1 object, in-process, no I/O
IntegrationDo these components work together?≥2 components / processes (e.g. a framework + its deps, or DO across processes)
FunctionalDoes a feature work end-to-end at the behavior/API level?A whole feature, often headless (no GUI needed)
UIDoes the actual GUI respond to clicks/keys correctly?Real widgets on a real (or virtual) display
Acceptance / BDDDoes it do what a human spec says, in plain language?Feature described in Given/When/Then; usually rides on functional or UI
System / smokeDoes the whole thing boot and come up?The full ISO in a VM
RegressionDid a past bug come back?Cross-cutting — any of the above, re-run on every change; visual regression = golden-image diff

The coverage matrix — the core answer. = strong native fit · ~ = possible/partial · = not this mechanism.

Mechanism (native unless noted)UnitInteg.Funct.UIAccept.SystemRegr.
GNUstep Testing framework (make check)~
Étoilé UnitKit~~
swift-corelibs-xctest~~
ObjC BDD-unit (Kiwi/Cedar/Specta)¹~~~
Distributed Objects (harness transport)~~
StepTalk (scripting driver)~~
NSEvent synthesis + in-app agent~~
External X11 (xdotool/SikuliX/PyAutoGUI)~~~~
Gherkin/BDD framework (proposed, §6)~~
QEMU screenshot gate (have it, §7)~✗³~~⁴
ImageMagick compare (golden image)assert~✔⁴

¹ Apple/XCTest-oriented; GNUstep support unverified — treat as "maybe." ² Only by having the bridged object post synthesized NSEvents (§5). ³ Whole-screen heuristic, blind to individual widgets. ⁴ The current gate uses a colour-count heuristic, not a golden-image diff — true visual regression would add compare (§9).

2Unit tests (native — the strong layer)

FrameworkStyleWhat it coversMaturity / notes
GNUstep "Testing" framework (gnustep-tests)macro-based (PASS, PASS_EQUAL, PASS_EXCEPTION)Unit + integration of ObjC classes; this is the harness GNUstep itself uses to regression-test libs-base/libs-guiShips in gnustep-make ($GNUSTEP_MAKEFILES/TestFramework/, Testing.h, ObjectTesting.h, gnustep-tests runner); wired via the make check target. The default, canonical choice.
Étoilé UnitKitxUnit (UKRunner, UKTest marker, UK* asserts)Unit; integration/functional as far as the test body drivesMinimalist (<2000 LoC), Mac/iOS/GNUstep; Étoilé ships a GNUstep port. This is what "UnitKit" refers to — a unit framework, not "UIKit".
swift-corelibs-xctestXCTest (XCTAssert*)Unit; functional if written soSwift-on-Foundation; runs on GNUstep Foundation but off Apple's ObjC runtime needs explicit allTests/XCTMain. No XCUITest (the GUI half is Apple-private).
Kiwi / Cedar / Specta+Expecta / QuickRSpec-style BDD-unit (describe/it)Behavior specs at the unit levelApple/XCTest-bound; GNUstep compatibility unverified — likely needs porting. Note this is BDD at the unit level (describe/it), distinct from Gherkin acceptance specs (§6).

3Integration & functional tests (native driving)

Above the unit line, GNUstep's advantage is that Distributed Objects lets a test process reach into a running app and message its real objects — the native substrate for integration and functional tests. StepTalk is a scripting front-end on top of it.

4The UI / acceptance wall: no accessibility

Everything above is headless and native. The moment you want to test the GUI, you hit the wall that shapes the rest of this document.

4.1 · The Gershwin-native fix: NSAccessibility over Distributed Objects (no D-Bus)

AT-SPI2 and D-Bus bridges are explicitly out of scope for Gershwin. AT-SPI2 is the Linux accessibility layer, and its "2" literally means "rewritten to run over D-Bus" — it stands up a separate a11y bus plus a registry daemon (at-spi2-registryd) and marshals every widget into a language-neutral D-Bus Accessible struct. Unwanted here. The correct fix is the one macOS itself uses: NSAccessibility is a Cocoa object API and on macOS does not use D-Bus at all (it rides Apple's own IPC). Gershwin implements that same API and transports it over Distributed Objects — Cocoa-authentic, D-Bus-free, and the same object layer that powers native UI/BDD testing.

AT-SPI2 / D-Bus conceptGershwin DO-native equivalent
a11y bus daemon (at-spi2-registryd)gdomap — GNUstep's existing DO name/port registry. No new bus.
well-known bus namesNSConnection registerName: per app (extends the delegate-vending convention apps already use)
Accessible object (role/state/name, marshalled)the real NSView/NSCell/NSControl, vended — role/enabled/value/title already live on it
D-Bus method call (Action.doAction)an ordinary ObjC message over DO: [element performAction: …]
D-Bus signals (focus-/value-changed)NSDistributedNotificationCenter, or a client callback proxy the app messages on events (DO is bidirectional); implement NSAccessibilityPostNotification to route here
D-Bus XML introspectionthe @protocol itself — compile-time typed, no introspection needed
bus service activationlaunchd (already in the Gershwin/NextBSD stack)

5UI tests — native and external

Two ways to drive real widgets: from inside the app (native NSEvents, semantic) or from outside at the X11/VM layer (toolkit-agnostic, pixel-based).

5.1 · Native, in-process: NSEvent synthesis

5.2 · External, toolkit-agnostic (works on GNUstep — input only)

ToolMouseKeyboardWidget-targetRead screenRuns where
XTEST / xdotoolwindow onlylive X11 / Xvfb
Xnee/cnee (record-replay)live X11 / Xvfb
scrot / import · ImageMagick · tesseractimage/OCRanywhere (files)
SikuliX / PyAutoGUI (image-based)by imagelive X11 / Xvfb
LDTP / Dogtail / Accerciser (AT-SPI)a11ya11yblocked on GNUstep

6Acceptance / BDD — a native Gherkin framework (proposed)

Human-readable acceptance tests are buildable natively, with direct prior art: Frank and Calabash did this for iOS — Gherkin .feature files on top, an in-app server that walks the view hierarchy underneath. GNUstep suits the same design, and it routes around the §4 accessibility gap:

.feature (Gherkin)──▶ godog runner + steps──▶ JSON / Unix socket──▶ in-app TestAgent (ObjC/AppKit)──▶ walk view tree · synth NSEvent · read state
LayerChoiceWhy
Human layerGherkin .featureReadable by non-coders; one corpus runs in CI and locally
Runnergodog (Cucumber-for-Go, single static binary) or behave (Python)Reused, not built; zero-dep binary is ideal for Actions and local. (No official C Gherkin lib, so a fully-native ObjC runner would embed a parser — reuse is pragmatic.)
TransportJSON line protocol over a Unix socketLanguage-neutral — the Go/Python runner never speaks ObjC DO
In-app TestAgentSmall GNUstep framework linked into dev/test buildsWalks the view tree, finds controls by title/tag/class, synthesizes NSEvents (§5.1), reads control state for assertions
Fallbackxdotool + screendump/scrot + OCRSame steps drop to pixels for un-instrumented apps or the whole-ISO gate
features/login.feature — the same file runs in CI and locally
Feature: First-boot login
  Scenario: Admin reaches the desktop
    Given the Gershwin session is running
    When I log in as "admin" with no password
    Then I should see the "System Disk" icon    # agent: findControlByTitle != nil (not an OCR guess)

7System / smoke — the current gershwin-on-nextbsd gate

Today's automation is a system/smoke test: boot the ISO in QEMU, drive input through the monitor socket (no guest agent), capture the framebuffer, assert with a colour-count heuristic + OCR. It lives in .github/workflows/build.yml (test job) + three tests/*.sh scripts.

boot ISO (QEMU/UEFI, headless)──▶ boot-test: greeter painted?──▶ loginwindow-test: sendkey admin──▶ workspace-test: desktop up?──▶ release continuous
build.yml — QEMU launch (input + capture over one monitor socket)
qemu-system-x86_64 -machine q35 -m 4G -smp 2 $ACCEL -bios "$OVMF" \
  -cdrom "$ISO" -boot d -vga std \
  -serial file:tests/serial.log \
  -monitor unix:tests/mon.sock,server,nowait \   # sendkey + screendump channel
  -nic user,model=e1000 -display none -no-reboot
# ACCEL = "-accel kvm -cpu host" if /dev/kvm else "-accel tcg -cpu qemu64"
tests/*.sh — inject via socat/sendkey, capture via screendump, assert via %k + OCR
mon() { printf '%s\n' "$*" | socat -t2 - "UNIX-CONNECT:tests/mon.sock" >/dev/null 2>&1 || true; }
key() { mon "sendkey $1"; sleep 0.2; }
colors() { identify -format '%k' "$1"; }               # unique-colour count

# boot-test.sh:   PASS when %k > 64        (COLOR_THRESHOLD)  → greeter painted
# loginwindow:    type "admin", ret, ret  (NON-gating, best-effort)
# workspace-test: PASS when %k > 1500 OR tesseract finds "System Disk"  (else exit 1)

8VM-level automation (drives §7; how to add clicks)

9Regression — cross-cutting

Regression isn't a separate tool; it's running the other categories on every change and catching what came back. Native building blocks:

10Gaps & a full-pyramid strategy

System / ISO smoke — QEMU gate (have it)
no guest agent
Acceptance/UI — BDD + agent (build, §6)
routes around no-a11y
Functional/integration — DO / StepTalk
native driver
Unit — Testing fw / UnitKit
native, strong

11Sources

Flagged/unverified: ObjC BDD-unit frameworks (Kiwi/Cedar/Specta) on GNUstep — Apple/XCTest-oriented, compatibility not confirmed. StepTalk 0.10.0 release date (NEWS undated). No existing DO/StepTalk-based GUI-test harness was found (viable, not observed). QEMU mouse_move under -device usb-tablet is version-dependent — the reliable absolute-click path is QMP input-send-event.