Tests

(Bild: hywards – Fotolia)

Mithilfe eines konkreten Beispiels lassen sich die für IoT-Systeme sinnvollen Testmethoden am einfachsten erläutern. Konkret soll es in diesem Beitrag um eine medizinische Lösung gehen, die im Wesentlichen aus vier Komponenten besteht (Bild 1):

  • einem Blutsensor als Wearable-Lösung (etwa ein Blutzucker-Messgerät),
  • einem Medikations-Injektor (etwa für Insulin) ebenfalls in Wearable-Ausführung,
  • einem Smartphone
  • sowie einem Cloud-basiertes Healthcare-System.
Tests

Bild 1: Schematischer Aufbau einer Cloud-basierten Lösung zur Bestimmung des Blutzuckerspiegels. Parasoft

Der Sensor untersucht das Blut auf einen bestimmten Parameter hin (beispielsweise den Blutzuckerspiegel). Die Übermittlung der Messergebnisse erfolgt drahtlos an eine Smartphone-App, die als Middleware fungiert. Neben einfachen Analysefunktionen übermittelt die App die Informationen über den Blutzuckerspiegel an ein Healthcare-System in der Cloud, das weitere Verarbeitungen vornimmt, etwa den Vergleich der aktuellen Messwerte mit historischen Daten und die Suche nach unerwünschten Mustern mithilfe aufwendigerer Analysen. Erkennt es eine potenzielle Gefahr, sendet das System eine Warnmeldung direkt an den Anwender oder informiert medizinisches Personal.

Im konkreten Anwendungsbeispiel sind der Glukosesensor und der Injektor als MCU-basierte, in C entwickelte Systeme implementiert. Die Middleware-Applikation im Smartphone und die Back-End-Lösung in der Cloud sind dagegen in Java programmiert. Sensor, Smartphone-App und Injektor tauschen ihre Daten per Funk mithilfe eines anwendungsspezifischen Protokolls aus. Der Datenaustausch zwischen der Smartphone-App und dem Healthcare-System in der Cloud erfolgt per HTTP.

End-to-End-Tests für das IoT

End-to-End-Tests auf der System-Ebene sind häufig die erste und einzige Option. Dies liegt hauptsächlich daran, dass es auf den ersten Blick als die logischste effektivste Nutzung der Qualitätssicherungs-Ressourcen erscheint, das System komplett zusammenzusetzen und dann zu testen. Nutzt man sie jedoch als primäres Mittel zur Qualitätssicherung, führt dies oft dazu, dass Fehler erst sehr spät entdeckt werden und das Produkt deshalb nur mit Verzögerung auf den Markt kommt.

Zweifelsohne sind End-to-End-Tests sinnvoll. Sie sollten aber dazu dienen, das korrekte Zusammenpassen aller Teile sicherzustellen, und nicht zum Aufdecken von Fehlern eingesetzt werden. Das System kann die Gültigkeit einiger Anforderungen überprüfen, aber nicht alle möglichen Situationen simulieren. Ein System mit diesen Fähigkeiten zu bauen, wäre zu zeit- und kostenintensiv.

Der primäre Testfall könnte die folgenden Aktionen abdecken:

  • Aktivierung des Blutzuckersensors
  • Erstellen eines Datenpakets
  • Weiterleitung der Daten an die Cloud
  • Generierung eines Alarms durch die Cloud
  • Abwarten der Reaktion des medizinischen Personals
  • Alarmierung des Patienten
  • Änderung des Injektionsplans oder Verabreichung einer Sofort-Injektion

Später erfolgende Tests zum Verifizieren dieses Szenarios sind extrem arbeits- und kostenaufwendig. Da zudem immer mehr Organisationen zu agilen Entwicklungskonzepten übergehen, wird das angemessene Testen innerhalb der Iterationen immer schwieriger. Außerdem fallen während des Entwicklungsprozesses potenziell lange Wartezeiten bis zum Beginn des Testens an. Hier müssen die Organisationen zwischen Schnelligkeit und Qualität abwägen.

Frühzeitigere Tests ermöglichen eine effektivere Automatisierung. Softwareingenieure und Prüfer können dann aussagefähigere Daten über den Code einholen, um die Aufgaben gemäß ihrer Entwicklungs-Prozedur zu priorisieren. Dies wiederum spart Zeit und Geld über den gesamten Entwicklungszyklus.

Effektiveres Testen durch Systemunterteilung

