Forum: PC-Programmierung Unit-Testing: Problem mit static in Funktion


von StinkyWinky (Gast)


Lesenswert?

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?

von GoP (Gast)


Lesenswert?

Sowas muss man nicht erst testen um zu wissen, dass es Murks ist. Das 
kann man gleich fixen.

von Neil (Gast)


Lesenswert?

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

von StinkyWinky (Gast)


Lesenswert?

Nein, mit lokal meine ich eine static Variable innerhalb einer Funktion.
Auf die Statischen eines Modules ist der Zugriff ja kein Problem.

von Markus V. (Gast)


Lesenswert?

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

von Michael R. (dj_motionx)


Lesenswert?

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.

von Udo S. (urschmitt)


Lesenswert?

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.

von Michael R. (dj_motionx)


Lesenswert?

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
}

von StinkyWinky (Gast)


Lesenswert?

@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.

von Klaus W. (mfgkw)


Lesenswert?

Welchen Sinn hat eigentlich ein unit-Test, wenn sich die Funktion 
während des Tests anders verhält dank #define UNIT_TEST?

von Tom K. (ez81)


Lesenswert?

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.

von StinkyWinky (Gast)


Lesenswert?

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.

von Unke (Gast)


Lesenswert?

... 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...

von GoP (Gast)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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.

von Mark B. (markbrandis)


Lesenswert?

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? ;-)

von Karl H. (kbuchegg)


Lesenswert?

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.)

von Klaus B. (Gast)


Lesenswert?

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 !

von Mark B. (markbrandis)


Lesenswert?

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
Noch kein Account? Hier anmelden.