Testabdeckung Kapitel 13.1 (Tests, Fehlerbehandlung)

Vier neue xUnit-Testprojekte (LehrerApp.Tests, .Data.Tests, .Desktop.Tests,
.Sync.Tests) mit zusammen 104 Tests: GradingService, SchoolYearService,
Repositories gegen In-Memory-LiteDB, Mitarbeits-Aggregation (3.2),
Zeugnisnotenberechnung (2.4) und ConflictResolver.

Dabei zwei echte Fehler in der Sync-Schicht gefunden und behoben:
- SyncEvent.EventId fehlte [BsonId], wodurch EventQueue.Acknowledge()
  nie etwas aus der Queue löschte.
- ConflictResolver verglich Zeitstempel unterschiedlicher DateTimeKind
  direkt (LiteDB liefert Local statt Utc zurück), was die Gleichstand-
  Regel außerhalb von UTC+0 verfälschte.

SchoolYearService.CurrentSchoolYear()/RecentSchoolYears() um ein optionales
today-Argument erweitert, um den Schuljahreswechsel deterministisch zu
testen; LiteDbContext um einen Stream-Konstruktor für In-Memory-Tests.
This commit is contained in:
2026-08-13 11:05:06 +02:00
parent a8b8a40b52
commit 273f459119
15 changed files with 1558 additions and 14 deletions
+37 -5
View File
@@ -452,11 +452,43 @@ Fächer- und Kompetenzverwaltung existiert bereits in
- [x] **13.1.1** Testprojekt `LehrerApp.Tests` angelegt (referenziert `LehrerApp.Core`, analog zu
[LehrerApp.Data.Tests](LehrerApp.Data.Tests/LehrerApp.Data.Tests.csproj) für die Datenschicht).
Noch ohne Testfälle — folgt mit 13.1.2ff.
- [ ] **13.1.2** Unit-Tests für `GradingService` (Notenschlüssel-Grenzfälle, gewichteter Schnitt).
- [ ] **13.1.3** Unit-Tests für `SchoolYearService` (Jahreswechsel, Halbjahresgrenze).
- [ ] **13.1.4** Repository-Tests gegen eine In-Memory-LiteDB.
- [ ] **13.1.5** Tests für die Mitarbeits-Aggregation (3.2) und Zeugnisnotenberechnung (2.4).
- [ ] **13.1.6** Tests für `ConflictResolver`.
- [x] **13.1.2** Unit-Tests für `GradingService` (Notenschlüssel-Grenzfälle, gewichteter Schnitt)
41 Tests in [GradingServiceTests.cs](LehrerApp.Tests/GradingServiceTests.cs), deckt
`CalculateGrade`, `ValidateGradingKey`, `WeightedAverage`, `RoundToGrade`, `ParticipationGrade`,
`ValidateGradingScheme` und `CalculateReportGrade` ab.
- [x] **13.1.3** Unit-Tests für `SchoolYearService` (Jahreswechsel, Schuljahresgrenze) —
13 Tests in [SchoolYearServiceTests.cs](LehrerApp.Tests/SchoolYearServiceTests.cs).
Dafür `CurrentSchoolYear`/`RecentSchoolYears` um ein optionales `today`-Parameter
erweitert (Default weiterhin `DateTime.Today`), um den 1.-August-Wechsel deterministisch
zu testen, ohne bestehende Aufrufer zu ändern.
- [x] **13.1.4** Repository-Tests gegen eine In-Memory-LiteDB — `LiteDbContext` um einen
`Stream`-Konstruktor erweitert (`new LiteDatabase(stream)`, kein Datei-I/O nötig),
19 Tests in [RepositoryTests.cs](LehrerApp.Data.Tests/RepositoryTests.cs): u.a.
Kaskadenlöschung bei `GroupRepository.Delete`/`ExamRepository.Delete`/
`ParticipationSessionRepository.Delete`, Eindeutigkeitsprüfungen (`GroupMembership`,
`ParticipationEntry`), Fach-Validierung (Trimmen, Duplikate, Löschsperre bei Verwendung),
`GradingScheme`-Auflösung gruppenspezifisch vs. Voreinstellung.
- [x] **13.1.5** Tests für die Mitarbeits-Aggregation (3.2) und Zeugnisnotenberechnung (2.4) —
neues Testprojekt `LehrerApp.Desktop.Tests` (referenziert `LehrerApp.Desktop`, damit
ViewModel-Logik ohne echtes UI getestet werden kann), gemeinsame Fakes in
[Fakes.cs](LehrerApp.Desktop.Tests/Fakes.cs). 9 Tests für
[ParticipationGradeAggregationTests.cs](LehrerApp.Desktop.Tests/ParticipationGradeAggregationTests.cs)
(gewichtetes Mittel, "nicht bewertet" ≠ schlechte Note, Trend, Aspektgewicht 0, Halbjahresfilter,
Übernehmen-Update-statt-Duplikat) und 9 für
[ReportGradeCalculationTests.cs](LehrerApp.Desktop.Tests/ReportGradeCalculationTests.cs)
(Schema-Auflösung gruppenspezifisch/Voreinstellung/Fallback, Pflicht-Begründung beim
Übersteuern, Festschreiben friert den Stand ein, Rundungsregel).
- [x] **13.1.6** Tests für `ConflictResolver` — neues Testprojekt `LehrerApp.Sync.Tests`,
10 Tests in [ConflictResolverTests.cs](LehrerApp.Sync.Tests/ConflictResolverTests.cs)
(Desktop schlägt Companion immer, Gleichstand nach Zeitstempel, kein Konflikt bei
unterschiedlicher Entität/gleichem Gerät). Dabei zwei echte Fehler gefunden und behoben:
(1) `SyncEvent` hatte keine `[BsonId]`-Markierung auf `EventId` — LiteDB vergab dadurch
einen eigenen, unabhängigen `_id`, wodurch `EventQueue.Acknowledge()` nie etwas aus der
Queue löschte (verlorene Konflikte blieben für immer offen). (2) `ConflictResolver` verglich
`local.Timestamp` (aus LiteDB, `Kind=Local` mit verschobenen Ticks) direkt mit
`remote.Timestamp` (`Kind=Utc`) — `DateTime`-Vergleiche ignorieren `Kind` und vergleichen
nur rohe Ticks, wodurch die Gleichstand-Regel außerhalb von UTC+0 falsch entschied. Fix:
`.ToUniversalTime()` auf beiden Seiten vor dem Vergleich.
### 13.2 Fehlerbehandlung & Logging
- [ ] **13.2.1** Zentrale Exception-Behandlung mit verständlicher Fehlermeldung statt Absturz.