CommunitySchreiben & Editierengithub.com

Embedded Unit Testing (Specifikationsbasiert)

Erzeugt eine Unit-Test-Suite für eingebettete Firmware-.c/.h-Dateien, basierend auf Maurício Aniches spezifikationsbasiertem Testen (Partitionen → Grenzen → Fälle → Abdeckung). Erkennt die Umgebung: Zephyr verwendet twister + ztest + FFF auf native_sim; ESP-IDF verwendet natives Unity in einem Host-Build + gcovr; anderes folgt dem bestehenden Test-Framework (Unity, CMock, Ceedling, GoogleTest…) oder stoppt und fragt, wenn keines vorhanden ist.

Was ist Embedded Unit Testing (Specifikationsbasiert)?

Diese KI-Agenten-Fähigkeit generiert automatisch eine vollständige Unit-Test-Suite für eine gegebene Menge von .c/.h-Dateien in eingebetteten Firmware-Projekten. Sie folgt den sieben Schritten des spezifikationsbasierten Testens aus Maurício Aniches Buch 'Effective Software Testing', wobei der Schwerpunkt auf der sorgfältigen Partitionierung der Funktionsdomänen liegt (dem wichtigsten Schritt), dem Ableiten von Grenzwerten, dem Entwerfen einer minimalen, aber effektiven Testsuite und deren Automatisierung im richtigen Framework des Projekts. Die Fähigkeit erkennt automatisch die Entwicklungsumgebung: Zephyr-Projekte (mit twister + ztest + FFF auf native_sim), ESP-IDF-Projekte (mit nativem Unity in einem Host-Build + gcovr) und andere Projekte (die dem vorhandenen Unit-Test-Framework wie Unity, CMock, Ceedling oder GoogleTest folgen). Sie erstellt detaillierte nummerierte Kataloge von Partitionen und Grenzen, die als Kommentare in der Testsuite eingebettet werden, um Rückverfolgbarkeit zu gewährleisten. Die Fähigkeit führt die Tests aus, misst die Codeabdeckung (Standardziel: 95 % Funktionen, 95 % Zeilen, 90 % Zweige) und iteriert, bis das Ziel erreicht ist, wobei sie Abweichungen zwischen Code und Dokumentation meldet.

Funktioniert mit✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/PedroGilo12/claude-skills/tree/HEAD/plugins/firmware-testing/skills/embedded-unit-testing

Installed? Explore more Schreiben & Editieren skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Embedded Unit Testing (Spezifikationsbasiert)

Erzeugt eine Unit-Test-Suite für eine Reihe von .c/.h-Dateien, die der Benutzer übergibt, unter Verwendung der 7 Schritte des spezifikationsbasierten Testens von Maurício Aniche (Effective Software Testing, Kap. 2). Die Besonderheit dieser Fähigkeit: gute Partitionierung der Domäne jeder Funktion (der wertvollste Schritt), Ableiten von Grenzen, Entwerfen der kleinsten Suite, die dennoch Fehler findet, Automatisierung im richtigen Framework des Projekts und Iteration, bis die Abdeckung das Ziel erreicht.

Lies references/specification-based-testing.md vor Phase 3 – es ist die Kalibrierung dafür, wie man über Partitionen und Grenzen denkt, und die Pragmatik, die eine aufgeblähte oder spröde Suite vermeidet.

