OTX Unit-Tests
- Jörg Supke

- 26. Okt.
- 4 Min. Lesezeit
Aktualisiert: 29. Okt.

Wie lässt sich sicherstellen, dass die Prüflogik wirklich das tut, was sie soll? Auch OTX als Programmiersprache muss – wie jeder andere Code – getestet werden. Der Test testet den Test – klingt verrückt, ist aber notwendig: Denn wo Prüflogik implementiert wird, braucht es auch Verifikation!
Inhalt
Einleitung
Um die in OTX programmierte Prüflogik gegen das erwartete Verhalten abzusichern, hat EMOTIVE die UnitTest-Extension für OTX entwickelt. Sie ist selbst OTX und erweitert den Core um Test-Prozeduren und Testfälle, Setup- und TearDown-Prozeduren sowie Neue Actions und Terms. Außerdem ist sie in der Lage, die komplette Umgebung einschließlich der Diagnosekommunikation (PduSimulation-Extension) für verschiedene Test-Szenarien zu simulieren. Die Extension wurde nach der ISO 29119 "Software Testing“ entwickelt und ist im Funktionsumfang ähnlich dem Test-Framework NUnit. Mit der UnitTest-Extension können Unit-Tests und Integrationstests beschrieben werden. Über ein separates Test-Projekt im OTF sind die Tests klar von der Prüflogik getrennt, so dass produktive Prüflogik und Test nie zusammen mit den Tests ausgeliefert werden können.
Grundlagen zum Testen von Software
Zuerst eine kurze Einführung zu den den Grundlagen des Testens von Software. Es gibt im Wesentlichen drei Arten von Tests, siehe Testpyramide in Abbildung 1.
Unit-Tests
Testen eine möglichst kleine, eigenständige Komponente.
Integrationstests
Testen das Zusammenwirken zwischen mehreren Komponenten in einem System, meist anhand von konkreten Anwendungsfällen wie beispielsweise Flashen, Kodieren oder Verbau-Lesen.
End-to-End-Test
Testen das Gesamtsystem in einer möglichst realitätsnahen Umgebung.
Je höher man in der Pyramide kommt desto teurer wird es und desto länger dauern die Tests.

Was sind Unittests?
Unit-Tests testen die kleinsten Einheiten einer Software auf ihre Funktionalität. Ziel ist es, jede einzelne Komponente unabhängig und isoliert von anderen Daten und externen Einflüssen nach jeder Änderung zu prüfen und somit die Qualität der Software sicherzustellen. Das ganz bezeichnet man auch als Regressionstest.
Was ist ein guter Unit-Test? Ein guter Unit-Tests wird mit dem so genannten FIRST-Prinzip beschrieben: Fast - Independent - Repeatable - Self-Validating - Timely. Er muss also schnell und unabhängig sein, muss bei Wiederholung immer zum selben Ergebnis führen, er muss entweder bestanden werden oder fehlschlagen und am Besten schreibt man den Test vor der Implementierung.
Für Unit-Tests gibt es im Wesentlichen zwei Testverfahren: Das spezifikationsorientierte Testverfahren und das strukturorientierte Testverfahren, siehe Abbildung 2.