Als Voraussetzung für die Unterteilung eines Systems in verschiedene Ebenen muss die Lösung von vornherein so angelegt werden, dass eine Segmentierung in kleinere Abschnitte mit definierten Schnittstellen möglich ist. Außerdem müssen automatisierte Testlösungen um diese kleineren Abschnitte herum implementiert werden. Insgesamt sollte der Testplan eine Kombination aus Modultests, Integrationstests und bestimmten End-to-End-Tests vorsehen. Je komplexer die Lösung ist, umso wichtiger werden die Modultests, denn mit zunehmender Komplexität der Software wird es immer schwieriger, die übergeordneten Schnittstellen zu aktivieren, die sicherstellen, dass die verschiedenen Pfade auch ausgeführt werden.

Modultests sind zeit- und ressourcenaufwendig. Sie müssen von kundigen Fachleuten geschrieben werden und sind eng an den Code gebunden. Das erfordert die fortlaufende Pflege bei Änderungen am Code. Tests auf der Funktionsebene sind weniger anfällig, machen aber das Aufdecken systemischer Probleme schwieriger. Bei Modultests dagegen ist die Ursachenfindung einfacher. Empfehlenswert ist daher ein kombiniertes Konzept – nicht nur für IoT-Umgebungen, sondern für alle Anwendungen, in denen es auf Geschwindigkeit und Qualität ankommt.

 

Lesen Sie auf der nächsten Seite über automatisierte Tests für Teilkomponenten und die Isolation von einzelnen Komponenten.

Automatisierte Tests für Teilkomponenten

Tests

Bild 2: Zum automatisierten Test des Blutzuckersensors wird ein Gerüst zum Stimulieren des Prüflings und zum Verifizieren seiner Antwort benötigt. Parasoft

Der Blutzuckersensor und der Injektor sind Beispiele für untergeordnete eingebettete Geräte. Da sie kein API und keine komplexe Benutzeroberfläche besitzen, ist ein automatisiertes Testen deutlich einfacher. Benötigt wird ein Gerüst zum Stimulieren des Prüflings und zum Verifizieren seiner Antwort (Bild 2).

In einfachen Fällen können Python oder einfache Skripte als Testlösungen dienen. Allerdings ist dieses Konzept nicht ohne weiteres skalierbar, denn mit zunehmender Zahl der Test-Szenarien wird das Hinzufügen und Verwalten der Skripte schwierig und ineffizient. Auch das Implementieren komplexerer Parameter oder Sequenzen ist mit einfachen Scripting-Tools sehr diffizil, ebenso das Validieren der Ergebnisse und die Sicherstellung konsistenter Reports. Am besten bewährt sich ein spezielles Tool, das unterschiedliche Payloads auf der Basis realistischer Test-Szenarien simulieren kann.

Als Backend der Beispiellösung dient ein Cloud-basiertes System, das regelmäßig Meldungen zu den Blutzuckerspiegeln der Patienten erhält. Getestet wird diese Komponente mithilfe simulierter Patienten-Datenpakete. Weist ein neuer Blutwert eine erhebliche Abweichung auf, hat das System über die richtige Reaktion zu entscheiden: Muss medizinisches Personal informiert werden? Soll der Patient gewarnt werden, verbunden mit Informationen über das nächstgelegene Krankenhaus, oder soll ein neuer Injektionsplan erstellt werden?

Die Testlösung führt eine Reihe von Tests mit unterschiedlichen Werten für verschiedene Patienten durch und prüft, ob das System jeweils richtig reagiert. Der Server ist allerdings nicht nur ein System, sondern setzt sich aus mehreren Systemen zusammen, die möglicherweise nicht für Tests verfügbar sind oder nicht die korrekten Testdaten zum Abarbeiten der Test-Szenarien liefern können. Deshalb sind realistische Simulationen der Funktionen des Backend-Systems notwendig. In einem agilen System kommt als weitere Schwierigkeit hinzu, dass nicht unbedingt alle Teilsysteme schon fertig sind. Das gleiche Problem stellt sich beim Testen des Sensors, der Abhängigkeiten von anderen Subsystemen aufweisen kann.

In den meisten Fällen ist es für eine hinreichende Automation um die geprüften Komponenten herum erforderlich, die Interaktionen von der realen Welt zu isolieren. Welche Strategien dafür angewendet werden müssen, hängt von der Technologie und der Art der Interaktionen ab.

Isolation von einzelnen Komponenten

Tests

Bild 3: Bei der Isolation des Sensors muss der mit „Stub/Mock“ (Blindleitung/Attrappe) bezeichnete Teil zur Emulation eines Hardware-Funktionsaufrufs installiert werden. Parasoft

Nachdem es stimuliert ist, beginnt das zu prüfende System mit der angeforderten Aktivität – in diesem Fall also mit der Ermittlung des Blutzuckerspiegels. Dazu braucht es ein zuverlässiges Eingangssignal, damit es mit einem gültigen Ergebnis antworten kann. Anstatt den Sensor in eine Flüssigkeit zu legen, die Blut mit einem bestimmten Zuckergehalt entspricht, empfiehlt sich eher das Abfangen des Funktionsaufrufs und seine Weiterleitung an einen virtuellen Sensor, der die Reaktion simuliert. Damit bleibt die Sonde selbst außen vor, während sich alle anderen Systemkomponenten automatisch testen lassen. Bild 3 skizziert dieses Konzept.

