Forum: PC-Programmierung poor mans unit tests


von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Hallöle,
ich suche gerade nach einer Möglichkeit eine Funktion vor main() 
implizit aufzurufen. In C++ kann man eine Variable mit einem 
Funktionsaufruf initialisieren. In C geht das wohl nicht!? (man 
korrigiere mich, wenn das doch geht).

Lösung (in C++):
1
// poor mans unit tests
2
#ifndef NDEBUG
3
4
static bool tests()
5
{
6
  static const uint8_t test_data[] = {
7
    0x3e, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09
8
  };
9
10
  const uint16_t crc = calc_e2e_crc( test_data, sizeof( test_data ) );
11
  assert( crc == 0x012f );
12
13
  return true;
14
}
15
16
bool e2e_crc_run_tests = tests();
17
#endif
18
19
Hat jemand eine Idee, wie ich ggf. ähnliches (Aufruf einer (lokalen) Funktion) in C (C99) hin bekomme?
20
21
mfg und schönen Dank,
22
Torsten

von Knut (Gast)


Lesenswert?

Hallo Torsten,

Welches Betriebssystem?
Welcher Prozessor?
Welches C?
Wozu das Ganze?

Mir hat man beigebracht, dass main() der Programmeinstiegspunkt sei. Hat 
sich das mittlerweile geändert?

Gruß Knut

von Tom (Gast)


Lesenswert?

Wie portabel muss es sein?

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Tom schrieb:
> Wie portabel muss es sein?

Hi Tom,
nicht portabel würde ich selbst irgend wie hingehackt bekommen. Ich 
könnte ja auch von main() aus solche Test-Funktionen aufrufen. Ich 
möchte die aber eigentlich gerne später wieder entfernen und dabei gerne 
maximal die betroffene Datei anpacken.

mfg Torsten

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Hallo Knut,

Knut schrieb:
> Welches Betriebssystem?

Kein Betriebssystem.

> Welcher Prozessor?

ARM Cortex-M3

> Welches C?

C99

> Wozu das Ganze?

Siehe Betreff.

> Mir hat man beigebracht, dass main() der Programmeinstiegspunkt sei. Hat
> sich das mittlerweile geändert?

War schon immer nur die halbe Wahrheit! ;-)

mfg Torsten

von Mikro 7. (mikro77)


Lesenswert?


von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

S. J. schrieb:
> Für gcc, also nicht portabel, gibt es sowas...
>
> 
http://www.geeksforgeeks.org/functions-that-are-executed-before-and-after-main-in-c/

Danke, wahrscheinlich bedeutet das aber auch, dass es mit C (so) nicht 
möglich ist.

mfg Torsten

von Schiko (Gast)


Lesenswert?

Was spricht dagegen eine c++ Datei (für Testzwecke) mitzulinken?

von Marc (Gast)


Lesenswert?

So ganz verstehe ich es nicht. Für meine Unit Tests habe ich nie um 
main() rum gearbeitet. Was ist so schlimm daran main nur als Einsprung 
zu sehen und dein eigentliches Programm main2 oder änlich zu nennen?

Das einzige Manko sind initialisierte Variablen, die vor dem Einsprung 
in main initialisiert werden, und nach dem Unit Test ggf Müll sind...
Was ist das eigentliche Ziel?

von Olaf D. (Firma: O.D.I.S.) (dreyero)


Lesenswert?

Hi,

ich weiß, es geht um einen ARM-Core.
Aber zumindest für den AVR mit GCC kann man die .init-Sections 
verwenden:
http://www.nongnu.org/avr-libc/user-manual/mem_sections.html

Etwas ähnliches sollte es auch für andere Compiler geben.

Gruß

Olaf

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Schiko schrieb:
> Was spricht dagegen eine c++ Datei (für Testzwecke) mitzulinken?

Ich muss im Großen und Ganzen C verwenden, weil die API, die ich 
verwenden muss Bugs enthält, die einen C++ compiler ausschließen. Aber 
Deine Idee ist voll gut: solange ich keinen API-Header einbinde, sollte 
zumindest so etwas funktionieren:
1
void test_crc();
2
void test_blablabla();
3
4
static bool run_tests()
5
{
6
  test_crc();
7
  test_blablabla();
8
}
9
10
static bool call_init = run_tests();