Scope Invarianten (zuerst lesen)

  • Ausgabe auf Englisch. Alles, was diese Fähigkeit produziert, ist auf Englisch, selbst wenn der Benutzer in einer anderen Sprache schreibt: Testcode, Testnamen, Kommentare, Katalog, Covers:-Annotationen, Skip/Ignore-Nachrichten, die Pfaddeklaration und die abschließende Zusammenfassung.
  • Nur Unit-Tests. Erzeuge niemals Integrationstests, E2E-Tests, Systemtests, HIL- oder Robot-Framework-Tests. Die Granularität ist die isolierte Funktion / das isolierte Modul, mit gefakten/gemockten Abhängigkeiten.
  • Nur die angegebenen Dateien. Die Suite ist für die .c/.h-Menge, die der Benutzer übergeben hat. Scanne nicht das Projekt nach anderen Zielen und teste nicht eigenständig benachbarte Module.
  • Ändere niemals die Zieldateien – nicht vorübergehend, nicht „um zu testen und rückgängig zu machen“. Die Suite kompiliert die originalen .c-Dateien, ohne Kopie im Testverzeichnis. Um etwas Schwieriges zu erreichen, verwende die öffentliche API, Fakes der Abhängigkeiten oder ein separates Test-Binary.
  • Autonom. Generiere, führe aus und berichte, ohne um Bestätigung zur Ausführung zu bitten. Es gibt zwei Ausnahmen, bei denen du anhältst und fragst: (a) Pfad C (weder Zephyr noch ESP-IDF), wenn du kein Unit-Test-Framework identifizieren kannst (Phase 0); (b) alle 2 Coverage-Iterationen, ohne das Ziel zu erreichen (Phase 8).
  • Kein von dir geschriebener Bericht. Du schreibst keinen Ergebnisbericht. Führe die Tools aus und weise auf die Artefakte hin, die sie erzeugen (Coverage-HTML, Runner-Ausgabe). Das „Ergebnis“ ist das, was das Tool ausgibt, nicht dein Text.
  • Coverage-Ziel: Funktion ≥ 95 %, Zeile ≥ 95 %, Zweig ≥ 90 % (95/95/90) standardmäßig. Sobald das Ziel erreicht ist, stoppe die Iteration und liste die verbleibenden nicht abgedeckten Zeilen/Zweige auf. Gehe auf 100/100/100 nur, wenn der Benutzer danach fragt. Unter dem Ziel nur, wenn das Fehlende nachweislich unerreichbar ist (Log/Assert-Rauschen, Testbarkeitseinschränkung des Moduls), wobei jede verbleibende Zeile/jeder verbleibende Zweig begründet wird.

Der Ablauf (Aniches 7 Schritte → 9 Phasen)

Die 7 Schritte sind keine starre Pipeline – sie sind iterativ (Phase 3 enthüllt oft etwas, das dich zurück zu Phase 1 schickt). Die folgenden Phasen sind das Rückgrat.

PhaseAniche SchrittWas du tust
0—Umgebung erkennen → Pfad A (Zephyr), B (ESP-IDF) oder C (andere)
11 VerstehenAnforderung, Eingaben, Ausgaben jeder gegebenen Funktion
22 Code erkundenAbhängigkeiten kartieren, um mit Fakes/Mocks zu isolieren
33 PartitionenNummerierter Katalog von Partitionen (der wichtigste Schritt)
44 GrenzenNummerierter Katalog von Grenzen (On-/Off-Point)
55 Fälle entwerfenPartitionen/Grenzen kombinieren, Kombinationen reduzieren
66 AutomatisierenSuite im Framework des Pfades schreiben, mit Rückverfolgbarkeit
7—Tools ausführen: Ausführung + Coverage
87 ErweiternÜber Lücken iterieren (Coverage + Katalogeinträge ohne Test)

Phase 0 — Umgebungserkennung

