Embedded-Software-Test objektorientiert für C++

Objektorientiertes Testen von Embedded-Software

Die Einführung von C++ in ein Embedded-Projekt ist für die meisten Teams eine große Herausforderung. Unterschätzt man den Aufwand, verzögert sich dadurch unter Umständen die ganze Entwicklung. Und jetzt soll die C++ Software mit zusätzlichen komplexen Tests noch auf Sicherheit hin geprüft werden?

Lässt sich denn Qualität überhaupt in eine C++ Software hineintesten?

Die objektorientierte Programmierung bringt neben attraktiven Features zusätzlich neue Fehlerquellen mit ins Projekt. Dazu gehören komplexe Eigenschaften, wie Datenkapselung, Vererbung, Assoziation, Aggregation, Komposition, Polymorphismus oder auch dynamisches Binden.

Objekte und Instanzen von Klassen werden in unterschiedlichen Kontexten verwendet oder können sich in unterschiedlichen Zuständen befinden, die das Verhalten beeinflussen (verschiedene Konstruktoren), und sie haben Beziehungen, die getestet werden müssen. Beim dynamischen Binden (Polymorphie) sollte man jede mögliche Bindung sowie die Verlagerung des Kontrollflusses aus den Prozeduren in die Nachrichten zwischen den Klassen sorgfältig testen.

Welche zusätzlichen Tests von C++ Software sind erforderlich?

Ganz oben auf der Liste der zu testenden Elemente stehen die Klassen. Wir betrachten sie von mehreren Seiten genauer. Das Testen von Klassen-Hierarchien erfolgt in der Vertikalen entlang der Vererbungshierarchie sowie in der Horizontalen entlang der Beziehungen zu anderen Klassen.

Klassenhierarchie

Bild 1: Klassenhierarchie

Die Klassen-Architekturen testet man sich auf Basis der Klassendiagramme entlang der Klassenhierarchie von der Basisklasse aus bis zur abgeleiteten Klasse. Die Basisklasse wird dabei zuerst getestet. Als drittes führt man die Klassen-Integrationstest durch. Auf Basis von Sequenzdiagrammen entlang der Assoziationen sind das Zusammenspiel der einzelnen Objekte sowie das korrekte Aufrufen von Funktionen besonders wichtig.

Klassenverband

Bild 2: Klassenverband, Klassenhierarchie ohne Vererbung

Bei den Klassen- und Integrationstests kommt es vor allem darauf an, dass alle Methoden ausgeführt und alle Parameter und Rückgabewerte korrekt verwendet werden (Äquivalenzklassen- und Grenzwerttests). Geht das System richtig mit gültigen und ungültigen Parametern um (Positiv- und Negativtests)? Wird eine ausgehende Exception ausgelöst und eine hereinkommende Exception als “xx wird xx” behandelt? Bei Klassen mit einem Zustandsverhalten sollte getestet werden, ob jeder Zustand erreicht und jede Methode in jedem Zustand des Objektes sowie alle Zustandsübergänge ausgeführt werden. Dazu kommt das Testen aller Beziehungen bezüglich falscher oder fehlender Verbindung und falscher Multiplizitäten.

Klasse_mit_Zustandsverhalten

Bild 3: Klasse mit Zustandsverhalten

Beim Testen der Vererbung sind zuerst die Methoden und Attribute in der Basisklasse dran. Da diese Methoden und Attribute vererbt werden können, müssen wir auch geerbte Methoden und Attribute aus getesteten Basisklassen im Kontext der abgeleiteten Klasse nochmals unter die Lupe nehmen. Methoden einer abgeleiteten Klasse können dabei überschrieben werden.