dann wäre das Ganze nicht mehr so ganz lokal, aber es wäre portable. Mal 
gucken, ob der startup code das ganze mitmacht.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Marc schrieb:
> So ganz verstehe ich es nicht. Für meine Unit Tests habe ich nie um
> main() rum gearbeitet. Was ist so schlimm daran main nur als Einsprung
> zu sehen und dein eigentliches Programm main2 oder änlich zu nennen?

Ich möchte eine Zeit lang einfache unit tests mit ins Projekt 
integrieren, ohne dafür eigene Projekte in meinen build zu integrieren. 
Wäre ich in der Wahl der Mittel frei, würde ich das Projekt so 
aufsetzen, dass ich unit tests in separaten executables ausführen kann.

Das Codebeispiel von oben, musst Du Dir am Ende der Datei vorstellen, 
die auch die zu testende Funktion (calc_e2e_crc) enthält. Wir mein 
Ansinnen so klarer?

mfg Torsten

von Knut (Gast)


Lesenswert?

Und was hat das jetzt mit PC-Programmierung zu tun?

Grüße Knut

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Knut schrieb:
> Und was hat das jetzt mit PC-Programmierung zu tun?

Tut mir leid lieber Knut, aber gibt hier halt kein C99 board. Ich hatte 
aber den Verdacht, dass hier viele C99-Experten unterwegs sind.

mfg Torsten

von Bernd K. (prof7bit)


Lesenswert?

Torsten R. schrieb:
> In C++ kann man eine Variable mit einem
> Funktionsaufruf initialisieren. In C geht das wohl nicht!?

Was hält Dich davon ab einfach eine Funktion zu schreiben die alle Tests 
aufruft und am Ende das Ergebnis irgendwo speichert? Die rufst Du dann 
von der main() aus auf. Ich sehe irgendwie Dein Problem nicht. Manchen 
Syntaxzucker von C++ gibts halt in C nicht und den muss man dann halt 
explizit hinschreiben, aber in Deinem Fall macht das ja so gut wie 
keinen Mehraufwand.

von Johnny B. (johnnyb)


Lesenswert?

Wenn Du vor dem main() was anderes aufrufen willst, dann geht das doch 
ganz einfach im startup code.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Hallo Bernd,

Bernd K. schrieb:
> Was hält Dich davon ab einfach eine Funktion zu schreiben die alle Tests
> aufruft und am Ende das Ergebnis irgendwo speichert? Die rufst Du dann
> von der main() aus auf. Ich sehe irgendwie Dein Problem nicht. Manchen
> Syntaxzucker von C++ gibts halt in C nicht...

danke, die Information, dass so etwas in C nicht geht reicht mir ja 
schon. Aber ohne zu fragen, hätte ich diese Erkenntnis nicht so leicht 
erhalten können.

Der "charm" der C++ Lösung ist, dass die nötigen Änderungen sehr lokal 
sind.

mfg Torsten

von Nestbeschmutzer (Gast)


Lesenswert?

Hallo Torsten,

ich hatte vor einiger Zeit eine ähnliche Frage gestellt und darauf auch 
kaum sinnvolle Antworten bekommen.
Manchen hier fällt es schwer sich Szenarien vorzustellen die neu für sie 
sind.

Mir ging es im Prinzip auch darum, meinen Code in einer Art Testumgebung 
einzubetten. Diese Umgebung musste vor main() initialisiert werden.
Außerdem war eine Anforderung, dass der eigentliche Code nicht verändert 
werden soll. Deswegen kam eine main2(), die in main() aufgerufen wird, 
nicht in Frage.
Per makefile konnte zwischen "richtigem" Build (ohne pre-main-Funktion) 
und Testbuild (mit pre-main-Funktion) umgeschalten werden.

Die Antwort, kurz und knapp:
Portabel geht nicht weil der Startup-Code, der die main() aufruft, libc- 
und OS-abhängig ist.

In meinem konkreten Fall (ATmega mit avr-libc) konnte ich die 
init-Sektionen benutzen, die Jörg Wunsch glücklicherweise in der 
avr-libc vorgesehen hat.
Per linker-attribute konnte ich meine pre-main-Funktion in eine 
init-Sektion meiner Wahl legen.

