Forum: Mikrocontroller und Digitale Elektronik Unit Tests auf ARM Cortex M


von Walter T. (nicolas)


Lesenswert?

Hallo zusammen,

auf meiner Liste von Dingen, die ich unbedingt ausprobieren will, stehen 
Unit-Tests an recht prominenter Stelle.

Bisher teste ich Funktionen eher rudimentär mit vielen Asserts und ein 
paar Ein-Ausgabetests, die aber nur bei der Entwicklung der Funktion 
genutzt werden.

Allerdings interessiert mich der Ansatz mit xUnit - Tests dauerhaft 
neben dem eigentlichen Programm zu halten - sehr.

In der Artikelsammlung findet sich ein Artikel zu uCUnit 
(https://www.mikrocontroller.net/articles/Unittests_mit_uCUnit), welches 
aber anscheinend seit 2012 verwaist ist.

Ich entwickle meine Progrämmchen für ARM Cortex M3/M4 
(STM32F103/STM32F446) und einen auf dem PC lauffähigen Mock-Up (mit 
Emulation der Benutzerschnittstelle Display, Knöpfe etc. über ein 
Fenster und Tastaturkommandos) in C mit dem ARM-GCC/MinGW.

Unit-Tests sollten natürlich auch auf beiden Plattformen laufen.

Als Build-Prozeß findet momentan der von EmBitz Verwendung.

Welches Test-Framework eignet sich hierfür? Und wie habt ihr angefangen?

Viele Grüße
W.T.

: Bearbeitet durch User
von Adapter (Gast)


Lesenswert?

Walter T. schrieb:
>
> Welches Test-Framework eignet sich hierfür? Und wie habt ihr angefangen?
>
> Viele Grüße
> W.T.

testIdea.

Allerdings sind Unit tests für typische Embedded Anwendungen nur 
beschränkt hilfreich, da sie nebenläufige Probleme (die speziell mit der 
Aussenwelt kommunizierenden uCs - alle praktisch Alle) nicht abdecken. 
Die Kosten-Nutzen Analyse für das doch sehr invasive Unit testen sollte 
also sehr sorgfältig vorgenommen werden, bevor man den Weg einschlägt. 
Was nicht heissen soll, dass man nicht testen sollte (je mehr Fehler man 
umso früher finden kann, desto besser), aber nicht Alles um jeden Preis. 
5% des Fehlerpontials zu eliminieren sollte keine 95% der 
Entwicklungskapazität binden. Diese Zahlen sind nicht auf Unit Testing 
bezogen, sondern nur ein überzogenes mögliches Szenario.

von Walter T. (nicolas)


Lesenswert?

Adapter schrieb:
> Die Kosten-Nutzen Analyse für das doch sehr invasive Unit testen sollte
> also sehr sorgfältig vorgenommen werden, bevor man den Weg einschlägt.

Das ist genau mein Ziel: Feststellen, ob die Nutzung eines 
Test-Frameworks den Aufwand wert ist.

Das Schöne am Hobby ist: Ich kann das völlig ergebnisoffen angehen.

von Walter T. (nicolas)


Lesenswert?

testIdea wurde schon in den Raum geworfen.

QTools von Quantum Leap scheint direkt für den  in "Test Driven 
Development for Embedded C" von James W. Grenning beschriebenen zu 
orientierten Workflow gebaut zu sein.

Hat irgendjemand schon irgendeinen Test-Harness wirklich 
benutzt/getestet und kann dazu etwas sagen?

von Oliver J. (skriptkiddy)


Lesenswert?

Adapter schrieb:
> Walter T. schrieb:
>>
>> Welches Test-Framework eignet sich hierfür? Und wie habt ihr angefangen?
>>
>> Viele Grüße
>> W.T.

Ich fahre den Ansatz, dass man für normalen hardwareunabhängigen Code 
seine Tests nicht auf der Zielhardware ausführen muss. Der Code wird 
schon modular aufgebaut und jedes Modul, sofern nötig, wird dann für 
sich losgelöst getestet. Dazu werden die Linkzeitabhängigkeiten durch 
Stubs ersetzt. Laufen lassen kann man das mit jeden beliebigen 
UnitTestFramework - für C/C++.

Konkret schaut das bei mir so aus:
Als Testfamework wird gtest/gmock genutzt. Die Sachen, die ich durch 
Stubs ersetzen will, werden durch freie Funktionen ersetzt, die Aufrufe 
in ein Mock-Singleton machen. Auf das Mocksingleton kann man dann wie 
üblich seine  Aufrufe Testen, Actions festlegen und und und ......
Gebaut wird das ganze für die Entwicklungsmaschine und wird dann auch 
auf der ausgeführt.

Das Ganze wirkt erst einmal sehr oversized, aber die Möglichkeiten von 
Gmock sind den Aufwand meiner Meinung nach wert.

Grüße Oliver

von Fragenüberfragen (Gast)


Lesenswert?

Interessanter Thread. Ist es auch möglich bzw. hat es schon mal jemand 
versucht gtest gmock auf der Zielhardware laufen zu lassen?

von Oliver J. (skriptkiddy)


Lesenswert?

Fragenüberfragen schrieb:
> Ist es auch möglich bzw. hat es schon mal jemand
> versucht gtest gmock auf der Zielhardware laufen zu lassen?

Aufgrund der wenigen Ressourcen in µC (also in dem Fall meine ich in 
etwa den Footprint der Cortex M) halte ich das für unmöglich, da der 
Speicher-Bedarf des gtest-/gmock-Frameworks erfahrungsgemäß zu hoch ist.


Achtung hier folgt mal wieder eine gepflegte Nähkästchenplauerei :D


Hatte bis jetzt aber nur einen einzigen Fall, bei dem durch Unittests, 
die nicht auf der Zielhardware liefen, ein Fehler nicht gefunden wurde, 
der dann nur auf dem Zielsystem zu Problemen geführt hat (von den 
anderen unentdeckten Fehlen mal abgesehen :D). Wir hatten in einem 
Framework einen statischen Memorypool, den wir beim Initialisieren mit 
einer eigenen Malloc-Routine angezapft haben. Leider hat niemand daran 
gedacht, dass es bei ARM Probleme gibt, wenn man Blöcke heraus gibt, die 
byte-wise-aligned sind - die alte Unaligned-Access-Geschichte bei ARM 
halt. ;) Wir haben auf X86 (dort ist das egal) entwickelt und die Test 
natürlich nur dort laufen lassen. Aber man kann nicht alles erwischen. 
;)