Identifiziere die Umgebung des Projekts, das die angegebenen Dateien enthält, in dieser Reihenfolge, und wähle den Pfad. Robot Framework, HIL, Integrationstests mit pytest sind KEINE Unit-Test-Frameworks – sie zählen nie in irgendeinem Pfad.

  1. Zephyr? → Pfad A. Signale: west.yml/west.yaml; CMakeLists.txt mit find_package(Zephyr ...); prj.conf; testcase.yaml/sample.yaml; tests/; ztest/ZTEST_SUITE. Tooling: twister + ztest + FFF + Coverage. Lies references/zephyr-path.md.

  2. ESP-IDF? → Pfad B. Signale: CMakeLists.txt mit idf_component_register(; sdkconfig/sdkconfig.defaults; Verwendung von $ENV{IDF_PATH}/idf.py; eine main/-Komponente. Tooling: ESP-IDFs natives Unity (Host-Build, linux-Ziel) + gcov/gcovr. Verwende Unity direkt – halte nicht an, um zu fragen. Ausnahme: wenn das Projekt bereits konsistent ein anderes Unit-Test-Framework verwendet (Ceedling, GoogleTest…), folge diesem. Lies references/espidf-path.md.

  3. Andere (weder Zephyr noch ESP-IDF) → Pfad C. Untersuche die vorhandene Unit-Test-Struktur (Unity, CMock, Ceedling, GoogleTest, benutzerdefiniert) und folge dem Muster des Projekts getreu. Wenn kein Unit-Test-Framework gefunden wird, HALTE AN und frage den Benutzer, welches verwendet werden soll – nimm niemals einen stillen Standard in Pfad C an. Lies references/non-zephyr-path.md.

Gib im Antworttext an (nicht nur in deiner Argumentation), bevor du fortfährst, den gewählten Pfad und die konkreten Signale, die ihn rechtfertigen, mit Datei und was du gefunden hast – z. B. „Pfad A (Zephyr): zephyr/west.yml, find_package(Zephyr) in tests/.../CMakeLists.txt, ZTEST_SUITE-Suites in tests/lib/.“


Phase 1 — Anforderung, Eingaben und Ausgaben verstehen

Für jede Funktion, die die Suite abdecken wird, extrahiere durch Lesen des .c/.h:

  • die Geschäftsregel: was sie tut und was sie nicht tun darf (Ecken eingeschlossen);
  • die Eingaben: Typen, Bereich/Domäne, Einschränkungen zwischen Variablen;
  • die Ausgaben: wie Eingaben zum Ergebnis werden (und beobachtbare Seiteneffekte: Ausgabe geschrieben, Fehler zurückgegeben, Bewegung ausgegeben/nicht ausgegeben).

Ein Teil davon ist explizit; ein Teil sind implizite Domänenregeln (Sättigung, Überlauf, Einheiten, Vorzeichen, Spiel), die du ableitest. Diese impliziten Regeln sind es, wo sich Fehler verstecken.

Phase 2 — Den Code erkunden

Lese das gesamte .c/.h und erstelle die Karte der zu isolierenden Abhängigkeiten: HAL, Treiber, Kernel/RTOS, I/O, Zeit, Aufrufe in andere Module. Jede Abhängigkeit wird in Phase 6 zu einem Fake/Mock. Identifiziere auch, wie die eigentliche Logik erreicht wird (direkte öffentliche API oder static-Funktionen über Handle/vtable – rufe sie über die echte API auf, erfinde niemals eine Signatur).

Phase 3 — Identifiziere die Partitionen (der wichtigste Schritt)

Wende die 3 Linsen aus references/specification-based-testing.md auf jede Funktion an: jede Eingabe isoliert, Eingaben in Kombination und Ausgabeklassen. Berücksichtige NULL/leer und die impliziten Domänenregeln. Wenn die Funktion einen Zustand hat, partitioniere auch nach dem Zustand des Moduls/Kontexts.

Erstelle pro Funktion einen benannten, nummerierten Katalog mit stabilen IDs innerhalb der Suite:

  • Partitionen: <func>/P1, P2, …
  • Grenzen (werden in Phase 4 ausgefüllt): <func>/B1, B2, …

Jeder Eintrag erhält eine kurze Beschreibung. Dieser Katalog ist der Rückverfolgbarkeitsvertrag der Suite (materialisiert in Phase 6) und die Quelle der Wahrheit für Lücken (Phase 8). Eine gut gemachte Phase 3 sollte beim ersten Coverage-Durchlauf fast alles abdecken.

Phase 4 — Analysiere die Grenzen

Für jede Grenze zwischen benachbarten Partitionen füge zum Katalog den On-Point (den Wert in der Bedingung) und den Off-Point (den nächsten Nachbarn in der gegenüberliegenden Partition) hinzu; Gleichheiten ergeben einen On- und zwei Off-Points. Lasse eine Grenze weg, die bereits durch einen anderen Fall abgedeckt ist – dupliziere nicht. „Partition oder Grenze? Es spielt keine Rolle“: was zählt, ist, dass der Fall existiert.

Phase 5 — Entwerfe die Testfälle

Kombiniere die Partitionen über Eingabekategorien hinweg, aber reduziere die kombinatorische Explosion: teste das Exceptionelle (NULL/leer) einmal und kombiniere es nicht mit allem; verwende die Domäne, um zu kürzen. Verwende eine einzelne Starteingabe, variiere nur das Minimum pro Fall (erleichtert das Debugging). Bei Zweifeln die einfachste Eingabe. Für Eingaben, die nicht am Verhalten teilnehmen, vernünftige/realistische Werte. Wenn ein Fall das gleiche Grundgerüst wie mehrere andere hat, plane ihn als parametrisiert. Liste die Fälle auf und für jeden die Katalog-IDs, die er übt.

Phase 6 — Automatisieren (mit obligatorischer Rückverfolgbarkeit)

Schreibe die Suite im Framework des erkannten Pfades (Syntax, Isolation und Gerüst Details in references/zephyr-path.md, references/espidf-path.md oder references/non-zephyr-path.md).

Der Katalog aus Phase 3/4 wird zu Kommentaren in der Suite, auf zwei Ebenen:

  1. Pro-Funktion-Header – ein Block am Anfang des Testabschnitts dieser Funktion, der den vollständigen Katalog auflistet (jede Partition/Grenze mit ihrer ID und Kurzbeschreibung).
  2. Pro-Test-Annotation – jeder Test verweist darauf, welche(n) Eintrag(e) er übt, z. B. /* Covers: P3 + B1 */.

Obligatorische Regeln (das macht die Rückverfolgbarkeit wertvoll):

  • Kein verwaister Test: jeder Test verweist auf ≥ 1 Katalogeintrag.
  • Kein verwaister Eintrag: jede Partition/Grenze wird von ≥ 1 Test geübt. Ein Eintrag ohne Test wird als Lücke markiert (speist Phase 8).
  • Stabile, identische IDs zwischen Header und Annotationen – damit ein grep "P4" den Header und jeden Test auflistet, der diese Partition berührt.
  • Ein Test, der eine Kombination abdeckt, listet alle IDs auf (/* Covers: P2 + P5 */).
  • Covers: sagt die Wahrheit: jede annotierte ID stimmt mit dem überein, was der Fall tatsächlich übt (Eingabe, Zustand und Assert), nicht nur mit einem Eintrag, der im Katalog existiert. Vor Phase 7, lies die Annotationen gegen jeden Testkörper erneut und korrigiere diejenigen, die nicht übereinstimmen.
  • In Pfad B und C das gleiche Schema, angepasst an die Syntax des Frameworks (Kommentar über jedem TEST(...)/TEST_CASE(...)/TEST_F(...)).

Kanonische Form (Pfad A, ztest):

/*
 * Katalog — motor_ctrl_transform  (Phase 3 · spezifikationsbasiertes Testen)
 * Partitionen:
 *   P1  p_axis_values == NULL        -> abgelehnt, Ausgabe nicht geschrieben
 *   P2  gültige axis_count (1..N)      -> transformiert
 *   P3  positiver Wert               -> positive Schritte
 *   P4  negativer Wert               -> Vorzeichen erhalten
 *   P5  Nullwert                     -> null Schritte
 * Grenzen:
 *   B1  axis_count == N (max)        -> akzeptiert
 *   B2  axis_count == N+1            -> abgelehnt
 */

/* Covers: P2 + P3 */
ZTEST(motor_ctrl_suite, test_transform_positive_gives_positive_steps) { /* ... */ }

/* Covers: P1 */
ZTEST(motor_ctrl_suite, test_transform_null_input_is_rejected) { /* ... */ }

Hinweis: In neuerem Zephyr erfordert der Runner, dass Testnamen mit test_ beginnen (ZTEST(suite, test_...)) – ohne dieses Präfix sammelt twister den Fall nicht.

Defekt gefunden: Code weicht von der Dokumentation ab

Wenn der Code dem Header, dem Funktionskommentar oder dem API-Vertragsdokument widerspricht (z. B. gibt einen anderen Fehlercode zurück als dokumentiert, stellt den Zustand bei Fehler nicht wieder her), lasse die Suite nicht rot und verwende nicht ZTEST_EXPECT_FAIL. Schreibe zwei Tests:

  1. Dokumentiertes Verhalten, mit Grund ignoriert – der Assert dessen, was die Dokumentation verspricht, übersprungen mit dem Grund: ztest_test_skip() (Pfad A) oder TEST_IGNORE_MESSAGE("BUG: ...") (Pfad B). Kommentar darüber: /* Covers: Pn — BUG: <docs say X; code does Y> */.
  2. Charakterisierung – fixiert das aktuelle Verhalten, sodass jede Änderung (einschließlich der Korrektur) sichtbar wird. Kommentar: /* Covers: Pn — Charakterisierung des obigen BUGs */.

Die Suite endet grün, der Fehler wird in jedem Durchlauf sichtbar (als Skip/Ignore) und das Binary geht in die Messungen ein. Syntax in den Pfad-Dateien. Liste die Defekte in der abschließenden Zusammenfassung auf.

Phase 7 — Ausführung + Coverage

Vor dem Ausführen aktiviere die Projektumgebung. Das Test-Tool ist selten im globalen PATH – es lebt normalerweise in einem Repo-Virtualenv. Ziehe nicht den Schluss „kann nicht ausgeführt werden“, bevor du die Umgebung gesucht und aktiviert hast (dies gilt für jeden Pfad):

  • Suche nach einem venv im Firmware-Root (.venv/, venv/, env/) und verwende sein Binary (.venv/bin/west, .venv/bin/python) oder source .venv/bin/activate. Das venv ist oft ein Symlink: Überprüfe mit test -e .venv/bin/activate oder ls -laL (welche dem Link folgen), niemals nur mit ls -d/find -type d. Sage, was du gefunden hast: den verwendeten venv-Pfad oder „kein venv in <geprüften Verzeichnissen>“.
  • Zephyr: setze ZEPHYR_BASE (z. B. deps/zephyr, oder west topdir/.west/config) und für den Host ZEPHYR_TOOLCHAIN_VARIANT=host. Wenn west nicht im PATH ist, aber der Zephyr-Baum existiert, führe twister direkt aus: python3 $ZEPHYR_BASE/scripts/twister ....
  • ESP-IDF: stelle sicher, dass die IDF-Umgebung gesetzt ist (. $IDF_PATH/export.sh wenn vorhanden) für den Host-Build; andernfalls kompiliere direkt mit gcc (siehe espidf-path.md).

Erst nachdem diese Suche erschöpft ist, ist es ehrlich zu berichten, dass die Ausführung hier nicht möglich war.

Kontextökonomie (Firmware-Builds sind ausführlich und jede Runde liest alles neu):

  • Leite Build-, Twister- und gcovr-Ausgabe in eine Datei um (> build.log 2>&1) und zeige nur die Zusammenfassung (tail -n 30 build.log, grep -E 'error|FAIL|PASS|lines|branches' build.log). Öffne das gesamte Log nur, wenn die Zusammenfassung den Fehler nicht erklärt.
  • Schreibe jede Testdatei vollständig auf einmal; korrigiere mit gezielten Änderungen.
  • Gruppiere unabhängige Lese- und Prüfvorgänge in derselben Runde; lese keine Dateien erneut, die du bereits gelesen hast.

Führe das Tool des Pfades aus und weise auf die Artefakte hin, die es erzeugt (schreibe keinen Bericht):

  • Pfad A (Zephyr): west twister --coverage -p native_sim -T <suite_path>. Artefakte: twister-out/coverage/index.html, twister-out/coverage.json, twister-out/twister.xml, twister-out/twister.json.
  • Pfad B (ESP-IDF): Host-Build mit Unity + gcov/gcovr (siehe espidf-path.md); weise auf den Coverage-Bericht (coverage.html/.xml) und die Testausgabe hin.
  • Pfad C (andere): Der eigene Runner des Projekts + Coverage (z. B. ceedling gcov, oder gcov/gcovr über den Test-Build). Behalte und weise auf die erzeugten Artefakte hin.

Details und Fehlerbehebung in der Datei jedes Pfades.

Phase 8 — Coverage-Iteration

Vergleiche die von den Tools gemeldete Coverage mit dem 95/95/90-Ziel (oder dem vom Benutzer gewünschten). Unter dem Ziel ist jede erreichbare Zeile/jeder erreichbare Zweig ohne Test eine Lücke; sobald das Ziel erreicht ist, stoppe und liste auf, was nicht abgedeckt bleibt. Füge für jede Lücke Fälle hinzu, die beide Quellen verwenden: die numerische Coverage (rote Zeilen/Zweige) und den Katalog (Partitionen/Grenzen ohne Test). Jede 2 Coverage-Iterationen ohne Erreichen des Ziels: HALTE AN und frage den Benutzer, ob er weitermachen soll.

Verwandte Skills