Unit Test

Ein Unit Test bezeichnet einen automatisierten Test, der eine klar abgegrenzte Softwareeinheit isoliert überprüft. Eine solche Unit ist typischerweise ein einzelnes Modul, eine Funktion oder eine Klasse. Ziel ist es, das Verhalten dieser Einheit unabhängig vom restlichen System zu verifizieren, indem definierte Eingaben mit erwarteten Ergebnissen verglichen werden. Unit Tests sind bewusst klein, schnell ausführbar und so gestaltet, dass sie Fehler eindeutig einer konkreten Codeeinheit zuordnen. Unit Tests sind Grundbestandteil moderner Embedded Software Entwicklung.

Unit Tests bei Embedded Systems

Im Kontext von Embedded Systems erhält der Begriff eine spezifische Ausprägung. Ein Unit Test Embedded Systems beschreibt die isolierte Prüfung von Firmware-Komponenten, die in der Regel nicht auf der Zielhardware, sondern auf einem Entwicklungsrechner oder Build-Server ausgeführt wird (off-target). Diese Entkopplung ist notwendig, da Embedded Software häufig direkt mit Hardware interagiert, die während der Entwicklung nicht immer verfügbar ist oder deren Nutzung Tests verlangsamen würde.

Was ist eine „Unit“

Eine Unit ist eine in sich geschlossene Implementierungseinheit, typischerweise ein einzelnes Modul oder eine Klasse, häufig repräsentiert durch eine .c– oder .cpp-Datei. Diese Einheit wird in eine Testumgebung eingebettet, die ihre isolierte Ausführung ermöglicht.

Struktur von Unit Test bei Embedded Systems
Struktur von Unit Test bei Embedded Systems

Ein Test Case beschreibt einen konkreten Testfall, der entweder besteht oder fehlschlägt. Er setzt sich aus einer Initialisierung (Setup) und einer oder mehreren Überprüfungen zusammen. Diese Überprüfungen werden über Test Assertions formuliert, also Aussagen über erwartete Zustände oder Rückgabewerte.

Mehrere Test Cases werden zu einer Test Suite zusammengefasst, die als ausführbares Programm läuft und die Gesamtheit der Tests repräsentiert.

Die getestete Einheit selbst wird als Code Under Test (CUT) bezeichnet. Ihre direkten Abhängigkeiten heißen Depended on Components (DOC), während indirekte Abhängigkeiten als Transitively Depended on Components (TDOC) bezeichnet werden. Diese Unterscheidung ist im Embedded-Bereich relevant, da Hardwarezugriffe häufig über mehrere Abstraktionsebenen hinweg erfolgen.

Unit Tests ohne Framework

Neben etablierten Frameworks existiert im Embedded-Bereich ein pragmatischer Ansatz: framework-lose Unit Tests, häufig realisiert als einfache .c-Dateien mit direkten Assertions.

Ein typisches Beispiel ist die Verwendung von assert() aus der Standardbibliothek:

int main(void)
{
    CanConfig config = {0};
    CanUnit   unit   = {0};

    bool result = CAN_Init(&config, &unit, 500000U, 2000000U);

    assert(result == true);
    assert(config.ctrl_baudrate == 500000U);
    assert(config.data_baudrate == 2000000U);
    assert(config.fd_enabled == true);

    assert(unit.initialized == true);
    assert(unit.active_ctrl_baudrate == 500000U);
    assert(unit.active_data_baudrate == 2000000U);
    assert(unit.fd_active == true);

    return 0;
}

Dieser Ansatz verzichtet vollständig auf Testframeworks, Test Runner oder Mocking-Infrastruktur. Die Tests werden als eigenständige Programme kompiliert und ausgeführt.

Zudem werden Assertions innerhalb von Funktionen verwendet, die der Absicherung von Vorbedingungen dienen. Dies sind Defensivprüfungen im Code. Sie prüfen zum Beispiel, ob übergebene Parameter gültig sind, und stoppen die Ausführung frühzeitig bei Verstößen. Diese Prüfungen sind Bestandteil der Implementierung und adressieren die korrekte Nutzung der Schnittstelle durch den Aufrufer. Beides ist nicht scharf voneinander zu trennen: Asserts können Defensivprüfungen oder Unit Tests sein.

Im Embedded-Kontext ist dieser Ansatz insbesondere dort relevant, wo klassische Unit-Test-Infrastrukturen schwer einzusetzen sind – beispielsweise bei direktem Zugriff auf Hardware-Register oder bei stark hardwaregekoppelter Logik. Hier kann ein einfacher, kontrollierter Testaufbau helfen, Verhalten zumindest punktuell zu verifizieren.

