Eine scheinbar harmlose Frage – aber mit vielen verschiedenen Antworten. Bei vielen Projekten ist das Testen beendet, wenn keine Zeit mehr bleibt. Offiziell ist es jedoch beendet, wenn die Funktion der Anwendung nachgewiesen ist. Eine Antwort kann also lauten »wenn alle Anforderungen nachweislich erfüllt sind«. Natürlich ist diese Antwort bei manchen Anwendungen nicht ausreichend, was ist mit Leistungs-, Sicherheits- und anderen nicht-funktionalen Anforderungen?
Bei sicherheitskritischen Systemen muss nicht nur die Funktion, Sicherheit und Unbedenklichkeit nachgewiesen werden, sondern auch, dass es im Code kein Problem gibt, das von den Tests unberührt geblieben ist. Die Durchführung von Tests hängt von vielen Faktoren ab, aber in jedem Fall ist die Tool-Automatisierung der Schlüssel zur Erfassung der verschiedenen Metriken, die den Projektstatus anzeigen, egal ob es sich um Code- oder Anforderungsabdeckung handelt.
Die Analyse der Codeabdeckung kommt oft in Verbindung mit der Ausführung von Einheitstests zum Einsatz. Der Code ist instrumentiert, und während der Ausführung der Tests werden Daten über die ausgeführten Teile des Codes erfasst. Zudem können einige manuelle Funktionstests der Anwendung notwendig sein, um Code zu testen, der sich nur schwer mit Unit-Tests ausführen lässt, z.B. wenn Integrations- oder höhere Systemtests erforderlich sind. Um genaue und vollständige Informationen über die Codeabdeckung zu erhalten, müssen daher die Daten sowohl von Unit- als auch Funktionstests zusammengeführt werden, wobei Überschneidungen bei der Abdeckung zu berücksichtigen sind. Das ist sehr wichtig, wenn es sich bei dem System um eine sicherheitskritische Anwendung handelt, die eine präzise Abdeckung für die Produktzertifizierung oder -qualifizierung voraussetzt.
Mit einem Tool zur Codeabdeckung ist es viel einfacher, die Qualität der Testfälle zu analysieren. Die Erfahrung zeigt, dass selbst nach dem Ausführen aller Testszenarien, die alle Anforderungen abzudecken scheinen, einige Teile des Codes möglicherweise nicht ausgeführt wurden. Die Untersuchung der nicht abgedeckten Codeabschnitte ermöglicht es, zu entscheiden, ob es zusätzliche Tests braucht.
Für gewöhnlich verlangen diese unentdeckten Codeabschnitte besondere Aufmerksamkeit - schließlich könnte es sich um Fehlerbehandlungscode oder Code zur Verarbeitung unerwarteter Eingaben handeln. Der einfachste Weg zum Check, ob ein solcher Code korrekt funktioniert, ist das Anwenden von Unit-Tests zusammen mit Stubbing-/Mocking-Frameworks, um das Standardverhalten von Softwarekomponenten außer Kraft zu setzen und die Codeausführung zu zwingen, seltene Pfade zu durchlaufen.
Bild 4 zeigt verschiedene Abdeckungsmetriken, die zum Messen der Testabdeckung verwendet werden können, jeweils mit unterschiedlichem Schwierigkeitsgrad, der von den grundlegendsten Metriken (Zeile und Anweisung) bis zu umfassenderen Metriken wie der modifizierten Zustands-/Entscheidungsabdeckung (MC/DC) reicht. Welche davon zu verwenden ist, wird in der Regel durch das mit dem zu testenden Code verbundene Risiko bestimmt. Beispielsweise definiert in der ISO-Norm 26262 der Automotive Safety Integrity Level (ASIL), welche Abdeckungsarten genutzt werden sollten: Level A kann nur »Statement« verwenden, Level D dagegen verlangt »Branch« und »MC/DC«.
Ein Beispiel für einen Code-Abdeckungsbericht direkt in der IDE von C/C++test zeigt Bild 5, mit den während der Testausführung abgedeckten und nicht abgedeckten Zeilen.
Das Beispiel in Bild 6 zeigt eine bestimmte Bedingung, die nicht im Rahmen von MC/DC abgedeckt ist.
Trotz erheblicher Investitionen in Unit-Tests wird es weiterhin Fehler geben, insbesondere Sicherheitslücken, die es durch die Unit-Tests und in spätere Entwicklungsstadien schaffen. Im schlimmsten Fall gelangen sie in den Produktionscode, wo sie teuer zu beheben sind und weitere Kosten verursachen, z.B. für eine erneute Zertifizierung oder tatsächliche Sicherheitsvorfälle. Mit der statischen Analyse und der Laufzeitanalyse gibt es zwei wichtige Techniken mit niedrigen Kosten und geringem Implementierungsaufwand. Bei der statischen Analyse wird der Quellcode auf Abweichungen von Best Practices untersucht, die zu Fehlern, Sicherheitslücken und Zuverlässigkeitsproblemen in Produktionsumgebungen führen können. Die Laufzeitanalyse hingegen ist die Instrumentierung und Überwachung der Laufzeitausführung von Anwendungen, um »echte« Fehler zur Laufzeit zu entdecken.
Die statische Analyse in C/C++test verifiziert die Qualität von C- und C++-Code und prüft das Einhalten von Sicherheits-, Funktionssicherheits- und Industriestandards – z.B. CERT, MISRA C oder JSF AV C++ – durch den Einsatz eines breiten Spektrums von Checkern und eines umfassenden Satzes an Techniken wie musterbasierte Analyse, Daten-/ Kontrollflussanalyse und Software-Metriken. Sie erfüllt folgende Funktionen;
Die Laufzeitanalyse findet Laufzeitdefekte, Stabilitätsprobleme und Sicherheitsschwachstellen wie Speicherlecks, Nullzeiger, nicht initialisierter Speicher und Pufferüberläufe, indem sie die ausgeführte Anwendung sowohl während der Ausführung von Unit-Tests als auch während der manuellen Ausführung der Anwendung überwacht. Sie beinhaltet:
Moderne DevOps- und Continuous-Everything-Initiativen setzen die Fähigkeit zur sofortigen und kontinuierlichen Bewertung der mit einem Release-Kandidaten verbundenen Geschäftsrisiken voraus. Kontinuierliches Testen hilft den Entwicklungsteams, die geschäftlichen Erwartungen zu erfüllen, und den Managern bei fundierten Kompromissentscheidungen, um den unternehmerischen Wert eines Release-Kandidaten zu optimieren. Einen Großteil dieser Aufgabe erfüllt die kontinuierliche Integration (CI).
Um moderne agile Methoden wie DevOps zu unterstützen, bietet es sich an, dass Teams Testmethoden wie Unit Testing, statische Analyse und strukturelle Codeabdeckung in ihr CI/CD-System integrieren. Dadurch können sie auf die automatisierten Testergebnisse in der IDE, mit HTML/XML-Berichten oder zusammengefasst in einem zentralen Dashboard zugreifen, z.B. im Tool DTP von Parasoft, für weitere Nachbearbeitungen, Berichte und Auswertungen.
Wie bei Unit-Tests können Entwickler statische Analysen in der IDE oder über die Befehlszeilenschnittstelle für Automatisierungs- und kontinuierliche Integrationsszenarien ausführen. Sie erhalten sofortigen Zugriff auf die Analyse-Ergebnisse in der IDE, mit HTML/XML-Berichten oder aggregiert in einem zentralen Dashboard in DTP für die weitere Nachbearbeitung, Reporting und Analyse.