Statische und dynamische Codeanalyse in einem kontinuierlichen Testprozess
Zunehmende Variantenvielfalt und steigende Komplexität stellen Entwickler von Embedded Software vor neue Herausforderungen. Kontinuierliche Integration gewinnt daher an Bedeutung. Durch die Kombination statischer und dynamischer Codeanalyse in einem kontinuierlichen Testprozess lässt sich effizient eine nachhaltige Qualitätssteigerung erzielen – die richtigen Analysewerkzeuge vorausgesetzt.
Anbieter zum Thema

Schon der Unternehmensgründer der Bosch Engineering GmbH hatte den Anspruch, ein hohes Maß an Qualität zu erreichen: „Es war mir immer ein unerträglicher Gedanke, es könne jemand bei der Prüfung eines meiner Erzeugnisse nachweisen, dass ich irgendwie Minderwertiges leiste. Deshalb habe ich stets versucht, nur Arbeit hinauszugeben, die jeder sachlichen Prüfung standhielt, also sozusagen vom Besten das Beste war," legte Robert Bosch 1918 in seinen Grundsätzen fest.
Heutzutage kommt noch die Einhaltung vertraglicher Anforderungen, Standards und Gesetze hinzu: „Wird durch den Fehler eines Produkts jemand getötet, sein Körper oder seine Gesundheit verletzt oder eine Sache beschädigt, so ist der Hersteller des Produkts verpflichtet, dem Geschädigten den daraus entstehenden Schaden zu ersetzen,“ heißt es in § 1 Abs. 1 des ProdhaftG, 1990, Haftung.
Umso wichtiger ist es daher, fehlerhaftes Verhalten eingebetteter Systeme auszuschließen. Diese kosten im Fall einer Haftung nicht nur Zeit und Geld, sondern können auch zu einem Imageschaden führen. Interessant ist hierbei, dass aufgrund der steigenden Komplexität und der sich stetig weiterentwickelnden Technologie immer mehr der Mensch als Fehlerquelle in den Vordergrund rückt.
Das Testparadoxon
Immer wieder müssen in Projekten die Barrieren zwischen Entwicklung und Test überwunden werden, bevor konstruktiv an einer Qualitätssteigerung gearbeitet werden kann. Wird beim Testen ein Fehler entdeckt, so zerstört dies meist das Vertrauen der Entwicklung zu Produkt und Prozess. Es gilt aber auch: Ist der Fehler behoben beziehungsweise erst gar nicht vorhanden, so liefert der Test im Idealfall den Nachweis für seine Abwesenheit und schafft Vertrauen.
Gerade im Bereich eingebetteter Systeme ist es daher umso wichtiger, dass Entwickler frühzeitig und direkt eine Rückmeldung bezüglich des aktuellen Entwicklungsstands erhalten. Zudem muss jeder Entwickler in der Lage sein, die entsprechenden statischen und dynamischen Codeanalysen selbst in seiner Entwicklungsumgebung auszuführen, um einem vermeidbaren und somit unnötigen Vertrauensbruch entgegenzuwirken.
Statische Codeanalyse
Das wohl bekannteste und hoffentlich auch meist genutzte Werkzeug für statische Codeanalysen ist das Review. Es deckt so gut wie alles ab. Doch auf Ebene des Quellcodes hat die Erfahrung gezeigt, dass bei alleiniger Nutzung des Reviews der Fokus nach einer gewissen Zeit immer mehr Richtung Trivialitäten und Formalitäten wandert und die vorhandenen funktionalen Fehler übersehen werden. Daher empfiehlt sich die Nutzung von Werkzeugen, die automatisiert und systematisch bei der Quellcodeanalyse unterstützen.
Eines dieser Werkzeuge ist der Compiler, welcher in jeder Software-Build-Kette zu finden ist. Eine Vielzahl lexikaler, syntaktischer und semantischer Analysen bringt der Compiler von Haus aus mit. Jedoch werden häufig in der Prototypenphase die dazugehörigen und wertvollen Warnungen und Fehlermeldungen per Compilerschalter deaktiviert und somit mögliche Fehlerquellen ausgeblendet. Werden diese dann erst zur Serienentwicklung wieder reaktiviert, werden viele Warnungen und Fehler sichtbar und verursachen dadurch einen erheblichen Aufwand bei der Fehlerkorrektur.
Ein weiteres Hilfsmittel stellen die Werkzeuge zur Quellcodeanalyse dar, die unter anderem den Quellcode gegen Standards (bis zu 100% Abdeckung) als auch gegen die projekteigenen Codier-Richtlinien (zwischen 80-90% Abdeckung) prüfen können. Zudem liefern diese Werkzeuge hilfreiche Metriken, mit denen Aussagen zur Komplexität und dem daraus resultierenden Aufwand zum Beispiel für Software-Unit-Tests getroffen werden können. Kombiniert man nun diese drei Methoden geschickt miteinander, so wird der eigentliche Fokus beim Quellcode-Review wieder mehr auf die Funktionalität gelenkt und zudem Zeit eingespart.
Es empfiehlt sich daher, folgende Fragen in der Review-Checkliste zu ergänzen:
- Wurden alle Compilerwarnungen und -fehlermeldungen behoben oder bewertet?
- Wurden alle Warnungen und Fehlermeldungen der Quellcodeanalyse behoben oder bewertet?
- Wurden alle Regeln der Codier-Richtlinien, die manuell geprüft werden müssen, erfüllt oder bewertet?
Dynamische Codeanalyse
Ein Großteil der dynamischen Codeanalyse nimmt die Simulation und Stimulation der zu testenden Funktionalität ein. Kombiniert man diese noch mit einem Werkzeug zum Messen der Codeabdeckung oder ist dieses Werkzeug sogar schon integriert, lassen sich neben der Überprüfung zur Funktionalität auch Aussagen zu fehlenden Testfällen oder nicht erreichbaren bzw. totem Quellcode machen. Viele Sicherheitsstandards fordern genau aus diesen Gründen die Einhaltung der Codeabdeckungswerte entsprechend der für das Produkt zutreffenden Sicherheitsstufe.
Kontinuierliche Integration
Ziel des kontinuierlichen Integrationsprozesses ist, frühzeitig Integrationsprobleme zu detektieren. Dabei wird die Software stückweise über mehre Teilarchive sogenannte Streams eines Versionsverwaltungssystems zusammengebaut. Hierzu ist allerdings eine vollautomatisierte Software-Build-Kette erforderlich (z.B. Jenkins-Server), welche die entsprechenden Compilerwarnungen und -fehlermeldungen direkt an die Entwicklung weiterleiten kann.
Schnelle Softwareänderungen sind mit diesem System auch kein Problem, solange die Implementierung modular und verständlich gehalten wird. Des Weiteren verhindert dieser Prozess Last-Minute Integrationen und stellt somit sicher, dass für Test, Demonstrationen und Ablieferungen immer ein lauffähiger Software-Build zur Verfügung steht.
Kontinuierliches Testen
Der kontinuierliche Testprozess ist eine Erweiterung des kontinuierlichen Integrationsprozesses mit zusätzlichen dynamischen Testschritten, um neben den Compilerwarnungen und -fehlermeldungen auch funktionale Fehler zu detektieren. Dies hat den Nebeneffekt, dass die Entwicklung dazu angehalten wird kontinuierlich sauberen und testbareren Quellcode zu schreiben.
Zudem fördert dieser Prozess die testgetriebene Anforderungs- und Architekturentwicklung, vorausgesetzt die verwendeten Testsysteme können auch hier voll automatisiert betrieben werden. Wird nun noch die zuvor erwähnte Methodenkombination für statische Codeanalyse berücksichtigt (Compilerwarnungen und -fehlermeldungen + Quellcode-Review), so können zusätzlich Regelverletzungen und Schnittstellenfehler mit im kontinuierlichen Testprozess überprüft werden.
Lokale Entwicklungsumgebung
Um dem Testparadoxon entgegenzuwirken ist es zwingend erforderlich, dass Entwicklung und Test in derselben lokalen Entwicklungsumgebung arbeiten können. Hier bietet sich zum Beispiel eine Entwicklungsumgebung wie Eclipse an. Diese erlaubt es unterschiedliche Plug-Ins für statische und dynamische Codeanalyse (z.B. QAC/QAC++ und Cantata) und viele weitere zu integrieren.
Auf der einen Seite kann so die Entwicklung Codeänderungen und deren Auswirkung eigenständig prüfen, bevor diese in den SW-Function Stream eincheckt werden und somit den formellen Testprozess starten. Auf der anderen Seite, bietet es dem Test eine Möglichkeit, entsprechende Lösungsvorschläge zu definieren, in dem die fehlerhaften Stellen temporär modifiziert werden.
Durch Integration dieser lokalen Entwicklungsumgebung in den kontinuierlichen Testprozess können nun auch vorhandene Testskripte auf Entwicklungsseite genutzt werden, was zudem einen schnelle Fehlerbehebung ermöglicht. Mit dem Einsatz geeigneter statischer und dynamischer Codeanalyse Werkzeuge können somit zu jedem Zeitpunkt der Entwicklung im kontinuierlichen Testprozess Aussagen bezüglich der aktuellen Softwarequalität getroffen werden. Zudem lassen sich unterschiedliche Metriken für die Dokumentation und Steuerung des Projektes generieren.
Codeabdeckung
Aus der Praxis wissen wir, dass je größer der betrachtete Systemfokus wird es auch immer schwerer wird eine vollständige Codeabdeckung zu erreichen. Auch startet der Test oft viel zu spät in der Projektlaufzeit. Hinzu kommt noch, dass meistens die Anforderungen auf den detaillierteren Entwicklungsebenen nur spärlich oder sogar überhaupt nicht existieren. Um der Lage dennoch Herr zu werden, vergrößert man die Testteams überproportional.
Ist die Umsetzung des Entwicklungsplans nicht eingehalten, reicht es meistens zeitlich nicht mehr die benötigten Detailanforderungen nachzuziehen. So werden kurzerhand auf Basis des Quellcodes die Testfälle abgeleitet. Problem hierbei ist, dass enorme Kosten verursacht werden ohne effektiven Mehrwert für die Qualität des Produktes (reine Artefaktbefriedigung).
Daher empfiehlt es sich den Fokus auf die nächsthöhere Entwicklungsebene zu legen, in der Anforderungen in einem adäquaten und testbarem Zustand existieren. Größtenteils ist dies auf Architekturebene noch der Fall. Hier können mit vertretbarem Aufwand relative gute Werte bezüglich Codeabdeckung erzielt werden.
Autotestfallgeneratoren auf Software-Unit-Ebene
Auf Basis der zuvor genannten Erfahrungswerte lässt sich nun Folgendes ableiten: Wenn wir wissen, dass im Bereich der Software/Software-Integration mit vertretbarem Testaufwand sehr gute Codeabdeckungswerte erreicht werden können und die Wahrscheinlichkeit höher ist, noch größere gravierende Fehler in der Software zu entdecken (Signalkettenfehler), die der Entwicklung weiterhelfen, warum werden dann nicht auf Software-Unit-Ebene Autotestfallgeneratoren genutzt?
Mit diesem Ansatz kann zum einen die fehlende Codeabdeckung nachgewiesen werden und zum anderen eine zusätzliche funktionale Überprüfung zum Quellcode-Review erfolgen. Ergänzt man hierzu die Review-Checkliste mit folgenden Fragen, ist diese Arbeitsweise legitim:
- Decken die autogenerierten Testfälle die zu testende Funktionalität sinnvoll und vollständig ab?
- Sind die Ergebnisse der autogenerierten Testfälle fehlerfrei?
- Wurde die geforderte Codeabdeckung erreicht?
Können diese Fragen nicht vollständig beantwortet werden, so besteht immer noch die Möglichkeit, manuell fehlende Testfälle zu ergänzen. Unabhängig davon stellt die Funktionalität zur automatischen Generierung von Testfällen zumindest entwicklungsseitig einen großen Nutzen dar, bei der initialen Implementierung eine Art stichprobenartige Funktionsüberprüfung durchzuführen.
:quality(80)/images.vogel.de/vogelonline/bdb/1588600/1588651/original.jpg)
Behaviour Driven Testing und automatische Unit-Test-Generierung
:quality(80)/images.vogel.de/vogelonline/bdb/1518400/1518454/original.jpg)
‚Shift Left‘: Wie man Performance-Tests in der Software-Entwicklung vorverlegt
:quality(80)/images.vogel.de/vogelonline/bdb/1323400/1323442/original.jpg)
Entwicklung vom agilen zum Continuous Testing
Dieser Beitrag stammt aus dem Tagungsband des Embedded Software Engineering Kongress 2016
* Dipl.-Ing. (FH) Matthias Schmidt, Projektleiter bei der Bosch Engineering GmbH, studierte an der Hochschule Karlsruhe Energie- und Automatisierungstechnik. Bei Bosch etablierte er innerhalb videobasierter Assistenzsysteme sein Spezialgebiet SW-Unit Test. Er ist Software-Produkt-Manager für Cantata und arbeitet aktuell als Testmanager in einem Projekt der Bosch General Aviation Technology GmbH.
(ID:44853795)