Hallo Gemeinde Ich habe eine C-Funktion, welche lokale static Variablen benützt, um z.B. den Zustand einer FSM zu speichern. Nun möchte ich zu dieser Funktion eine Testfunktion schreiben. Dazu müsste ich aber die statische Variable von aussen manipulieren können. Gibt es eine halbwegs elegante Lösung, wie man von aussen (separates Testmodul) an diese statische Variable gelangt?
Sowas muss man nicht erst testen um zu wissen, dass es Murks ist. Das kann man gleich fixen.
Nein, wenn Du mit "lokal" eine lokale Variable innerhalb der Funktion meinst. Ja, wenn Du mit "lokal" eine lokale Variable innerhalb des C-Moduls meinst in dem sich deine Funktion befindet. In diesem Fall mußt Du dem Modul eine "set"-Funktion für diese Variable hinzufügen und via Header sichtbar machen. Neil
Nein, mit lokal meine ich eine static Variable innerhalb einer Funktion. Auf die Statischen eines Modules ist der Zugriff ja kein Problem.
Ein relativ eleganter Weg wäre, die statics in ein eigenes Modul zu packen, das während des Tests zur Laufzeit durch ein Mock-Modul ersetzt wird. Das ist in OO-Sprachen eine gängige Vorgehensweise und leicht zu realisieren, in C könnte das aber das ein klein wenig Hirnschmalz erfordern. Gruß Markus
So wenn das jetzt völliger Schnurz ist killt mich bitte nicht ;-) Die statische Variable bleibt konstant an einer Stelle im Speicher. Du könntest also in eine externe Zeigervariable die Adresse deiner statischen Variable in der Funktion schreiben. Diese Adressvariable macht du dann mittels Header deinem Unit Test Modul sichtbar. Willst du deine statische Variable verändern schreibt dein Testmodul auf die Speicherstelle auf die der Zeiger zeigt. Das ganze macht du mittels #define für den "normalen" Betrieb unsichtbar. L.g.
Die Frage ist doch ob diese statische Variable Sinn macht. Man versucht da ja innerhalb der Funktion eine Art Gedächtnis zu implementieren. Das ist wie bei strtok(). Die Funktion ist nicht reentrant und führt in jeglichen Multithreadinganwendungen zu nicht reproduzierbaren Fehlern. Im Allgemeinen ist es oft besser statt einer static Variablen innerhalb einer Funktion dieser Funktion eine Adresse auf eine ausserhalb definierten Variable zu übergeben. So können unterschiedliche Aufrufer (rekursiv / multithreading) auch ggf. unterschiedliche Versionen der Variable halten.
So hab das jetzt schnell probiert. Problem hatte ich nur dass die Adresse ja irgendwie initialisiert werden muss. Das hab ich mir einer zusätzlichen init Variable gelöst. Aber das Prinzip mit der Adresse funktioniert. Hier mein Minimalcode:
1 | #define UNIT_TEST
|
2 | |
3 | #ifdef UNIT_TEST
|
4 | static char init; |
5 | #endif
|
6 | |
7 | static unsigned int *a_step; |
8 | |
9 | void fsm(void); |
10 | |
11 | void fsm(void) { |
12 | static unsigned int step = 0; |
13 | #ifdef UNIT_TEST
|
14 | if (init){ |
15 | a_step=&step; |
16 | init=0; |
17 | return; |
18 | }
|
19 | #endif
|
20 | |
21 | switch (step) { |
22 | case 0: |
23 | printf("Case 1\n"); |
24 | step++; |
25 | break; |
26 | case 1: |
27 | printf("Case 2\n"); |
28 | step++; |
29 | break; |
30 | case 2: |
31 | printf("Case 3\n"); |
32 | step = 0; |
33 | break; |
34 | defualt:
|
35 | break; |
36 | }
|
37 | }
|
38 | |
39 | void normal (void) { |
40 | printf("Normal Operation\n"); |
41 | printf("Call 1\n"); |
42 | fsm(); |
43 | printf("Call 2\n"); |
44 | fsm(); |
45 | printf("Call 3\n"); |
46 | fsm(); |
47 | }
|
48 | |
49 | void unitTest (void) { |
50 | #ifdef UNIT_TEST
|
51 | // call fsm first for init address
|
52 | init=1; |
53 | fsm(); |
54 | printf("Test case 3\n"); |
55 | *a_step = 2; |
56 | // test
|
57 | fsm(); |
58 | #else
|
59 | fprintf(stderr,"Unit test not defined but function called\n"); |
60 | #endif
|
61 | }
|
@Michael Danke für das Beispiel. Ja das funktioniert so. Jetzt müsste man das nur noch in ein Makro packen können, damit es wenn möglich so ähnlich aussieht:
1 | void fsm(void) { |
2 | MY_SPECIAL_STATIC(unsigned int, step, 0); |
3 | |
4 | switch (step) { |
5 | case 0: |
6 | printf("Case 1\n"); |
7 | step++; |
8 | break; |
9 | ...
|
Das wäre wahrscheinlich sogar machbar. @Markus Das mit dem Auslagern von funktions-lokalen statischen Variablen ist mir nicht klar, wie das gehen soll. @Udo Full ACK. Bei diesem Ziel-Projekt handelt es sich um ein Embedded System, ohne Multi-Threading. Getestet wird allerdings auf dem PC, mit GCC.
Welchen Sinn hat eigentlich ein unit-Test, wenn sich die Funktion während des Tests anders verhält dank #define UNIT_TEST?
Warum nicht so?
1 | typedef struct |
2 | {
|
3 | uint8_t step; |
4 | } fsm_state; |
5 | |
6 | void fsm(fsm_state* p_state) |
7 | {
|
8 | switch(p_state->step) |
9 | ...
|
10 | ...
|
11 | ...
|
12 | }
|
13 | |
14 | void unit_test(void) |
15 | {
|
16 | fsm_state state; |
17 | state.step = 2; |
18 | fsm(&state); |
19 | ...
|
20 | |
21 | }
|
Wie Udo schon schrieb, sind static variablen für eine vernünftige Testbarkeit des Codes fast so tödlich wie die bösen Singletons.
Natürlich macht es wenig Sinn, überall #ifdef UNIT_TEST einzustreuen. Aber machnchmal geht's halt nicht anders. Und wegen in paar solcher Situationen auf Unit-Tests zu verzichten, kann nicht die Lösung sein. @Tom Ich sehe da zwei Probleme: - Teilweise habe ich Funktionen, die mehrere statische Variable haben. - Der Aufrufer muss die Variable ja auch statisch halten. Am Schluss sind dann alle statischen als Modul-global zu definieren. Das will ich genau nicht. Ich versuche es mal mit dem Ansatz von Michael. Danke für Eure Meinungen.
... du solltest dir aber im klaren darüber sein, dass bereits ein #ifdef UNIT_TEST eine Validierung deiner Funktion durch den Unit Test über den Haufen wirft. Aber vielleicht kommt es hier darauf ja gar nicht an...
Offensichtlich geht es hier sowieso nicht darum, mehr Qualitaet zu erreichen, sondern nur darum den Punkt "Unit-Tests" auf der Anforderungsliste abzuhaken. Da ist es dann nicht wichtig, ob man mit Ausnahmen fuer den Unit-Test denselbigen ad absurdum fuehrt.
Trenn halt einfach deine FSM in ein eigenes Modul ab, pack es in sein eigenes C-File. Dann machst du deine State Variable anstatt funktions-static file-static und schon ist das Problem keines mehr.
1 | static unsigned int step; |
2 | |
3 | void fsm(void) |
4 | {
|
5 | switch (step) { |
6 | case 0: |
7 | printf("Case 1\n"); |
8 | step++; |
9 | break; |
10 | case 1: |
11 | printf("Case 2\n"); |
12 | step++; |
13 | break; |
14 | case 2: |
15 | printf("Case 3\n"); |
16 | step = 0; |
17 | break; |
18 | defualt:
|
19 | break; |
20 | }
|
21 | }
|
22 | |
23 | void unit_test() |
24 | {
|
25 | step = 0; |
26 | // sonstige Vorbedingungen herstellen
|
27 | fsm(); |
28 | if( step != 1 ) |
29 | printf( "ERR001: Fehler in FSM. Zustandsübergang 0->1 nicht durchgeführt\n" ); |
30 | |
31 | ...
|
32 | }
|
Die Funktion unit_test könnte man als Ganzes noch mit einem #ifdef aus dem Compilevorgang ausschliessen, wenn man dem Linker nicht traut. Aber mehr muss es nicht sein. Die Organisation von C-Programmen fängt auf File-Ebene an. Ein C-File ist ein Modul. Und das kann in sich geschlossen werden, so dass zwar alle Funktionen innerhalb des Moduls sich Daten(strukturen) teilen können, aber trotzdem ausserhalb keiner ran kann.
Udo Schmitt schrieb: > Die Frage ist doch ob diese statische Variable Sinn macht. Man versucht > da ja innerhalb der Funktion eine Art Gedächtnis zu implementieren. > Das ist wie bei strtok(). > Die Funktion ist nicht reentrant und führt in jeglichen > Multithreadinganwendungen zu nicht reproduzierbaren Fehlern. Tom K. schrieb: > Wie Udo schon schrieb, sind static variablen für eine vernünftige > Testbarkeit des Codes fast so tödlich wie die bösen Singletons. Hmmmm. Also fassen wir zusammen: 1.) Globale Variablen gelten bekanntlich als "böse" und man soll sie so wenig verwenden wie eben möglich. 2.) static-Variablen innerhalb einer Funktion sind Euch zufolge ebenfalls sehr pöse. Was aber nun, wenn ich ein Gedächtnis brauche? Da gibt es doch etliche Anwendungsfälle dafür, wo eben das Ergebnis einer Berechnung im aktuellen Durchlauf abhängig ist von früheren Berechnungen. Beispiel: Digitales Filter. Wie setzt Ihr sowas um, dass es Euren hehren Ansprüchen genügt? Wilde Verpointerung quer über alle Module? ;-)
Mark Brandis schrieb: > Hmmmm. Also fassen wir zusammen: > 1.) Globale Variablen gelten bekanntlich als "böse" und man soll sie so > wenig verwenden wie eben möglich. richtig. Man soll Funktionen mit Argumenten aufrufen und nicht Argumentpassing per Zuweisung an globle Variablen lösen. Darum geht es nämlich in Wirklichkeit bei der Argumentation "Globale Variablen sind böse". Darum und um gordnete Progammstrukturen mittels Datenkapselung, die durch globale Variablen komplett ausgehebelt wird. > 2.) static-Variablen innerhalb einer Funktion sind Euch zufolge > ebenfalls sehr pöse. sie sind nicht böse. Nur in manchen Fällen unpraktisch. Weil es eben eher selten ist, dass sich eine Funktion, und nur diese eine Funktion, etwas von einem Aufruf zum nächsten merken muss. Spätestens wenn es für den Mechanismus dann einen Init() oder Reset() geben muss, fällt man damit auf die Schnauze oder landet bei wild zusammengewürfelten Funktionsargumenten. > Was aber nun, wenn ich ein Gedächtnis brauche? Da gibt es dann immer noch die 3. Variante: File-Static im Sinne von 'lokal zu einem ganzen Modul'. Und das ist eine der sinnvolle Varianten, wie sich Module etwas funktionsübergreifend merken können. Neben noch anderen Mechanismen, wie zb opaque Datentypen (Ich gebe dem Aufrufer einen Pointer auf ein Datenpaket raus, welches ich zur Speicherung meiner Daten benutze, gebe ihm aber weiter keine Information darüber, wie dieses Datenpaket aufgebaut ist, so dass er mit dem Speicher nichts anfangen kann. Wann immer er etwas von mir will, gibt er mir den Pointer wieder und mit meiner Kentniss des Datenaufbaus kann ich damit arbeiten. Vulgo: Der Aufrufer kriegt von mir einen void Pointer auf die Daten und in der bearbeitenden Funktion wird er wieder zurückgecastet.)
StinkyWinky schrieb: > Gibt es eine halbwegs elegante Lösung, wie man von aussen (separates > Testmodul) an diese statische Variable gelangt? Hallo, ich schlage folgende simple Lösung vor: #ifdef UNIT_TEST #define STATIC #else #define STATIC static #endif STATIC uint32 myGlobalVar_u32; ... Viele Grüße !
Karl Heinz Buchegger schrieb: > Neben noch anderen Mechanismen, wie zb opaque Datentypen > (Ich gebe dem Aufrufer einen Pointer auf ein Datenpaket raus, welches > ich zur Speicherung meiner Daten benutze, gebe ihm aber weiter keine > Information darüber, wie dieses Datenpaket aufgebaut ist, so dass er mit > dem Speicher nichts anfangen kann. Wann immer er etwas von mir will, > gibt er mir den Pointer wieder und mit meiner Kentniss des Datenaufbaus > kann ich damit arbeiten. Vulgo: Der Aufrufer kriegt von mir einen void > Pointer auf die Daten und in der bearbeitenden Funktion wird er wieder > zurückgecastet.) Klingt gut. Danke für die Erläuterung :-)
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.