Wie ich WPF-Anwendungen teste
- Matt
Die verbreitete Antwort auf die Frage, wie man WPF testet, lautet gar nicht. Das war lange auch meine, und es hat mich mehrfach Abende gekostet.
Inzwischen teste ich drei Dinge und lasse den Rest bewusst weg.
ViewModels
Der ganze Punkt an MVVM ist, dass die Logik in einer Klasse liegt, die kein Fenster braucht. Genau dort setzt der Test an:
[TestClass]
public class SucheViewModelTests
{
[TestMethod]
public async Task Suche_ohne_Treffer_setzt_Hinweistext()
{
var service = Substitute.For<IKundenService>();
service.Suchen("xyz", Arg.Any<CancellationToken>()).Returns([]);
var vm = new SucheViewModel(service);
vm.Suchbegriff = "xyz";
await vm.SuchenCommand.ExecuteAsync(null);
Assert.AreEqual(0, vm.Ergebnisse.Count);
Assert.AreEqual("Keine Treffer", vm.Statustext);
}
}
Dass SuchenCommand hier ein IAsyncRelayCommand ist und ExecuteAsync kennt, kommt vom CommunityToolkit. Mit einer selbstgebauten ICommand-Implementierung wird derselbe Test unangenehmer.
Konverter
IValueConverter sind reine Funktionen und damit die dankbarsten Testkandidaten überhaupt. Sie werden trotzdem selten getestet, obwohl genau dort die stillen Fehler sitzen: der Fall null, der Fall DependencyProperty.UnsetValue, die falsche Kultur.
[TestMethod]
[DataRow(null, "-")]
[DataRow(0, "-")]
[DataRow(1500, "1.500,00 €")]
public void Betrag_wird_formatiert(object eingabe, string erwartet)
=> Assert.AreEqual(erwartet,
new BetragConverter().Convert(eingabe, typeof(string), null,
new CultureInfo("de-DE")));
Was ich nicht teste
Die Oberfläche selbst. UI-Automatisierung über FlaUI oder WinAppDriver funktioniert technisch, aber die Tests sind langsam, brechen bei jeder Layoutänderung und melden Fehler, die keine sind. Bei zwei Projekten habe ich es versucht und beide Male nach einem halben Jahr wieder ausgebaut, weil niemand mehr hingeschaut hat, wenn sie rot waren.
Ein roter Test, den alle ignorieren, ist schlechter als kein Test.
Der Trick mit dem Thread
Ein Detail, das jeden einmal erwischt: Sobald ein Test ObservableCollection aus einem anderen Thread verändert oder ein DispatcherTimer beteiligt ist, braucht es einen STA-Thread. In MSTest stellt man das über eine .runsettings-Datei ein:
<RunSettings>
<RunConfiguration>
<ExecutionThreadApartmentState>STA</ExecutionThreadApartmentState>
</RunConfiguration>
</RunSettings>
Eingebunden wird sie über die Projektdatei:
<RunSettingsFilePath>$(MSBuildProjectDirectory) ests.runsettings</RunSettingsFilePath>
Das gilt dann für alle Tests im Projekt. Wer nur einzelne braucht, kann sie in ein eigenes Testprojekt legen.
Wer die Meldung zum ersten Mal sieht, sucht sonst lange an der falschen Stelle.