Tests

Bild 4: Für mehr Flexibilität lässt sich die Prüfausrüstung mit einem API ausrüsten. Parasoft

Der mit „Stub/Mock“ (Blindleitung/Attrappe) bezeichnete Teil muss zur Emulation eines Hardware-Funktionsaufrufs installiert werden. Die Reaktion kann dann mit fest codierten Werten oder Testdaten erfolgen, die aus einer externen Quelle gelesen werden. Ist mehr Flexibilität gefragt, lässt sich die Prüfausrüstung mit einem API ausstatten, das ein Einstellen der gewünschten Parameter ermöglicht (Bild 4).

Ausgehend davon, dass in unserem Beispiel eine einfache, in C geschriebene Funktion vorliegt, lässt sich die Testattrappe einfach durch bedingte Kompilierung einbringen. Derartige Vorkehrungen sind an verschiedenen Stellen der Quelldateien möglich, um mehrere Objektdateien durch solche zu ersetzen, die Testversionen der jeweiligen Funktionen enthalten. In komplexeren Fällen ist die Verwendung von Codeinstrumentierungs-Tools anzuraten, die den Aufwand deutlich verringern.

Tests

Bild 5: Für das automatische Testen einer Serverkomponente ist auch diese Komponente durch ein virtuelles Gegenstück zu ersetzen. Parasoft

Serverseitig sind die Herausforderungen anders, denn ein Bestandteil des geprüften Systems ist ein Dienst, der medizinische Beratung durch einen Menschen bietet. Für das automatische Testen ist auch dieser Teil durch ein virtuelles Gegenstück zu ersetzen (Bild 5).

Anforderungen und Codeüberdeckung

Wie lässt sich feststellen, ob die durchgeführten Tests ausreichen? Ist die Anwendung gut abgesichert und arbeitet sie zuverlässig? Beantworten lassen sich diese Fragen mit Tools, die die Codeüberdeckung erfassen, Maßzahlen sammeln und diese mit den Testergebnissen korrelieren. Dies dient als Basis für fundierte Entscheidungen, wann ein Produkt auf den Markt kommen kann und welche Auswirkungen sich bei Änderungen ergeben.

Sogenannte Code-Coverage-Tools werden oft im Zusammenhang mit Modultests eingesetzt. Um präzise und vollständige Informationen über die Codeüberdeckung zu erhalten, gilt es Daten aus Modul- und Funktionstests zusammenzuführen, wobei Überlappungen bei der Überdeckung zu berücksichtigen sind. Äußerst wichtig ist dies in sicherheitskritischen Anwendungen. In unserem Beispiel ist die exakte Codeüberdeckung für die Freigabe seitens der FDA erforderlich.

Einige Prüflösungen auf dem Markt können Coverage-Daten aus verschiedenen Prüfaktivitäten zu präzisen Überdeckungs-Resultaten zusammenführen. Entscheidend ist, sowohl Code-Coverage-Daten, die sich auf die Struktur des physischen Codes beziehen, als auch Angaben zu Anforderungsüberdeckung zu sammeln. Letztere beziehen sich auf die tatsächlich gestellten und die angenommenen Anforderungen.

Steht ein Code-Coverage-Tool zur Verfügung, lässt sich die Qualität der Testfälle wesentlich leichter analysieren. Schließlich hat die Praxis gezeigt, dass bestimmte Codeabschnitte selbst dann möglicherweise nicht ausgeführt werden, wenn alle Testszenarien, die scheinbar alle Anforderungen abdecken, durchgespielt wurden. Eine Analyse der nicht abgedeckten Codeabschnitte ermöglicht eine Entscheidung, ob zusätzliche Tests notwendig sind. In der Regel erfordern diese nicht abgedeckten Teile des Codes besondere Aufmerksamkeit, denn möglicherweise geht es um Code für die Fehlerbehandlung oder die Verarbeitung unerwarteter Inputs. Der einfachste Weg, die korrekte Arbeitsweise von solchem Code zu verifizieren, sind möglicherweise Modultests im Verbund mit den oben erwähnten Stub/Mock-Anordnungen, um die Softwarekomponenten von ihrem regulären Verhalten abzubringen und in selten genutzte Pfade zu zwingen.

Sehr wichtig ist es, die Code-Coverage-Resultate mit den Anforderungen zu verknüpfen, denn so lassen sich die mit bestimmten Testfällen verbundenen Risiken verstehen.

Belastungstests