Grüße Oliver

: Bearbeitet durch User
von Dr. Sommer (Gast)


Lesenswert?

Oliver J. schrieb:
> die alte Unaligned-Access-Geschichte bei ARM
> halt. ;)
Sowas ist auch laut C bzw. C++ -Standard verboten, nur leider merkts 
man's auf x86 halt nicht, wenn mans falsch macht (wird nur langsamer). 
ARM ist hier also nicht schuld... Man kann sowas auf x86 mittels 
Instrumentierung finden, z.B. beim GCC & Clang indem man 
"-fsanitize=undefined" übergibt. Dieser fehlerhafte Code:
1
int main () {
2
  char array [100];
3
  *((int*) (array+1)) = 42;
4
}
Wird dann mit dieser Meldung quittiert:
1
test.c:3:22: runtime error: store to misaligned address 0x7fff5d6d00d1 for type 'int', which requires 4 byte alignment
Die Option findet auch eine Menge weiterer Fehler und ist zum 
Testen/Debuggen sehr hilfreich (verlangsamt aber das Programm).

von Oliver J. (skriptkiddy)


Lesenswert?

Dr. Sommer schrieb:
> ARM ist hier also nicht schuld

Hat auch keiner behauptet. Wir sind natürlich daran Schuld gewesen.

Der sanatizer war zu der Zeit leider keine wirkliche Option, da wir 
unter Windows mit mingw entwickelt haben. Da  war keine Unterstützung 
geboten. Geht das mittlerweile mit mingw?

Grüße Oliver

von Dr. Sommer (Gast)


Lesenswert?

Oliver J. schrieb:
> Der sanatizer war zu der Zeit leider keine wirkliche Option, da wir
> unter Windows mit mingw entwickelt haben.
Achso.

Oliver J. schrieb:
> Geht das mittlerweile mit mingw?
Sieht leider nicht so aus :-/

von Walter T. (nicolas)


Lesenswert?

Oliver J. schrieb:
> Geht das mittlerweile mit mingw?

Mit gcc-5.1.0 geht es nicht. Beim Build fehlen ihm quasi sämtliche 
Library-Funktionen (`__ubsan_handle_sub_overflow', 
`__ubsan_handle_add_overflow', `__ubsan_handle_type_mismatch' ...)

von J. F. (Firma: Père Lachaise) (rect)


Lesenswert?

Walter T. schrieb:
> testIdea wurde schon in den Raum geworfen.

