--- title: Base VIII date: 2020-01-06 --- # Tests unitaires - Compilation `!=`{.C} bon fonctionnement! - Toujours tester vos programmes. - Tester les fonctionnalités une par une $\Rightarrow$ **tests unitaires**. - Plus le code est modulaire, plus il est simple à tester. - Plus le code est testé, moins il aura contiendra de bugs. *Testing shows the presence, not the absence of bugs.* E. W. Dijkstra. # Assertions (1/3) ```C #include <assert.h> void assert(int expression); ``` ## Qu'est-ce donc? - Macro permettant de tester une condition lors de l'exécution d'un programme: - Si `expression == 0`{.C} (condition fausse), `assert()`{.C} affiche un message d'erreur sur `stderr`{.C} et termine l'exécution du programme. - Sinon l'exécution se poursuit normalement. ## À quoi ça sert? - Permet de réaliser des tests unitaires. - Permet de tester des conditions catastrophiques d'un programme. - **Ne permet pas** de gérer les erreurs. # Assertions (2/3) \footnotesize ## Exemple :::::::::::::: {.columns} ::: {.column width="52%"} ```C #include <assert.h> #include <stdlib.h> void copy(int *src, int *dst, int n) { // identique à assert(src != NULL) assert(src); assert(dst); assert(n >= 0); for (int i = 0; i < n; ++i) { dst[i] = src[i]; } } void fill(int *dst, int n, int val) { assert(dst && "problem with allocated mem"); assert(n >= 0 && "n is the size of dst"); for (int i = 0; i < n; ++i) { dst[i] = val; } } ``` ::: ::: {.column width="48%"} ```C int main(int argc, char **argv) { int size = 10; int *src = malloc(size * sizeof(int)); fill(src, size, 0); int *dst = malloc(size * sizeof(int)); copy(src, dst, size); return EXIT_SUCCESS; } ``` ::: :::::::::::::: # Assertions (3/3) ## Cas typiques d'utilisation - Vérification de la validité des pointeurs (typiquement `!= NULL`{.C}). - Vérification du domaine des indices (dépassement de tableau). ## Bug vs erreur de *runtime* - Les assertions sont là pour détecter les bugs (erreurs d'implémentation). - Les assertions ne sont pas là pour gérer les problèmes externes au programme (allocation mémoire qui échoue, mauvais paramètre d'entrée passé par l'utilisateur, ...). # Généricité en C ## Problématique * En C on doit écrire chaque algorithme/structures de données pour des types précis (arbres, tri, ...). * Duplication du code pour chaque type possible et imaginable. ## Solution: `void *`{.C} * En général, un pointeur connaît son **adresse** et le **type** des données sur lesquelles il pointe. ```C int *a = malloc(sizeof(*a)); int *b = malloc(sizeof(int)); ``` * Un `void *`{.C} le connaît **que** son adresse, au programmeur de pas faire n'importe quoi. * Vous avez déjà utilisé des fonctions utilisant des `void *`{.C} ```C void *malloc(size_t size); void free(void *); ``` # Attention danger * Ne permet pas au compilateur de vérifier les types. * Les données pointées n'ayant pas de type, il faut déréférencer avec précaution: ```C int a = 2; void *b = &a; //jusqu'ici tout va bien double c = *b; // argl! ``` * À la programmeuse de faire attention à ce qu'elle fait. # Cas d'utilisation (1/3) ## Que fait cette fonction? \footnotesize ```C void *foo(void *tab, int n_items, int s_items, bool (*bar)(void *, void *)) { if (n_items <= 0 || s_items <= 0 || NULL == tab) { return NULL; } void *elem = tab; for (int i = 1; i < n_items; ++i) { // void pointer arithmetics is illegal in C // (gcc is ok though) void *tmp_elem = (void *)((char *)tab + i*s_items); if (bar(elem, tmp_elem)) { elem = tmp_elem; } } return elem; } ``` # Cas d'utilisation (2/3) ## Avec un tableau de `int`{.C} ```C bool cmp_int(void *a, void *b) { return (*(int *)a < *(int *)b); } int main() { int tab[] = {-1, 2, 10, 3, 8}; int *a = foo(tab, 5, sizeof(int), cmp_int); printf("a = %d\n", *a); } ``` # Cas d'utilisation (3/3) ## Avec un tableau de `double`{.C} ```C bool cmp_dbl(void *a, void *b) { return (*(double *)a < *(double *)b); } int main() { double tab[] = {-1.2, 2.1, 10.5, 3.6, 18.1}; double *a = foo(tab, 5, sizeof(double), cmp_dbl); printf("a = %f\n", *a); } ```