
Seit knapp zwei Jahren entwickle ich zusammen mit meinem Mitgründer Nick die Hard- und Software für StarterStopper, eine elektronische Wegfahrsperre aus Cairns, Australien. Getestet wurde bisher stets manuell mit einer Testbox, die alle notwendigen Komponenten eines Autos mit entsprechenden Hardwarekomponenten simuliert. Dazu gab es manuell geführte Prüfprotokolle und jede größere Code-Änderung zog ein striktes Regiment an Tests nach sich.
Das Problem mit Agenten und Hardware
Coding-Agenten sind inzwischen erstaunlich gut darin, sich durch eine Codebasis zu arbeiten: Zustandsautomaten lesen, Übergangstabellen dagegen halten, Tests schreiben, Tests laufen lassen, Ergebnis interpretieren. Bei Embedded-Firmware endet das an einer harten Grenze. Der Code läuft auf einem Mikroprozessor, der Effekt ist ein Relais, das klickt. Kein Agent der Welt kann einen Taster drücken.
Die Lösung ist alt und unspektakulär: ein sogenanntes Datenerfassungsgerät (DAQ). In meinem Fall ein LabJack T4 am Ethernet. Drei seiner Ausgänge schalten Relaismodule, die echte 12 V auf Taster, Zündung und Versorgung des Geräts legen. Drei Eingänge lesen über Optokoppler die Relaiskontakte für Kraftstoff, Alarm und den Leuchtring zurück. Ein Zener-Board macht aus dem Beeper-Ton einen Pegel, den man zählen kann. Materialkosten unter 500 Euro und die Verdrahtung kostete einen Nachmittag.
Damit hat der Agent Hände und Augen. Und ab da wird es interessant.
Methodisch durch den Code
Der Ablauf ist im Grunde immer der gleiche: Man liest den Zustandsautomaten der jeweiligen Variante, hält ihn gegen die Übergangstabelle (transition table) und gegen das manuelle Prüfprotokoll. Für jede Protokollzeile entsteht ein Test, der die Timer, Konstanten und Schaltung direkt aus der Firmware zieht. Jeder Test beginnt dabei mit einem Power-Cycle des Geräts, ebenfalls automatisiert getriggert, damit kein Zustand vom Vortest übrig bleibt.
Das Ganze sieht dann beispielhaft wie folgt aus:
TEST_F(HilTest, CorrectCodeUnlocksAndRelocks) {
const auto unlocked = unlockWithDefaultCode();
ASSERT_TRUE(unlocked);
bench().setIgnition(false);
const auto relocked = waitFor([](const Inputs& i) { return !i.fuel && !i.blue_light; },
kEngineOffDelay + milliseconds(4000));
ASSERT_TRUE(relocked) << "DUT did not re-lock after ignition off";
}
Kuriose Bugs
Dabei sind ein paar Bugs zutage getreten, die man unter „normalen” Bedingungen entweder gar nicht oder nur höchst unwahrscheinlich und dann nicht reproduzierbar findet: Timing im Millisekundenbereich oder ein Verhalten, das an einer Reihe von miteinander verschachtelten Zuständen hängt.
Ein Beispiel: Wird die Zündung weniger als 250 ms nach dem Loslassen des Tasters ausgeschaltet, bleibt die Benzinpumpe 20 s statt 5 s frei. Die Flanke des losgelassenen Tasters landete als eigenes Ereignis im nächsten Zustand und startete dort den falschen Timer. Eine Race Condition, die ein Relais mit 20 ms Schaltzeit zuverlässig trifft, ein Mensch nie.
Die Zahlen
Insgesamt ist durch meinen Hardware-in-the-Loop-Prüfstand (HIL) die Testabdeckung noch einmal deutlich gestiegen, und auch die Anzahl an Tests wurde um einen guten zweistelligen Prozentsatz gesteigert.
Der Testcode ist mit rund 2.100 Zeilen C++ und 800 Zeilen Python inzwischen ein Viertel der Firmware. Das klingt viel und ist genau richtig.
Ein kompletter Classic-Durchlauf dauert sieben Minuten, die One mit Lernmodus und 30-Sekunden-Sirene eine halbe Stunde. Das ist die Zeit, die die Firmware braucht, nicht der Prüfstand. Natürlich alles schick in CMake (bzw. CTest) eingegliedert, sodass man bequem aus der IDE heraus die komplette Test-Suite steuern kann.
Fazit
Der eigentliche Gewinn hier ist, dass der Agent jetzt selbstständig weitere Features entwickeln kann und beispielsweise auch größere Refactorings vornehmen kann, ohne dass Teile des Codes dabei kaputtgehen. Sämtliche kritischen Pfade sind vollständig (auf Hardware) getestet und können für jede Änderung neu verifiziert werden.
Als Nächstes steht jetzt noch Fuzz-Testing an, und ich werde diesen Testaufbau auch für weitere StarterStopper-Varianten wieder einsetzen können. Falls jemand Ähnliches für seine Hardware vorhat: Genau diese Art Brücke zwischen Firmware und Prüfstand baue ich auch für Kunden, siehe Embedded-Software-Engineering.