Gute Unit-Test-Beispiele für Embedded C-Entwickler [geschlossen]

20

Ich werde nächste Woche in meiner Abteilung einen Vortrag über Unit-Tests und testgetriebene Entwicklung halten. Als Teil davon werde ich einige reale Beispiele aus einem Code zeigen, den ich kürzlich geschrieben habe, aber ich möchte auch einige sehr einfache Beispiele zeigen, die ich im Vortrag schreiben werde.

Ich habe im Internet nach guten Beispielen gesucht, aber ich habe mich bemüht, solche zu finden, die besonders für unseren Entwicklungsbereich geeignet sind. Fast die gesamte Software, die wir schreiben, besteht aus tief eingebetteten Steuerungssystemen, die auf kleinen Mikrocontrollern ausgeführt werden. Es gibt eine Menge C-Code, der sich leicht auf Unit-Tests anwenden lässt (ich spreche über Unit-Tests auf dem PC und nicht auf dem Ziel selbst), solange Sie sich von der „untersten“ Ebene fernhalten: dem Material, das direkt spricht zu den Mikrocontroller-Peripheriegeräten. Die meisten Beispiele, die ich gefunden habe, basieren jedoch auf der Verarbeitung von Zeichenfolgen (z. B. das hervorragende Beispiel für römische Dive Into Python-Zahlen), und da wir kaum Zeichenfolgen verwenden, ist dies nicht wirklich geeignet (was die einzigen Bibliotheksfunktionen betrifft, die in unserem Code normalerweise verwendet werden) sind memcpy, memcmpund memset,strcat oder reguläre Ausdrücke sind nicht ganz richtig).

Fahren Sie mit der Frage fort: Kann jemand einige gute Beispiele für Funktionen anbieten, mit denen ich Unit-Tests in einer Live-Sitzung demonstrieren kann? Eine gute Antwort nach meiner (vorbehaltlich Änderungen) Meinung wäre wahrscheinlich:

  • Eine Funktion, die einfach genug ist, dass jeder (auch diejenigen, die nur gelegentlich Code schreiben) verstehen kann;
  • Eine Funktion, die nicht sinnlos erscheint (dh das Ermitteln der Parität oder der CRC ist wahrscheinlich besser als eine Funktion, die zwei Zahlen miteinander multipliziert und eine Zufallskonstante hinzufügt);
  • Eine Funktion, die kurz genug ist, um vor einem Raum von Menschen zu schreiben (ich kann die vielen Zwischenablagen von Vim nutzen, um Fehler zu reduzieren ...);
  • Eine Funktion, die Zahlen, Arrays, Zeiger oder Strukturen als Parameter verwendet und etwas Ähnliches zurückgibt, anstatt Zeichenfolgen zu verarbeiten.
  • Eine Funktion mit einem einfachen Fehler (z. B. >nicht >=), der einfach einzufügen ist, funktioniert in den meisten Fällen noch, bricht jedoch mit einem bestimmten Randfall: Einfach zu identifizieren und mit einem Komponententest zu beheben.

Irgendwelche Gedanken?

Obwohl dies wahrscheinlich nicht relevant ist, werden die Tests selbst wahrscheinlich mit dem Google Test Framework in C ++ geschrieben: Alle unsere Header sind bereits mit dem #ifdef __cplusplus extern "C" {Wrapper versehen. Dies hat mit den Tests, die ich bisher durchgeführt habe, gut funktioniert.

DrAl
quelle
Wenn man das "Problem" hier als Präsentation zum Verkauf von TDD an das Management ansieht, scheint dies meiner Meinung nach dem gewünschten Format angemessen zu entsprechen. Das OP scheint nach bestehenden Lösungen für dieses Problem zu fragen.
Technophile

Antworten:

15

Hier ist eine einfache Funktion, die eine Prüfsumme über len Bytes erzeugen soll .

int checksum(void *p, int len)
{
    int accum = 0;
    unsigned char* pp = (unsigned char*)p;
    int i;
    for (i = 0; i <= len; i++)
    {
        accum += *pp++;
    }
    return accum;
}

Es hat einen Fencepost-Bug: In der for-Anweisung sollte der Test sein i < len.