Unit Tests in der funktionalen Sicherheit

Im Kontext der funktionalen Sicherheit sind Unit Tests nicht nur ein Entwicklungswerkzeug, sondern Bestandteil eines strukturierten Nachweiskonzepts. Sie dienen dazu, die korrekte Implementierung von Softwareeinheiten gegen ihre spezifizierten Anforderungen zu verifizieren und sind typischerweise im linken Ast des V-Modells verankert.

Im V-Modell erfolgt die Entwicklung entlang von Spezifikationsebenen (System, Softwarearchitektur, Module), während die Verifikation spiegelbildlich auf der rechten Seite stattfindet. Unit Tests sind dabei der untersten Verifikationsebene zugeordnet und prüfen, ob einzelne Softwareeinheiten ihre spezifizierten Funktionen korrekt erfüllen. Sie stehen in direkter Beziehung zu Low-Level Requirements.

Ein zentrales Kriterium in diesem Umfeld ist die Code Coverage, also die Abdeckung des Codes durch Tests. Diese dient nicht als Selbstzweck, sondern als Nachweis dafür, dass relevante Teile der Implementierung tatsächlich ausgeführt und überprüft wurden.

Wichtige Metriken sind dabei:

  • Statement Coverage beschreibt, welcher Anteil der Anweisungen im Code mindestens einmal ausgeführt wurde. Sie gibt eine grundlegende Aussage darüber, ob der Code überhaupt erreicht wurde.
  • Branch Coverage geht einen Schritt weiter und betrachtet Entscheidungsstrukturen. Sie misst, ob alle möglichen Verzweigungen, beispielsweise in if– oder switch-Konstrukten, durchlaufen wurden.

Unit Test Tools

Im Embedded-Umfeld unterscheidet man zwischen zertifizierten (qualifizierbaren) Tools für regulierte Branchen und freien Frameworks für die Entwicklung.

Zertifizierte / qualifizierbare Tools (DO-178C, ISO 26262)

Diese Werkzeuge sind auf funktionale Sicherheit ausgelegt und unterstützen Anforderungen wie Coverage, Traceability und Audit-Nachweise.

  • TESSY (Razorcat)
    Automatisiert den gesamten Unit-Test-Prozess inklusive Testdesign, Ausführung und Reporting sowie Coverage und Traceability .
    Zertifiziert für Standards wie ISO 26262 und IEC 61508 .
  • QA Systems Cantata
    Unit- und Integrationstest-Tool für C/C++ mit Fokus auf automatisierte Testausführung, Reporting und Integration in Embedded-Toolchains .
  • Parasoft C/C++test
    Kombiniert Unit Testing, statische Analyse, Code Coverage und Requirements Traceability. TÜV-zertifiziert für sicherheitskritische Anwendungen und unterstützt Standards wie ISO 26262 und DO-178C .
  • VectorCAST (Overview)
    Industrietool für automatisiertes Unit Testing und Coverage im sicherheitskritischen Umfeld, häufig im Avionik- und Automotive-Bereich eingesetzt.

Freie Unit-Test Frameworks

Diese Tools sind nicht zertifiziert, aber technisch weit verbreitet und Grundlage vieler Embedded-Teststrategien.

  • GoogleTest
    Standard im C++-Umfeld mit guter Integration und Mocking-Unterstützung. Für sicherheitskritische Projekte sind zusätzliche Tools für Coverage und Traceability notwendig .
  • CppUTest
    Leichtgewichtiges Framework speziell für Embedded Systems, mit Fokus auf kleine Footprints und einfache Integration.
  • Unity / Ceedling
    Weit verbreitet im C-Umfeld, insbesondere durch automatische Mock-Generierung (CMock) und einfache Toolchain-Integration.

Zusammenfassung

Die Bedeutung von Unit Tests ist im Embedded-Bereich besonders hoch, da Systeme oft unter restriktiven Bedingungen betrieben werden, lange Lebenszyklen haben und im Fehlerfall schwer zugänglich sind. Unit Tests ermöglichen es, Fehler früh im Entwicklungsprozess zu erkennen und die Wartbarkeit der Software über die gesamte Lebensdauer hinweg sicherzustellen. Sie sind zudem eine Grundlage für moderne Entwicklungsansätze wie Test-Driven Development, bei dem Tests aktiv den Implementierungsprozess steuern.

Insgesamt stellen Unit Tests – insbesondere im Embedded-Kontext – ein zentrales Werkzeug dar, um Software systematisch, reproduzierbar und unabhängig von Hardwarezuständen zu verifizieren.

Zurück zum Glossar