Files
info 66ecf55ea9 docs: OpenWiki-Dokumentation und Auto-Update-Workflow hinzufügen
Fügt generierte OpenWiki-Docs (Architektur, Domain, Betrieb, Tests, Workflows)
sowie den GitHub-Actions-Workflow zur automatischen Aktualisierung hinzu.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 14:53:30 +02:00

106 lines
3.6 KiB
Markdown

---
type: TestingGuide
title: Test-Leitfaden
description: pytest-Setup für whisper-local. Plattformspezifische Tests via skipif, AsyncMock für asyncio-Callbacks, Mocking-Muster für sounddevice und faster-whisper.
tags: [testing, pytest, asyncio, mocking, platform-specific]
---
# Test-Leitfaden
## Setup
```bash
uv run pytest # alle Tests
uv run pytest -v # verbose
uv run pytest tests/test_main.py # einzelne Datei
```
Dev-Dependencies: `pytest>=8.0.0`, `pytest-asyncio>=0.24.0` (in `pyproject.toml` unter `[dependency-groups] dev`).
## Plattformspezifische Tests
Plattformabhängige Tests werden mit `@pytest.mark.skipif` übersprungen:
```python
import sys
import pytest
@pytest.mark.skipif(sys.platform != "linux", reason="nur Linux")
def test_evdev_listener():
...
@pytest.mark.skipif(sys.platform != "win32", reason="nur Windows")
def test_pynput_listener():
...
```
Dieses Muster wird durchgehend angewendet — z.B. in `test_hotkey.py` (evdev vs. pynput), `test_media_mpris.py` (Linux), `test_media_smtc.py` (Windows).
## Async-Tests
Async-Tests verwenden `pytest-asyncio`:
```python
@pytest.mark.asyncio
async def test_on_press_starts_recording():
app = App(config=Config())
app.on_press = AsyncMock()
await app.on_press()
...
```
Async-Callbacks (`on_press`, `on_release`, Mikrofon-Monitor-Callbacks) werden mit `unittest.mock.AsyncMock` gemockt.
## Mocking-Muster
### sounddevice
`Recorder` und `PollMonitor` verwenden `sounddevice`. In Tests wird `sd.InputStream` bzw. `sd.query_devices` gemockt:
```python
@patch("whisper_local.recorder.sd.InputStream")
def test_recorder_start(mock_stream):
recorder = Recorder()
recorder.start()
mock_stream.assert_called_once()
```
### faster-whisper
`Transcriber` wird mit einem gemockten `WhisperModel` instanziiert, um Modell-Downloads zu vermeiden:
```python
def test_transcriber(mock_model):
transcriber = Transcriber(model=mock_model, language="de")
...
```
Der `Transcriber` akzeptiert ein optionales `model`-Argument, das in Tests genutzt wird — ein expliziter Design-Entscheidung (Commit `3a58099`).
### Windows COM / winrt
`test_media_smtc.py` sichert `winrt`-Imports plattformabsichern ab. `test_main.py` mockt `create_monitor`, um COM-Abhängigkeiten zu umgehen.
## Test-Übersicht
| Test-Datei | Fokus | Plattform |
|---|---|---|
| `test_config.py` | Config laden/speichern, Defaults, TOML-Escaping | alle |
| `test_recorder.py` | Recorder start/stop, min_duration-Filter | alle |
| `test_transcriber.py` | Transcriber mit gemocktem Model | alle |
| `test_hotkey.py` | Beide Backends, Key-Repeat-Unterdrückung | je `skipif` |
| `test_inserter.py` | Wayland + Win32, Clipboard-Restaurierung | je `skipif` |
| `test_media_factory.py` | Factory-Dispatch | alle |
| `test_media_mpris.py` | MprisController, Circuit-Breaker | Linux |
| `test_media_smtc.py` | SmtcController, Circuit-Breaker | Windows |
| `test_microphone_monitor.py` | PollMonitor, Start-Check | alle |
| `test_tray.py` | Tray-App, AppState, Icon, Settings | alle |
| `test_download_progress.py` | Download-Dialog | alle |
| `test_main.py` | App-Integration: Zyklus, Config-Reload, Mikrofon | alle |
## Wichtige Test-Konventionen
- **Mocks statt echter Hardware**: Kein echtes Mikrofon, keine echte Tastatur, kein D-Bus, kein SMTC in Tests.
- **`create_monitor` mocken in `test_main.py`**: Verhindert COM-Initialisierung auf Nicht-Windows-Plattformen (Commit `1f8adc5`).
- **Recorder vor Ersatz stoppen**: `test_main.py` stellt sicher, dass der Recorder gestoppt wird, bevor er durch Config-Reload ersetzt wird (Commit `1f8adc5`).