185 lines
6.2 KiB
Markdown
185 lines
6.2 KiB
Markdown
# 🧐 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: `<ProduktionsKlasse>Test.kt` oder `<Feature/UseCase>Test.kt`
|
||
- Test-Funktionsname (kotlin.test): ``fun `<Methode/Intent>_<Bedingung>_<ErwartetesVerhalten>()``
|
||
- 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/<feature>/...`, `desktopTest/<flow>/...`
|
||
|
||
---
|
||
|
||
## 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`)
|