Sehe ich das richtig, dass ich für die Benutzung von testIdea auch 
zwingend deren Hardware (diese Blue Boxes) verwenden muss? Genauer: 
neben den Kosten für die Software fallen hier auch kosten für die 
Hardware an?

von Decius (Gast)


Lesenswert?

Wie es weiter oben schon bemerkt wurde, kann es zu unentdeckten Fehlern 
führen, wenn man nicht auf der Zielhardware testet. Daher wird das bei 
uns immer so gehandhabt. Folge ist dann natürlich das man ein 
ressourcesparendes Unit Testframework braucht. Ich komme im embedded 
Bereich mit dem folgenden gut zurecht.

http://ucunit.org/

@Adapter
>"Allerdings sind Unit tests für typische Embedded Anwendungen nur
>beschränkt hilfreich, da sie nebenläufige Probleme (die speziell mit der
>Aussenwelt kommunizierenden uCs - alle praktisch Alle) nicht abdecken."

Modultest sind lediglich dafür gedacht statisch Algorithmen zu testen. 
Das Testen auf nebenläufige Probleme würde ich eher im Bereich des 
Integrationstests sehen.

von Walter T. (nicolas)


Lesenswert?

Hallo zusammen,

ich war zunächst sehr mißtrauisch gegenüber ucunit, insbesondere wegen 
des nur einzigen Commits, der zudem lange zurückliegt  und der kurzen 
Doku.

Aber: Das Ganze besteht ja ohnehin nur aus ein paar C-Dateien, Makefiles 
und .bat-Dateien. Die eigentliche "Test-Suite" ist eine Sammlung von 
Makros in uCUnit-v.h. Die einzige Beispieldatei scheint Doku genug zu 
sein.

Also habe ich einfach mal die ersten Versuche gemacht. Einfache Funktion 
und Tests entwickelt (Dezibel in Leistungsfaktor umrechnen).

Das ging relativ schnell.

Die Makefiles sind sehr einfach gestrickt, daß sich ucunit auch 
problemlos in der gewohnten IDE bauen läßt.

Was mir noch nicht klar ist, wie ich das auf ein ganzes Projekt beziehe, 
ohne daß entweder die Tests-Datei ein riesiger Spaghettiteller und 
Header-Grab wird oder ich die Übersicht über die Tests verliere.

Außerdem ist mir noch nicht klar, ob ich irgendwelche wirkungslosen 
Stubs implementieren muß, um beispielsweise "UCUNIT_Tracepoint()" in 
meinem "Produktivcode" behalten zu können.

Zuletzt ist mir noch unklar, was mit den Checklists gemeint ist und wie 
sie sich vom Testcase unterscheiden.

Naja, wie immer, wenn man ein neues System lernt: Möglichkeiten und 
Probleme müssen sich erst zeigen.

: Bearbeitet durch User
von Walter T. (nicolas)


Lesenswert?

ucUnit habe ich gestern abend dann doch erst einmal beiseitegelegt.

Was daran super ist: Es läßt sich kurz und schmerzlos in den 
Build-Prozeß der IDE einbauen. Der Fußabdruck ist winzig, und es ist für 
die Target-Plattform vorgesehen.

Was mir auf den Geist geht: In den "FAILED"-Meldungen fehlt die 
Information, wie der Test fehlgeschlagen ist. Ich hätte etwas wie 
"expected X, got Y" erwartet. So mußte ich bei jedem Test auch erst 
einmal jede Menge printf-Ausgaben verteilen, um die Fehlerursache 
einzugrenzen.

Mein nächster Testkandidat ist "unity". Es sind auch reine 
C-Source-Dateien, sie lassen sich auch problemlos in den bisherigen 
Workflow einbinden,  und die "Failed"-Meldungen sind deutlich lesbarer.

Zumindest die Beispieldateien scheinen auch mehr darauf gestrickt zu 
sein, viele kleine Tests auf viele kleine Quelltextdateien verteilen zu 
können.

Ich bleibe dran.

Beitrag #5301469 wurde von einem Moderator gelöscht.
von ArmFreak (Gast)


Lesenswert?

J. F. schrieb:
> Sehe ich das richtig, dass ich für die Benutzung von testIdea auch
> zwingend deren Hardware (diese Blue Boxes) verwenden muss?

würde mich auch interessieren

von Walter T. (nicolas)


Lesenswert?

Nach knapp zwei Wochen mit "unity" würde ich behaupten: Das ist mein 
Ding. Ich sehe nur einen großen und einen für mich momentan noch weniger 
relavanten Nachteil. Dazu später. Ich denke allerdings, das 
mittelfristig die Vorteile überwiegen.