Das spezifikationsorientierte Testverfahren bezeichnet man auch als Black-Box-Test, da es immer gegen die Spezifikation testet. Black-Box-Test sind robust gegen Änderungen, bieten einen guten Schutz vor Regression, müssen aber immer manuell erstellt werden. Der White-Box-Test betrachtet den inneren Aufbau des Codes. Im Gegensatz zum Black-Box-Tests können White-Box-Tests automatisiert erzeugt werden. Sind aber empfindlich gegen Änderungen. Im Umfeld von OTX werden normalerweise immer Black-Box-Tests verwendet.
Wie schreibt man einen Unit-Test?
Hierfür gibt es die so genannte Äquivalenzklassenbildung und die Grenzwertanalyse. Bei der Äquivalenzklassenbildung definiert man für die Parameter Wertebereichsklassen, für die man gleiches Verhalten erwartet, die so genannten Äquivalenzklassen. Dann sucht man für jede gefundene Klasse einen Repräsentanten und schreibt dafür einen Testfall. Ziel ist, eine hohe Testabdeckung mit möglichst wenigen Testfällen. Die Grenzwertanalyse ist letztlich eine Äquivalenzklassenbildung an den Grenzen, da die Fehler nicht gleich verteilt sind, sondern häufiger an den Grenzen auftreten.
Der nachfolgende OTL-Code (OTX in ASCII-Notation) soll dies am vereinfachten Beispiel einer Test-Prozedur mit zwei In- und einem Out-Parameter darstellen. Die Test-Prozedur prüft auf die korrekte Durchführung einer Integer-Division: der Quotient (q) ergibt sich aus Dividend (D) geteilt durch Divisor (d).
[Test]
// Normal division
[TestCase(D = 10, d = 2, expected q = 5)]
// Division with a negative sign
[TestCase(D = -10, d = -2, expected q = 5)]
[TestCase(D = -10, d = 2, expected q = -5)]
// Division with remainder
[TestCase(D = 11, d = 3, expected q = 3)]
// Division at range border
[TestCase(D = 2147483647, d = 2, expected q = 1073741823)]
// Division from zero
[TestCase(D = 0, d = ValueList(1, -1), expected q = 0)]
// Division with zero
[TestCase(D = ValueList(10, -10, 0), d = 0, exception Exception)]
[Parallelizable]
IntegerDivisionTest(in Integer D, in Integer d, out Integer q = 0)
{
// Can contain arbitrary OTX code, like a ProcedureCall
Division(D, d, out q);
}
// OTX procedure which will be tested
procedure Division(in Integer D, in Integer d, out Integer q = 0)
{
// Division as a simple example for test logic to be tested
q = D / d;
}Um die OTX-Prozedur als Test-Prozedur zu kennzeichnen, erhält sie am Anfang das Attribut [Test]. Es gibt 7 Testfälle, die so genannten [TestCase]. Jeder Testfall enthält die Eingabe-Parameter Dividend und Divisor sowie das erwartete Ergebnis der Division als Quotient. Der erste exemplarische Testfall rechnet einfach 10 geteilt durch 5, was 2 ergeben sollte. Der zweite Testfall bezieht das Vorzeichen mit ein. Danach wird geprüft, ob bei 11 geteilt durch 3 der Rest der Division abgeschnitten wird. Ich nächsten Testfall geht man an die Bereichsgrenze (hier 32-Bit Integer) und danach kommt die Division mit Null ins Spiel. Im letzten Testfall wird das Verhalten im Fehlerfall bei verschiedenen Divisionen durch Null geprüft. Hier wird eine konkrete Ausnahme, die ArithmeticException erwartet. Da die Testprozedur keine Seiteneffekte hat kann sie bei der Testausführung parallelisiert werden, was durch ein entsprechendes Attribut gekennzeichnet ist. Dies kann insbesondere bei sehr vielen Testfällen die Ausführungszeit der Tests deutlich reduzieren.
Tool-Unterstützung

Für die UnitTest-Extension gibt es eine komplette Integration in das Open Test Framework. Die Tests können graphisch oder im OTL-Editor erstellt und im Test-Explorer automatisiert ausgeführt werden. Jede Ausführung erzeugt ein übersichtliches Testprotokoll. Über den so genannten Test-Kontext können verschiedene Umgebungsszenarien über OTX-Mapping-Dateien simuliert und ebenfalls automatisiert ausgeführt werden. Es gibt zusätzlich einen Test-Protokoll Viewer, der auch die Unterschiede zwischen verschiedenen Testdurchläufen darstellen kann.
Ein Testprojekt kann man als PUX-Datei exportieren. Diese Datei enthält alle erforderlichen Abhängigkeiten für die Ausführung der Tests. Eine PUX-Datei ist unabhängig von der Entwicklungsumgebung mithilfe der mitgelieferten Konsolenanwendung ausführbar, beispielsweise automatisiert in einer CI/CD-Umgebung.
Fazit
Die Unit-Test Extension schließt die Lücke zur Absicherung der OTX-Prüflogik im Prozess. Mit ihr ist es möglich, OTX-Prüflogik im Prozess eines Automobilherstellers auf erwartetes Verhalten zu prüfen und Testprotokolle zu erstellen. Die komplette Integration in das Open Test Framework erlaubt es, effizient und komfortabel Testfälle zu schreiben, auszuführen und zu Änderungen vergleichen. Testfälle können als PUX-Datei exportiert und in einer CI/CD-Umgebung automatisiert ausgeführt werden.


