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

20

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 j'aimerais é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 s'appliquent particulièrement à 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 exemple de chiffres romains Dive Into Python) et comme nous n'utilisons presque jamais de chaînes, cela ne convient pas vraiment (à propos des seules fonctions de bibliothèque que notre code utilise généralement sont memcpy, memcmpet memset,strcat ou les expressions régulières ne sont pas tout à fait correctes).

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;
  • Une fonction qui a une erreur simple (par exemple >plutôt que >=) qui est facile à insérer et qui fonctionnerait toujours dans la plupart des cas, mais qui romprait avec un cas particulier: 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" {wrapper autour d'eux; cela a bien fonctionné avec les tests que j'ai effectués jusqu'à présent.

DrAl
la source
Prenant le "problème" ici comme une présentation pour vendre TDD à la direction, cela me semble convenir assez bien au format souhaité. L'OP semble demander des solutions existantes à ce problème.
Technophile

Réponses:

15

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 devrait ê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);
    }
}
Bob Murphy
la source
Très bien! C'est exactement le genre de réponse que j'espérais: merci.
DrAl
Lorsque vous créez le tampon que vous avez déjà dans cette mémoire, est-il vraiment nécessaire de l'initialiser avec des nombres aléatoires?
Snake Sanders
@SnakeSanders Je dirais que oui, parce que vous voulez que les tests unitaires soient aussi déterministes que possible. Si le compilateur que vous utilisez arrive à y mettre un 0 sur votre machine de développement et un 10 sur votre machine de test, vous aurez du mal à trouver le bogue. Je pense que le faire dépendre du temps au lieu d'une graine fixe est une mauvaise idée, pour la même raison.
Andrew dit Réintégrer Monica le
Se fier à des comportements non déterministes dans un test unitaire est une mauvaise idée. Un test floconneux vous donnera tôt ou tard des maux de tête ...
sigy
2

Qu'en est-il de l'implémentation d'une fonction de tri comme le tri à bulles ? Une fois que la fonction de tri fonctionne, vous pouvez continuer avec la recherche binaire qui est tout aussi bonne 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 faire attention. 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 passer, 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 comme le type d'entrée à utiliser pour le test (zéro élément, entrée aléatoire, entrées en double, énormes tableaux, etc.).
Martin Wickman
la source
Avez-vous des suggestions spécifiques pour une erreur simple qui montrerait comment les tests facilitent la vie?
DrAl
@DrAl: J'ai mis à jour ma réponse avec ça.
Martin Wickman