Wichtig war mir die Integration in mein bestehendes Projekt. Schließlich 
will ich durch die Geschichte unterm Strich mehr Zeit einsparen als 
aufwenden.

Der Workflow sieht jetzt so aus: Modultests werden (momentan) nur für 
Funktionen geschrieben, an denen aus irgend einem Grund etwas geändert 
wird. Sei es, daß sie erweitert werden, ein Fehler gesucht oder sie neu 
angelegt werden. Und momentan noch ausschließlich auf dem PC. Bevor die 
Funktion angefaßt wird, wird anstelle der normalen printf-Ausgabe für 
die Untersuchung einfach direkt der passende Modultest angelegt und 
ausgeführt. Der Unterschied besteht momentan also erst einmal darin, daß 
ich nach getaner Arbeit die Debug-Ausgaben nicht mehr lösche, weil sie 
ohnehin nicht mehr an Stellen stehen, wo sie stören.

Viel Zeit habe ich dadurch noch nicht gespart, aber meine Skrupel, alten 
Quelltext zu löschen oder anzufassen, haben massiv abgenommen.

Und jetzt die beiden Nachteile: Wenn ich das richtig sehe, gibt es keine 
Möglichkeit, eine "code test coverage" zu instrumentieren oder zu 
ermitteln. Na gut. Damit kann ich (momentan) leben. Die Testabdeckung 
ist ohnehin noch sehr gering.

Der relevantere Nachteil: Was für ein bekloppter Name! Wie soll ich bei 
so einem Allerweltsnamen überhaupt Informationen in einer Suchmaschine 
finden?

Und deshalb die Frage an die Nutzer: Sehe ich das richtig, daß es keine 
vorgesehene Möglichkeit gibt, die TEST-Makros auszuschalten?

Irgendwie nervt es mich gerade, daß ich die Test-C-Dateien alle einzeln 
aus dem Build-Prozeß für den Release-Code herausnehmen muß. Hier wäre es 
praktisch, wenn ich die TEST-Makros einfach zu einer wirkungslosen 
Codezeile machen könnte. Oder wie sorgt ihr dafür, daß die Tests nicht 
im Release-Build landen?

: Bearbeitet durch User
von Christopher J. (christopher_j23)


Lesenswert?

Walter T. schrieb:
> Irgendwie nervt es mich gerade, daß ich die Test-C-Dateien alle einzeln
> aus dem Build-Prozeß für den Release-Code herausnehmen muß. Hier wäre es
> praktisch, wenn ich die TEST-Makros einfach zu einer wirkungslosen
> Codezeile machen könnte.

Normalerweise würde man das wohl einfach durch separate Targets lösen. 
Ein Test-Target, ein Debug-Target und ein Release-Target. Jedenfalls 
empfinde ich persönlich das umbauen des CPP zu einer Art Build-System 
ein bisschen "von hinten durch die Brust ins Auge". Die Macher von Unity 
haben dafür ja eigens das Test-Build-System "ceedling" geschaffen. Ich 
denke aber man kann das auch mit normalem Make bzw. CMake hinwurschteln.

von Walter T. (nicolas)


Lesenswert?

Christopher J. schrieb:
> Normalerweise würde man das wohl einfach durch separate Targets lösen.

Ja. Jein.

Separate Targets benötige ich ja sowieso - allein, weil ich für zwei 
(vier) unterschiedliche Plattformen bauen will.

Aber: Ich finde es immer angenehm, wenn ich der Build-Umgebung (egal ob 
make oder IDE) einfach eine komplette Verzeichnisstruktur zum Fraß 
vorwerfen kann, ohne jede Datei einzeln zu jedem Target an- oder 
abwählen zu müssen.

von Joe J. (j_955)


Lesenswert?

abo!

von Walter Tarpan (Gast) (Gast)


Lesenswert?

Joe J. schrieb:
> abo!

Nicht nötig. Der Drops ist gelutscht. Die automatisierten Modultests mit 
Unity auf dem PC und auf dem Target laufen seit Februar völlig 
zuverlässig und haben mir schon viele Fehler erspart oder anderweitig 
die Fehlersuche erleichtert, insbesondere dann, wenn man doch mal den 
Debugger zur Hilfe nehmen muß.