Die Korrektheit eines Systems lässt sich erst dann schlüssig verifizieren, wenn auch sein Verhalten unter extremen Bedingungen (etwa bei starkem Datenverkehr) überprüft wurde. Bei IoT-Lösungen gelten nicht für alle Komponenten die gleichen Performance-Eigenschaften beziehungsweise die gleichen Anforderungen oder Service Level Agreements (SLA). Die Sensor-Ebene, die in erster Linie Daten produziert, dürfte kaum zu einem Engpass werden und ist nur in begrenztem Umfang starkem Traffic ausgesetzt. Im Gegensatz dazu muss die Service-Seite unbedingt auf ihre Fähigkeit geprüft werden, die definierte Maximallast zu bewältigen.

Hierfür kommen spezielle Leistungsprüfungs-Tools in Frage. Entscheidend ist aber, auf jeder Ebene das korrekte SLA einzurichten und zu validieren, indem man jeweils grundlegende Fragen stellt: Wie viele Sensoren sind mit dem Server verbunden und wie oft senden und empfangen sie Daten? Wie lang ist die Reaktionszeit zwischen Server und Sensor?

Bei Performance-Tests an einer einzelnen Komponente ist es ebenso wie beim automatisierten Testen unter normalen Bedingungen erforderlich, die jeweilige Komponente von den von ihr abhängigen Teilen zu isolieren. Service-Virtualization-Lösungen erlauben die Simulation von verschiedenen SLA-Szenarien (beispielweise partiellen Payloads und extremen Latenzen), um verdeckte Probleme ans Licht zu bringen und Ausnahmefälle durchzuspielen, die in einer vollständigen End-to-End-Testumgebung unmöglich berücksichtigt werden können.

Sicherheitstests

Die Sicherheit einer Lösung zu gewährleisten, gehört zu den anspruchsvollsten Aufgaben bei der Entwicklung, denn hier spielt die Erfahrung des jeweiligen Teams eine entscheidende Rolle. Es geht hier nicht allein darum, das für die Qualitätssicherung zuständige Personal über bestimmte Bedrohungen auf dem Laufenden zu halten, denn auch die Entwickler müssen über ungünstige Codemuster Bescheid wissen, die zum Entstehen von Schwachstellen führen können. Hilfreich kann die Einrichtung eines regelmäßigen Code-Review-Prozesses zwischen den Entwicklungs- und Qualitätssicherungs-Teams sein.

Sie sollten die Nutzung von statischen Analysetools für den Quellcode als Bestandteil des Entwicklungsprozesses automatisieren, um Muster zu exponieren, die Sicherheitslücken verursachen können. Eine statische Analyselösung, die gute Programmierpraktiken wie CWE oder CERT implementiert, untersucht den Code und hält nach Mustern Ausschau, aus denen ein für schädliche Zwecke verwertbarer Code hervorgehen kann.

Speziell in IoT-Umgebungen kommt es darauf an, dass das verwendete statische Analysetool alle für das System genutzten Codiertechnologien abdeckt. Unterbleibt die Analyse auch nur eines kleinen Bestandteils einer Lösung ohne sicherheitsorientiertes Scannen, kann dies böswilligen Hackern als Einfallstor zum Infizieren des gesamten Systems dienen. Darum sollte die Entwicklungsprozedur unbedingt eine statische Analyse einschließen. Dazu gehören:

  • Die Definition der zu vermeidenden Muster, um Defekte zu unterbinden, die für die Organisation riskant sein könnten.
  • Die Durchführung von Flow- oder dynamischen Analysen in einer frühen Phase, um vor den Systemtests potenzielle Schwachstellen aufzudecken.
  • Die Nutzung von Penetration-Tests zum Prüfen der APIs.

In jeder Phase sollte das Prozedere gemäß den gemeldeten Problemen verfeinert werden, um Defekte systematisch zu vermeiden. Schließlich wiegt ein Designfehler weit schwerer als die Kosten für den Einsatz einer leistungsfähigen Testlösung.

Eck-DATEN

End-to-End-Tests sind sinnvoll, sollten aber dazu dienen, das korrekte Zusammenpassen aller Teile sicherzustellen, und nicht zum Aufdecken von Fehlern eingesetzt werden. Ein Testplan sollte daher eine Kombination aus Modultests, Integrationstests, Funktionstests und bestimmten End-to-End-Tests vorsehen. Grundsätzlich gilt dabei, dass ein Designfehler weit schwerer wiegt als die Kosten für den Einsatz einer leistungsfähigen Testlösung.

Marc Brown

(Bild: Parasoft)
Chief Marketing Officer bei Parasoft

(ku)

Sie möchten gerne weiterlesen?

Unternehmen

Parasoft Corp.

Römischer Hof, Unter den Linden 10
10117 Berlin
Germany