Was Spaß macht, ist, wenn Sie es auf eine Textzeichenfolge wie diese anwenden ...

char *myString = "foo";
int testval = checksum(myString, strlen(myString));

Sie erhalten die "richtige Antwort"! Das liegt daran, dass das zusätzliche Byte, das mit einer Prüfsumme versehen wurde, das Null-Zeichenfolgen-Abschlusszeichen war. Auf diese Weise können Sie diese Prüfsummenfunktion in den Code einfügen und möglicherweise sogar mit diesem Code versenden, ohne ein Problem zu bemerken - das heißt, bis Sie damit beginnen, sie auf etwas anderes als Textzeichenfolgen anzuwenden.

Hier ist ein einfacher Unit-Test, der diesen Fehler aufzeigt (meistens ... :-)

void main()
{
    // Seed the random number generator
    srand(time(NULL));

    // Fill an array with junk bytes
    char buf[1024];
    int i;
    for (i = 0; i < 1024; i++)
    {
        buf[i] = (char)rand();
    }

    // Put the numbers 0-9 in the first ten bytes
    for (i = 0; i <= 9; i++)
    {
        buf[i] = i;
    }

    // Now, the unit test. The sum of 0 to 9 should
    // be 45. But if buf[10] isn't 0 - which it won't be,
    // 255/256 of the time - this will fail.
    int testval = checksum(buf, 10);
    if (testval == 45)
    {
        printf("Passed!\n");
    }
    else
    {
        printf("Failed! Expected 45, got %d\n", testval);
    }
}
Bob Murphy
quelle
Sehr gut! Dies ist genau die Antwort, auf die ich gehofft habe: Danke.
DrAl
Wenn Sie den Puffer erstellen, den Sie bereits in diesem Speicherbereich gespeichert haben, müssen Sie ihn dann wirklich mit Zufallszahlen initialisieren?
Snake Sanders
@ SnakeSanders Ich würde ja sagen, weil Sie Unit-Tests so deterministisch wie möglich sein möchten. Wenn der von Ihnen verwendete Compiler dort auf Ihrem Entwicklercomputer eine 0 und auf Ihrem Testcomputer eine 10 setzt, haben Sie eine schreckliche Zeit, den Fehler zu finden. Ich denke, dass es aus dem gleichen Grund eine schlechte Idee ist, es von der Zeit abhängig zu machen, anstatt von einem festen Startwert.
Andrew sagt Reinstate Monica
Sich in einem Unit-Test auf nicht deterministisches Verhalten zu verlassen, ist eine schlechte Idee. Ein schuppiger Test wird Ihnen früher oder später Kopfschmerzen
bereiten
2

Was ist mit der Implementierung einer Sortierfunktion wie Bubble Sort ? Sobald Sie die Sortierfunktion aktiviert haben, können Sie mit der binären Suche fortfahren, die für die Einführung von Unit-Tests und TDD ebenso gut geeignet ist.

Sortieren und Suchen hängen von Vergleichen ab, was leicht zu verwechseln ist. Es beinhaltet auch das Vertauschen von Zeigern, um die herum Vorsicht geboten ist. Beides ist fehleranfällig, also zögern Sie nicht, es durcheinander zu bringen :)

Noch ein paar Ideen:

  • Unit-Tests helfen beim Refactoring sehr. Sobald Ihre Blasensortierung funktioniert, können Sie sie in eine leistungsstärkere Sortierung umwandeln qsort. Die Tests sollten dennoch erfolgreich sein und beweisen, dass Ihre neue Sortierfunktion auch funktioniert.
  • Das Sortieren ist einfach zu testen, das Ergebnis ist entweder sortiert oder nicht, was es zu einem guten Kandidaten macht.
  • Das selbe für das Suchen; es existiert entweder oder es existiert nicht.
  • Das Schreiben von Tests zum Sortieren eröffnet Diskussionen darüber, welche Art von Eingabe für Tests verwendet werden soll (null Elemente, zufällige Eingabe, doppelte Einträge, große Arrays usw.).
Martin Wickman
quelle
Haben Sie konkrete Vorschläge für einen einfachen Fehler, die zeigen, wie das Testen das Leben erleichtert?
DrAl
@ DrAl: Aktualisiert meine Antwort damit.
Martin Wickman