Testautomatisierung beim Parsing von Dokumenten

Als Entwickler steht man regelmäßig vor der Aufgabe, Dokumente mit einem bestimmten Format zu lesen und weiterzuverarbeiten. Ebenso kommt es immer wieder vor, dass es für das gewünschte Dokumentenformat noch keinen passenden Parser gibt. Daraus entsteht die Aufgabe, selbst einen kleinen Parser zu entwickeln. Gesagt, getan. Bei Xml- oder Json-Dokumenten etwa ist das i. d. R. auch relativ schnell erledigt. Nicht selten gibt es aber dennoch verschiedene Besonderheiten, die es zu beachten gilt. Etwa bestimmte Elemente in der Datei, die nicht immer enthalten sind. Nehmen wir als Beispiel ein Dokument, welches einen Kassenbon abbildet. Standardmäßig stehen alle Artikel drin, welche der Kunde gekauft hat. Es gibt allerdings weitere Fälle, wie Artikelrückgaben (Pfand), Stornierung einzelner Zeilen durch das Kassenpersonal, rabattierte Artikel etc. Schnell ergibt sich eine längere Liste von Fällen, die das Parsing und die anschließende Weiterverarbeitung beeinflussen. Aus diesem Grund kann es eine gute Idee sein, möglichst viele Teile davon per Testautomatisierung abzusichern.

Ein konkretes Beispiel

Verlassen wir das Beispiel des Kassenbons und schauen wir uns stattdessen das GPX-Format an. GPX steht für GPS Exchange Format [1]. GPX-Dateien speichern Routen anhand einer Aneinanderreihung von GPS-Koordinaten. Das Format ist stark verbreitet und wird insbesondere zur Weitergabe von Wanderrouten, Fahrradrouten und Ähnliches verwendet. Letztes Jahr habe ich einen Viewer für dieses Format entwickelt [2] und musste mich in diesem Rahmen auch um das Parsing der Dateien kümmern. Im Grunde ist GPX ein auf XML basierendes Format. Es ist somit mit einem C#-Programm relativ einfach zu lesen. Funktionen wie sie vom System.Xml namespace bereitgestellt werden sind zwar bereits sehr alt, verrichten aber ihre Arbeit wie vorgesehen. Ich habe mich entschieden, für das Parsing auf den XmlSerializer [3] zu setzen. Hierzu erzeugt man ein Klassenmodell, welches der Struktur der Xml-Datei entspricht. Anschließend kümmert sich der XmlSerializer darum, aus dem XML-Code die entsprechenden Objekte zu erzeugen und Eigenschaftswerte zuzuordnen. Alle notwendigen Klassen und Methoden für das Parsing habe ich in dem Projekt FirLib.Formats.Gpx auf GitHub hochgeladen [4]. Beim Testen mit GPX-Dateien aus unterschiedlichen Quellen bin ich allerdings auch auf ein paar Besonderheiten gestoßen:

  • GPX-Dateien mit XML Version 1.1. Diese Version des XML-Formats ist sehr wenig verbreitet und wird nach wie vor nicht durch .Net unterstützt.
  • Es existieren unterschiedliche Versionen vom GPX-Format (1.0 und 1.1). Diese unterscheiden sich zwar nur geringfügig, der abweichende XML Namensraum muss bei der Verwendung des XmlSerializers aber beachtet werden
  • Erweiterung am Dateiformat durch andere Tools. Abhängig davon, mit welcher Software die GPX-Datei erzeugt wurde, sind an einigen Stellen im Dokument weitere Informationen enthalten

Für all diese und ggf. weitere Fälle in der Zukunft gilt es sicherzustellen, dass diese sauber funktionieren.

UnitTests mit Beispieldateien als Input

Ein für mich guter Weg an dieser Stelle war Testautomatisierung in Form von UnitTesting. Die Unit ist dabei nicht etwa eine Methode, die sehr wenige Dinge tut. Die Unit ist dabei die Methode, die eine vollständige GPX-Datei lädt. Auf diese Weise lassen sich sowohl der Standardfall, als auch oben beschriebene Sonderfälle automatisch durchtesten. Da die Beispieldateien direkt im Quellcodeverwaltungssystem eingecheckt werden können, ist auch die Ausführungsgeschwindigkeit der Tests flott (i. d. R. wenige 100 Millisekunden). Im Unittest-Projekt sieht das wie folgt aus.

UnitTest-Projekt mit mehreren Beispieldateien als Input

Eine Testmethode sieht dann wie in folgendem Codeausschnit aus. Zunächst wird die GPX-Datei geparst. Anschließend wird mit einfachen Schritten geprüft, ob der Inhalt richtig gelesen wurde. Für alle weiteren Fälle sehen die Test-Methoden sehr ähnlich aus. Das vollständige Test-Projekt liegt unter [5].

[TestMethod]
public void GpxVersion1_1_CompatibilityMode()
{
    var resLink = new AssemblyResourceLink(
        typeof(FileLoadTests),
        "Test_Gpx1_1.gpx");
    using var inStream = resLink.OpenRead();

    var gpxFile = GpxFile.Deserialize(inStream, GpxFileDeserializationMethod.Compatibility);

    Assert.IsNotNull(gpxFile);
    Assert.IsNotNull(gpxFile.Metadata);
    Assert.AreEqual("Kösseine", gpxFile!.Metadata!.Name);
    Assert.AreEqual(1, gpxFile.Tracks.Count);
}

Fazit

Testautomatisierung ist in den meisten Fällen eine gute Idee. In diesem Artikel habe ich ein Beispiel beschrieben, wie ich sie regelmäßig in Projekten einsetze. Hierbei darf man sich nicht täuschen lassen, dass es hier am Beispiel von GPX-Dateien nur 3 verschiedene Testfälle gibt. In einem meiner letzten Projekte waren es bei einem json basierten Format insgesamt 18 verschiedene Testfälle mit jeweils eigener Beispiel-Datei. Jede dieser Dateien adressiert eine Besonderheit, die es beim Parsing zu beachten gibt. Sobald neue Besonderheiten dazukommen ist das Funktionieren der vorherigen Fälle über die Testautomatisierung sichergestellt.

Verweise

  1. Allgemeine Infos zum GPX Format
    https://de.wikipedia.org/wiki/GPS_Exchange_Format
  2. Repository von RK GPXviewer auf GibHub
    https://github.com/RolandKoenig/GpxViewer
  3. Dokumentation der Klasse XmlSerializer
    https://docs.microsoft.com/de-de/dotnet/api/system.xml.serialization.xmlserializer?view=net-6.0
  4. Eigene Implementierung des Parsers für GPX Dateien
    https://github.com/RolandKoenig/FirLib/tree/main/src/FirLib.Formats.Gpx
  5. Tests für obigen Parser
    https://github.com/RolandKoenig/FirLib/tree/main/src/FirLib.Formats.Gpx.Tests

Ebenfalls interessant

  1. MSTest, oder wie ich es verwende
    https://www.rolandk.de/wp-posts/2014/08/mstest-oder-wie-ich-es-verwende/
  2. Um was geht es beim Testen
    https://www.rolandk.de/wp-posts/2015/02/um-was-geht-es-beim-testen/
  3. Protobuf in C# serialisieren und deserialisieren
    https://www.rolandk.de/wp-posts/2023/03/protobuf-in-c-serialisieren-und-deserialisieren

Schreibe einen Kommentar

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.