Testen von polymorphen Strukturen: Tauchen in einem Vererbungszweig einer Klassenhierarchie mehrere Methoden auf unterschiedlichen Hierarchieebenen auf, jedoch mit gleicher Signatur, bestimmen wir erst zur Laufzeit, welche der Methoden von welchem Vererbungsweg wir für ein gegebenes Objekt verwenden. Bei tiefen Klassenhierarchien ist schwer nachvollziehbar, welche Methode gerufen wird. Achten Sie darauf, dass die beteiligten Klassen instanziiert sind. Jede mögliche Bindung sollte ebenfalls getestet werden.

Polymorphie_abstrakte_Klasse_und_Interfaces

Bild 4: Polymorphie, abstrakte Klassen und Interfaces

Gut zu testende Software führt zu neuen Software-Designanforderungen

Sicherheit ist auch eine Frage von handwerklich sauberem Code. Genau wie in Embedded-Systemen ist auch bei C++ Software von Anfang an eine durchdachte Softwarearchitektur unerlässlich, um einen sicheren Qualitätsstandard zu erreichen. Im Sinne einer gut zu testenden Software  kann dies auch im Rahmen der objektorientierte Programmierung zu neuen Software-Designanforderungen führen.

Mehrfaches und wiederholtes Vererben führt zu erschwerter Verständlichkeit und erhöht damit die Fehlerwahrscheinlichkeit. Das beste Rezept dagegen ist, tiefe Vererbungshierarchien zu vermeiden. Gleiches gilt für Polymorphie und dynamisches Binden.

Fehlerquellen

Bei der Programmierung paralleler Prozesse in objektorientierten Systemen gibt es zahlreiche Fehlerquellen. Nachfolgend eine Übersicht der wichtigsten Tests zur Vermeidung solcher Fehler:

  1. Zu testende Fehlerquellen beim Warten auf Synchronisationsereignisse:
    • Unnötiges Warten: Rechenleistung geht verloren – Operationssequenzen können nicht ausgeführt werden, obwohl sie ausführbar sein sollten.
    • Zu langes Warten: Timeout wird nicht erkannt – der Prozess „verhungert“ (Starvation/ Deadlock).
    • Fehlendes Warten: unerlaubte Prozessüberlappung/-überlagerung
  2. Shared Variables: Variablen werden von verschiedenen, nebenläufigen Prozessen verwendet.
  3. Race Conditions: Zeitkritische Abläufe können bei gleichen Eingabewerten unterschiedliche Ergebnisse liefern.
  4. Nachrichtenkonflikte: Können bei der Kommunikation und Synchronisation auftreten, z.B. eine fehlerhafte Reihenfolge von Nachrichten bei unterschiedlichen äußeren Einflüssen.

Multitasking, parallele-SW-Verarbeitung

Bild 5: Multitasking, parallele SW-Verarbeitung

Fazit

Das richtige Testen von objektorientierten Systemen sollte von Anfang an in das Projektbudget mit eingeplant werden. Verzichtet man darauf zugunsten einer schnelleren oder preisgünstigen Projektabwicklung, ist der Schaden nicht abschätzbar. Die Qualität Ihrer Software hängt einerseits von der Wahl der richtigen Software-Architektur und andererseits von einer genauen Kenntnis der Programmiersprache ab. Mit einem tiefgreifenden Test-Know-how erzielen Sie einen hohen Qualitätsgrad für Ihre Software und einen bedeutenden Wettbewerbsvorsprung.

Weiterführende Informationen

Training: Embedded-Software-Test – Best Practices für den Unit-/Modul-/Komponenten-Test

MicroConsult Training & Coaching zum Thema Test und Debug

MicroConsult Training & Coaching zum Thema Softwareentwicklung

MicroConsult Fachwissen zum Thema Test und Debug

Veröffentlicht von

Ingo Pohle

Ingo Pohle

Ingo Pohle ist Mitgründer und Geschäftsführer der MicroConsult GmbH und international anerkannter Spezialist für Embedded-Lösungen, mit einem reichen Erfahrungsschatz rund um den Einsatz von Embedded-Mikrocontrollern, Bussystemen und RTOS.