web-dev-qa-db-fra.com

Bons exemples de tests unitaires pour les développeurs C embarqués

Je vais donner une conférence à mon département la semaine prochaine sur les tests unitaires et le développement piloté par les tests. Dans le cadre de cela, je vais montrer des exemples concrets à partir d'un code que j'ai écrit récemment, mais je voudrais également montrer quelques exemples très simples que j'écrirai dans l'exposé.

J'ai cherché sur le Web de bons exemples, mais j'ai eu du mal à en trouver qui soient particulièrement applicables à notre domaine de développement. Presque tous les logiciels que nous écrivons sont des systèmes de contrôle profondément intégrés fonctionnant sur de petits microcontrôleurs. Il y a beaucoup de code C qui est facilement applicable aux tests unitaires (je parlerai de tests unitaires sur le PC plutôt que sur la cible elle-même) tant que vous restez à l'écart de la couche `` inférieure '': ce qui parle directement aux périphériques du microcontrôleur. Cependant, la plupart des exemples que j'ai trouvés ont tendance à être basés sur le traitement de chaînes (par exemple, l'excellent Dive Into Python chiffres romains) et comme nous n'utilisons presque jamais de chaînes, cela ne convient pas vraiment ( les seules fonctions de bibliothèque que notre code utilise généralement sont memcpy, memcmp et memset, donc quelque chose basé sur strcat ou des expressions régulières n'est pas tout à fait correct) .

Donc, passons à la question: s'il vous plaît, quelqu'un peut-il offrir de bons exemples de fonctions que je peux utiliser pour démontrer les tests unitaires dans une session en direct? Une bonne réponse à mon avis (sous réserve de modifications) serait probablement:

  • Une fonction suffisamment simple pour que tout le monde (même ceux qui n'écrivent du code qu'à l'occasion) puisse comprendre;
  • Une fonction qui ne semble pas inutile (c'est-à-dire que déterminer la parité ou le CRC est probablement mieux qu'une fonction qui multiplie deux nombres ensemble et ajoute une constante aléatoire);
  • Une fonction assez courte pour écrire devant une salle de personnes (je peux profiter des nombreux presse-papiers de Vim pour réduire les erreurs ...);
  • Une fonction qui prend des nombres, des tableaux, des pointeurs ou des structures comme paramètres et renvoie quelque chose de similaire, plutôt que de gérer des chaînes;
  • ne fonction qui a une simple erreur (par exemple > plutôt que >=) qui est facile à mettre en place et qui fonctionnerait toujours dans la plupart des cas, mais qui romprait avec certains cas Edge particuliers: facile à identifier et à corriger avec un test unitaire.

Des pensées?

Bien que ce ne soit probablement pas pertinent, les tests eux-mêmes seront probablement écrits en C++ à l'aide de Google Test Framework: tous nos en-têtes ont déjà le #ifdef __cplusplus extern "C" { envelopper autour d'eux; cela a bien fonctionné avec les tests que j'ai effectués jusqu'à présent.

21
DrAl

Voici une fonction simple qui est censée générer une somme de contrôle sur len octets.

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;
}

Il a un bogue fencepost: dans l'instruction for, le test doit être i < len.

Ce qui est amusant, c'est que si vous l'appliquez à une chaîne de texte comme celle-ci ...

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

vous obtiendrez la "bonne réponse"! C'est parce que l'octet supplémentaire qui a été additionné est le terminateur de chaîne zéro. Vous pouvez donc finir par mettre cette fonction de somme de contrôle dans le code, et peut-être même l'envoyer avec, et ne jamais remarquer de problème - c'est-à-dire jusqu'à ce que vous commenciez à l'appliquer à autre chose que des chaînes de texte.

Voici un test unitaire simple qui signalera ce bug (la plupart du temps ... :-)

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);
    }
}
15
Bob Murphy

Qu'en est-il de l'implémentation d'une fonction de tri comme tri à bulles ? Une fois que la fonction de tri fonctionne, vous pouvez continuer avec recherche binaire qui est tout aussi bon pour introduire les tests unitaires et TDD.

Le tri et la recherche dépendent de comparaisons qui sont faciles à se tromper. Cela implique également d'échanger des pointeurs autour desquels il faut procéder avec précaution. Les deux sont sujets aux erreurs, alors n'hésitez pas à gâcher :)

Quelques idées supplémentaires:

  • Les tests unitaires aident beaucoup lors de la refactorisation. Donc, une fois que votre tri à bulles fonctionne, vous pouvez le changer en un tri plus puissant comme qsort, et les tests devraient toujours réussir, prouvant que votre nouvelle fonction de tri fonctionne également.
  • Le tri est facile à tester, le résultat est trié ou non, ce qui en fait un bon candidat.
  • De même pour la recherche; elle existe ou elle n'existe pas.
  • L'écriture de tests pour le tri ouvre des discussions sur le type d'entrée à utiliser pour le test (zéro élément, entrée aléatoire, entrées en double, énormes tableaux, etc.).
2
Martin Wickman