Bei Funktionen, die dazu geeignet sind (also alles, was nichts mit I/O, 
Benutzerinteraktion oder grafischer Darstellung zu tun hat), nutze ich 
regelmäßig sogar test-driven-design. Bei den Funktionen, bei denen das 
gut geht, geht es sogar sehr gut.

Alles in allem denke ich, ist der break-even-point, bei dem mehr Zeit 
eingespart als hineingesteckt wird, seit Monaten weit überschritten.

Ich hatte zwar mal damit angefangen, meine Erfahrungen als kurzen 
Artikel zusammenzufassen, aber die finale Überarbeitung steht immer noch 
aus. Egal. Einfach selbst ausprobieren. Ist kein Hexenwerk.

von soso (Gast)


Lesenswert?

Wie hast denn das mit der Instrumentierung gelöst?Über Makros dann?

Der Code wird dann super unübersichtlich. Cantata hat da einen ganz 
netten Ansatz kostet aber richtig asche. Das Framework kann aber dafür 
schon einiges.

Ich hasse ebenfalls die Code-Instrumentierung, da dies den Code deutlcih 
aufbläst.

von soso (Gast)


Lesenswert?

Wie lang bist denn dran gesessen, bis du ein Konzept hattest für Deine 
Projekte, bist einfach nach dem Schema vorgegangen wie auf UNITY HP 
dargestellt?

BTW:
Unittests auf dem Target sind unerlässlich imho um zu sehen ob der 
Compiler und die CPU das machen was sie sollen, nicht wahr?

von Walter Tarpan (Gast) (Gast)


Lesenswert?

soso schrieb:
> Wie hast denn das mit der Instrumentierung gelöst?

Was meinst Du mit "Instrumentierung"? Wenn Du darunter das Gleiche 
verstehst wie ich (Extra-Code zur Bestimmung der Testabdeckung und fürs 
Profiling): Gar nicht.

Wenn Du darunter die Möglichkeit verstehst, im Test Fehler zu triggern: 
Die Funktionen error() und assert() wurden durch eigene Varianten 
ersetzt, bei denen zur Laufzeit die Möglichkeit besteht, für einen 
einzigen Fehler lediglich einen Rücksprung zu erzeugen und nicht den 
Fault-Handler aufzurufen.

Ein Test, der eine Fehlerbedingung testet, sieht dann eben so aus:
1
    TEST(bitmath, nullArgument)
2
    {
3
        prepareErrorCondition();
4
        if( TEST_PROTECT() )
5
        {
6
            // Funktionen sind fuer zweiter Parameter 0 komplett sinnlos
7
            areAllBitsSet(0x0, 0x0);
8
        }
9
        TEST_ASSERT_TRUE( isErrorConditionTriggered() );
10
        clearErrorCondition();
11
    }

von Walter Tarpan (Gast) (Gast)


Lesenswert?

soso schrieb:
> Wie lang bist denn dran gesessen, bis du ein Konzept hattest für Deine
> Projekte,

Wenn ich den Thread überfliege, würde ich sagen: 10 Tage. Also maximal 
20...30 Stunden.

soso schrieb:
> bist einfach nach dem Schema vorgegangen wie auf UNITY HP
> dargestellt?

Umgekehrt: Ich habe erst ein paar Beispiele aus dem Unity-Packages ans 
Laufen bekommen, abgewandelt, und dann erst verstanden, was die mir mit 
den Infos auf der Website sagen wollen. Hatte noch nie zuvor, auf keine 
Plattform, mit soetwas gearbeitet.

soso schrieb:
> Unittests auf dem Target sind unerlässlich imho um zu sehen ob der
> Compiler und die CPU das machen was sie sollen, nicht wahr?

Wahrscheinlich ist das die offizielle Begründung. Ehrlich gesagt nutze 
ich die Modultests auf dem Target hauptsächlich dazu, Fehler schnell und 
reproduzierbar zu erzeugen, um sie dann mit dem Debugger zu jagen.
Unterm Strich geht das immer schneller, als mehrmals hintereinander den 
gleichen Programmablauf durch Benutzerinteraktion zu erzeugen. Plus ich 
habe die Sicherheit, daß ein einmal entfernter Fehler auch entfernt 
bleibt.

von Walter Tarpan (Gast) (Gast)


Lesenswert?

Walter Tarpan (Gast) schrieb:
> nutze
> ich die Modultests auf dem Target hauptsächlich dazu, Fehler schnell und
> reproduzierbar zu erzeugen, um sie dann mit dem Debugger zu jagen.

*) Nachtrag: Wenn sie durch die Modultest-Assertions nicht schon von 
selbst sichtbar werden.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.