von DerLang (Gast)


Lesenswert?

Hallo,

mal ein Denkansatz, aber da man eh per Präprozessor Teile des Testcodes 
ausblendet, warum nicht in der main das gleiche tun (aber Vorsicht, bin 
kein Profi-C'ler, jedenfalls noch nicht):

void main(void) {
#ifndef NDEBUG
  initUnitTests();
#endif

// main code folgend

}

3 Zeilen mehr in der Main, aber ansonsten wie gehabt und es wird 
definitiv als erstes initialisiert.

Cheers,
Joerg

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Hallo Joerg,

DerLang schrieb:

> 3 Zeilen mehr in der Main, aber ansonsten wie gehabt und es wird
> definitiv als erstes initialisiert.

ja, das wäre dann die zu wählende Lösung. Allerdings bleibt es ja nicht 
bei den 3 Zeilen. Für jedes Modul, dass so einen Test enthält, braucht 
es einen globalen Namen und Prototypen für die Aufzurufenden Funktion. 
Es müssen also immer mindestens zwei Dateien angefasst werden.

Kind regards,
Torsten

von Knut (Gast)


Lesenswert?

Torsten R. schrieb:
> Knut schrieb:
>> Und was hat das jetzt mit PC-Programmierung zu tun?
>
> Tut mir leid lieber Knut, aber gibt hier halt kein C99 board. Ich hatte
> aber den Verdacht, dass hier viele C99-Experten unterwegs sind.

Na jetzt wird mir einiges klar! Ausserdem wird ja die Elektronik auch 
vom PC aus programmiert.

Eine Frage hätte ich noch:

Torsten R. schrieb:
> nicht portabel würde ich selbst irgend wie hingehackt bekommen. Ich
> könnte ja auch von main() aus solche Test-Funktionen aufrufen. Ich
> möchte die aber eigentlich gerne später wieder entfernen und dabei gerne
> maximal die betroffene Datei anpacken.

Da Du das Ganze sowieso in ein #define einpackst, ist es dann nicht 
egal, wo Dein Testcode steht - also ob in ein einer oder mehreren 
Dateien?

PS: Aber schicker ist es allemal, wenn man vor der main() noch eine 
andere Funktion aufruft.

Grüße Knut

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Knut schrieb:
> ist es dann nicht
> egal, wo Dein Testcode steht - also ob in ein einer oder mehreren
> Dateien?

Naja, so lokal wie möglich wäre schon schön...

mfg Torsten

von Marc (Gast)


Lesenswert?

Jetzt hab ich es verstanden.

Die GCC Variante von oben beschrieben ist ganz gut. Etwas fummelig mit 
den Prioritäten der Calls. Damit hast du aber zumindest über gcc einen 
großen Teil der Plattformen erschlagen.

Mein Vorschlag (ohne Einsatz von __attribute__((constructor))  )
Die Testcase Funktionen schreib ich meist in die C-Datei mit rein (für 
Zugriff auf modul-lokale Variablen). Die Declaration schreib ich nicht 
ins Header File (da drück ich ma beide Augen zu).

Beispiel:
1
#if UNIT_TEST
2
extern void testcase_crc()
3
{...}
4
#endif

Mittels Skript/grep suche ich mir alle "testcase_" fälle raus und 
generiere daraus eine testrun Datei:
1
// grep "testcase_" ....
2
extern void testcase_crc();
3
extern void testcase_xyz();
4
extern void testcase_abc();
5
6
extern void runAllTests() {
7
   testcase_crc();
8
   testcase_xyz();
9
   testcase_abc();
10
}

Vorteil: Man vergisst keinen testcase und kann die Reihenfolge im 
Testrun trotzdem noch manuel anpassen (wobei ein Unittest normalerweise 
so geschrieben sein sollte, dass er natürlich andere Unittests nicht 
beeinflusst... ergo die Reihenfolge egal sein sollte).

von Marc (Gast)


Lesenswert?

Ergänzung: die Anzahl der passed/failed Testcases zähle ich 
normalerweise über die Vergleichsfunktionen mit. Aber das ist abhängig 
davon, welche Unittest Lib genutzt wird. Deshalb liefern die testcase_ 
Funktionen keinen Rückgabewert.

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.