106 lines
3.6 KiB
Markdown
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`).
|