# 🧐 QA Test-Strategie — Compose Desktop App > Stand: 2. April 2026 > GĂŒltig fĂŒr: Kotlin Multiplatform / Compose Desktop (JVM) Frontend --- ## Ziel Konsistente, schnelle und verlĂ€ssliche Tests fĂŒr die Desktop-App. Diese Richtlinie definiert die Testpyramide, das Tooling und verbindliche Konventionen (Benennung, Ordnerstruktur, Arrange–Act–Assert), damit Frontend, QA und DevOps nahtlos zusammenarbeiten können. --- ## Testpyramide fĂŒr Compose Desktop 1) Unit-Tests (70–80%) - Reine Kotlin-Logik ohne UI: ViewModels, Reducer/Intent-Handler, Mapper/Formatter, Validatoren - Keine echten I/O‑Operationen, keine echten Uhren/Dispatcher → alles gemockt oder gefaked - Laufzeit: < 100 ms pro Test, parallelisierbar 2) Integrations-Tests (15–25%) - Zusammenspiel mehrerer Komponenten auf JVM (z. B. ViewModel + Repository mit In‑Memory/Fake Datenquelle) - Optionale Nutzung eines Test-Dispatchers; kein echtes Netzwerk/Dateisystem - Ziel: Korrekte State-Transitions, Fehlerpfade, Laden/Speichern‑Flows 3) UI-Tests (5–10%) - Compose UI Test Framework (Desktop): Semantics-basierte Interaktion und Assertions - Abdecken kritischer User-Flows (Happy Path + 1–2 Edge Cases) - Headless-fĂ€hig fĂŒr CI (Xvfb/JetBrains Runtime Headless). KĂŒrzere, robuste Tests (keine Pixel-Assertions) Leitprinzip: So viel wie möglich unterhalb der UI testen (schnell/stabil), nur kritische End-to-End‑Flows als UI-Test absichern. --- ## Tooling-Entscheidung - Test-Framework: `kotlin.test` - Einheitlich ĂŒber KMP, minimaler Overhead, gute IDE/CI-Integration - Mocking/Stubs: `MockK` - Kotlin‑freundlich, unterstĂŒtzt Klassen/Objekte, klare Verifikation von Interaktionen - Coroutines/Flows testen: `kotlinx-coroutines-test` - `StandardTestDispatcher`, `runTest {}`, virtuelle Zeitsteuerung, deterministische Tests - UI-Tests: Compose UI Test (Desktop) - `compose.ui.test` APIs: `onNodeWithText`, `performClick`, `assertIsDisplayed`, etc. - Headless in CI via JVM Args/Virtual Display (DevOps setzt Runner bereit) - Optional (nur falls notwendig): - Snapshot-Testing vermeiden (fragil im Desktop-Kontext) - Property-based Testing optional (z. B. mit Kotest property) — nicht Standard --- ## Ordner- und Modulstruktur (KMP/Compose Desktop) Beispielhaft; bitte auf bestehende Modulnamen im Repo mappen: - `frontend/` (Wurzel des KMP-Frontends) - `commonMain/` — UI-agnostische Logik, Models, Use-Cases, Validatoren - `commonTest/` — Unit- und Integrations-Tests fĂŒr Common - `desktopMain/` — Desktop-spezifische UI (Compose Desktop) und Integrationscode - `desktopTest/` — UI-Tests (Compose UI Test) und Desktop-Integrations-Tests Konvention: - Business-/State-Logik so weit wie möglich in `commonMain` halten → maximaler Anteil schneller Unit-Tests in `commonTest`. - UI-spezifische Tests in `desktopTest` nur fĂŒr kritische End-to-End‑Flows. --- ## Benennungs- und Strukturkonventionen - Test-Klassenname: `Test.kt` oder `Test.kt` - Test-Funktionsname (kotlin.test): ``fun `__()`` - Beispiel: ``fun `onSave_withInvalidInput_emitsValidationError`()`` - Arrange–Act–Assert (AAA) strikt einhalten: - Arrange: Testdaten, Mocks, System Under Test (SUT) erstellen - Act: eine gezielte Aktion / Intent ausfĂŒhren - Assert: erwarteten Zustand/Ereignisse prĂŒfen - Given/When/Then als Kommentare optional: - `// Given`, `// When`, `// Then` — keine unnötigen Kommentare, nur zur Struktur - Ordner nach DomĂ€ne/Feature gruppieren (bevorzugt): - `commonTest//...`, `desktopTest//...` --- ## Coroutines & State-Tests (Beispiel) ```kotlin import kotlin.test.* import kotlinx.coroutines.test.* class AnmeldungViewModelTest { @Test fun `onWeiter_withEmptyPflichtfeld_emitsValidationError`() = runTest { // Arrange val vm = AnmeldungViewModel(/* fakes */) // Act vm.onWeiter() // Assert assertTrue(vm.state.value.validationErrorShown) } } ``` --- ## Compose UI Test (Desktop) — GrundsĂ€tze - Selektoren ĂŒber Semantics (Text, ContentDescription, TestTags via `Modifier.testTag("
")`) - Keine FragilitĂ€t: Keine Pixel-/Layout‑abhĂ€ngigen Asserts - Jeder UI-Test prĂŒft genau einen kritischen Flow, Laufzeitziel < 3s pro Test Minimalbeispiel: ```kotlin import androidx.compose.ui.test.* import kotlin.test.Test class NennungUiTest { @Test fun startliste_filterByBewerb_showsOnlyMatchingEntries() { // Given val rule = createComposeRule() rule.setContent { AppRoot() } // When rule.onNodeWithTag("filter-bewerb").performClick() rule.onNodeWithText("CSN-C-NEU 95cm").performClick() // Then rule.onAllNodesWithTag("startlisten-zeile") .assertCountEquals( /* erwartete Anzahl */ 5) } } ``` Hinweis: Die konkrete Setup-/Runner-Konfiguration fĂŒr Headless-AusfĂŒhrung wird in `docs/07_Infrastructure/ci-testing.md` (DevOps) dokumentiert. --- ## Testdaten und Fakes - Fakes statt Mocks, wenn Verhalten wichtiger als Interaktion ist (z. B. InMemoryRepository) - Test-Datenbuilder verwenden (kleine DSLs / Fabriken) statt anonymer Maps/Listen - ZeitabhĂ€ngiges Verhalten: `TestCoroutineScheduler` + injizierbare Clock/Now‑Provider --- ## QualitĂ€tsregeln fĂŒr Tests - StabilitĂ€t > VollstĂ€ndigkeit: flaky Tests sind zu entfernen oder neu zu schneiden - Ein Test – ein Verhalten: keine ĂŒberladenen Tests - Determinismus: keine versteckten Sleeps/Delays, virtuelle Zeit nutzen - Lesbarkeit: AAA, sprechende Namen, knappe Arrange‑Blöcke --- ## CI/CD‑Integration (Kurz) - Unit/Integrations: JVM headless, parallel (Gradle `--parallel`), `-Dkotlinx.coroutines.scheduler.keep.alive.sec=
` falls nötig - UI: Headless via Xvfb oder JBR Headless; kurzer Smoke‑Satz ausfĂŒhren - Reports: Gradle HTML/JUnit XML; Flaky-Tracking ĂŒber CI möglich --- ## Checkliste (DoD) - [ ] Neuer Test folgt AAA und Namenskonvention - [ ] LĂ€uft headless lokal und in CI - [ ] Keine externen AbhĂ€ngigkeiten ohne Fake/Mock - [ ] UI-Test nutzt Semantics/TestTags und ist robust --- ## Verweise - Roadmap: `docs/04_Agents/Roadmaps/QA_Roadmap.md` (Punkt A-1) - DevOps (Headless/CI): `docs/07_Infrastructure/` (geplantes Dokument `ci-testing.md`)