6.2 KiB
🧐 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
- 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
- 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
- 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-testStandardTestDispatcher,runTest {}, virtuelle Zeitsteuerung, deterministische Tests
-
UI-Tests: Compose UI Test (Desktop)
compose.ui.testAPIs: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, ValidatorencommonTest/— Unit- und Integrations-Tests für CommondesktopMain/— Desktop-spezifische UI (Compose Desktop) und IntegrationscodedesktopTest/— UI-Tests (Compose UI Test) und Desktop-Integrations-Tests
Konvention:
- Business-/State-Logik so weit wie möglich in
commonMainhalten → maximaler Anteil schneller Unit-Tests incommonTest. - UI-spezifische Tests in
desktopTestnur für kritische End-to-End‑Flows.
Benennungs- und Strukturkonventionen
-
Test-Klassenname:
<ProduktionsKlasse>Test.ktoder<Feature/UseCase>Test.kt -
Test-Funktionsname (kotlin.test):
fun `<Methode/Intent>_<Bedingung>_<ErwartetesVerhalten>()- Beispiel:
fun `onSave_withInvalidInput_emitsValidationError`()
- Beispiel:
-
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/<feature>/...,desktopTest/<flow>/...
Coroutines & State-Tests (Beispiel)
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:
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 Dokumentci-testing.md)