--- 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`).