Télécharger la présentation
La présentation est en train de télécharger. S'il vous plaît, attendez
Publié parCerise Hoarau Modifié depuis plus de 10 années
1
Test et débogage Tests unitaires. Gestion d’erreurs. Notion d’état, de pré-condition et de post-condition. Assertion. Traces de programme. Débogueur et ses limites. Stratégies de mise au point du programme. Normes de programmation.
2
La gestion des erreurs Plusieurs problèmes peuvent survenir pendant l’exécution d’un programme: - insuffisance de mémoire, disque plein, - perte d’un fichier, imprimante non branchée ou à court de papier, - saisie non valide d’une valeur, - une fonction qui fonctionne mal, - etc. Rôle des programmeurs face à ces erreurs d’exécution: - prévoir ces erreurs, - une application étant construite à partir d’un enchaînement d’appels de fonctions, toute fonction pouvant être confrontée à une erreur d’exécution, il devient nécessaire de pouvoir gérer les appels (les erreurs) en cascade de fonctions, - en informer l’utilisateur, sauver son travail et arrêter le programme de façon contrôlée, - éventuellement, de mettre en œuvre des solutions de reprises et de correction.
3
Mise en œuvre de la gestion des erreurs
PROBLÈME RENCONTRÉ : le code qui détecte l’erreur ne peut rien faire pour sauver le travail de l’usager et quitter élégamment. Supposer que les erreurs ne se produiront pas est une mauvaise approche. Utiliser une variable globale qui sera initialisée par la fonction provoquant l’erreur d’exécution. Le contenu de cette variable sera ensuite récupéré et traité par la fonction appelante de celle qui a entraîné l’erreur d’exécution. Les variables globales n’offrent aucune garantie de sécurité. Cela signifie que l’on peut y accéder de n’importe où sans restriction. **** à éviter **** Ignorer l’erreur Programmation défensive Peut masquer des erreurs importantes. Prendre la décision de ne rien faire en cas de demande erronée. Ex. : Ne pas insérer un nouvel élément dans une pile pleine. Dans certains cas, on ne peut ignorer l’erreur parce que l’opération ne peut être complétée. Ex. : lors d’un calcul.
4
Mise en œuvre de la gestion des erreurs
Imprimer un message d’erreur et/ou arrêter le programme Le code de traitement des erreurs est dispersé et imbriqué tout au long du code du système. Les erreurs sont gérées là où ces erreurs sont le plus susceptibles de se produire. Avantage: En lisant le code, on peut voir le traitement d’erreur dans le voisinage immédiat du code et déterminer si une vérification adéquate des erreurs a été mise en place. Désavantage: Le code est « pollué » par le traitement d’erreurs. Cela rend le code plus difficile à comprendre et à maintenir. Envisageable au cours de la phase de mise au point seulement. **** à éviter ****
5
Mise en œuvre de la gestion des erreurs
Syntaxe : assert(expression logique); Donne lieu à aucune action si l’expression logique est vraie. Autrement, le programme termine anormalement en affichant un diagnostic. Note : Cette clause fait partie de la librairie C : assert.h #include <stdio.h> #include <assert.h> int sommation(int n) { assert(n > 0); return n * (n + 1) / 2; } void main() int N; printf("Entrez un nombre entier positif : "); scanf("%i", &N); printf("\nSommation : %i", sommation(N)); -5 Assertion failed: n > 0, file e:\nouveau_dossier\chiffres_romains.c, line 6
6
Mise en œuvre de la gestion des erreurs
Abandonner Recommencer Ignorer Le programme prend fin Le programme prend fin avec un message qu’il termine anormalement. Envoyer le rapport d’erreurs Débogage Ne pas envoyer Mettre fin à l’exécution est souvent une méthode trop radicale pour répondre à une erreur. De nombreuses erreurs peuvent être gérées si elles sont signalées de façon à ce qu’un programmeur puisse les détecter et les analyser.
7
Mise en œuvre de la gestion des erreurs
En C++, on utilise la librairie cassert. #include <iostream.h> #include <cassert> int sommation(int n) { assert(n > 0); return n * (n + 1) / 2; } void main() int N; cout << "Entrez un nombre entier positif : "; cin >> N; cout << "\nSommation : " << sommation(N); cout << "\nFIN\n";
8
Mise en œuvre de la gestion des erreurs
Approche classique d’interception d’erreurs: retour de code d’erreur - des fonctions symbolisent les traitements à mettre en œuvre; - ces fonctions renvoient des valeurs qui peuvent servir à déterminer le succès ou l’échec du traitement; - on peut tester les valeurs de retour des fonctions et réagir en conséquence aux erreurs d’exécution.
9
Utilisation des valeurs de retour
#include <iostream.h> int Fonction_1(………….) { ……... // Retourne 0 si aucune erreur n’est détectée; // un entier positif autrement. …….. } ………... int Fonction_m(………….)
10
Utilisation des valeurs de retour
int main() { ………. // Appel de la fonction Fonction_i. // Si une erreur est détectée, alors gérer cette erreur en lien avec la fonction Fonction_i sinon appel de la fonction Fonction_j si une erreur est détectée, alors gérer cette erreur en lien avec Fonction_j sinon ……….. } Cela met en évidence l’architecture nécessaire à des appels de fonctions en cascade qui peuvent entraîner une erreur d’exécution. Architecture peu lisible et surtout très pénible à maintenir (if-else imbriqué qui alourdit l’écriture et la mise à jour du programme).
11
Utilisation des valeurs de retour
#include <iostream.h> enum Nom_des_fonctions {Fn_A, Fn_B}; int Fonction_A(float v) { if (v < 0.0) return 1; if (v == 0.0) return 2; // Traitement. return 0; } int Fonction_B(char c) { if (c == ' ') return 1; if ((c < 'A') | (c > 'Z')) return 3; // Traitement. return 0; }
12
Utilisation des valeurs de retour
int Gestion_des_erreurs(Nom_des_fonctions Nom, int code_erreur) { if (Nom== Fn_A) switch(code_erreur) case 0 : cout << "Parfait"; return 0; case 1 : cout<<"Erreur 1"; return 1; case 2 : cout << "Erreur 2"; return 0; default: cout << "Erreur"; return 0; } else if (Nom== Fn_B) case 3 : cout <<"Erreur 3"; return 1; // Erreur grave else cout << "incomplet "; return 0;
13
Utilisation des valeurs de retour
int main() { char C = ' '; float U = -3.2f; if (Gestion_des_erreurs(Fn_A, Fonction_A(U))) return 1; if (Gestion_des_erreurs(Fn_B, Fonction_B(C))) return 1; return 0; }
14
Mécanisme des exceptions
Solution fiable et standardisée de gestion d’erreurs d’exécution qui ne se fonde pas sur les valeurs de retour des fonctions. permet de capturer toutes les exceptions d’un type donné, permet de lever des exceptions et transférer le contrôle avec les informations pertinentes à une autre partie du code qui pourra gérer la situation correcte- ment, en retirant le code de traitement des erreurs du « flot principal » d’exécution d’un programme, cela permet d’améliorer la lisibilité des programmes et faciliter leur maintenance. permet de gérer nativement les appels de fonctions en cascade.
15
Test unitaire d’une fonction
Au lieu d’assembler chaque composante d’un programme en espérant que cela fonctionne, il est préférable de les tester individuellement. Le test unitaire (ou test de la boîte noire) est un programme avec un résultat booléen qui ne se préoccupe pas du fonctionnement interne de la fonction. Voici une fonction qui calcule la racine carrée d’un nombre entier positif ou nul avec une précision EPSILON : Exemple : #include <iostream.h> const float EPSILON = f; // Calcule la racine carrée de la valeur réelle a. // // Paramètre d 'entrée : // a : une valeur réelle // Valeur de renvoi : // la racine carrée de a.
16
Test unitaire d’une fonction
float RacineCarree(float a) { if (a < 0.0f) throw(1); // Lancement d’une exception. if (a == 0.0f) return 0.0f; float xnouveau = a; float xvieux; do xvieux = xnouveau; xnouveau = (xvieux + a / xvieux) / 2.0f; } while ( (xnouveau - xvieux) >= EPSILON || (xnouveau - xvieux) <= - EPSILON ); return xnouveau;
17
Test unitaire d’une fonction
void main() { float x, y, z; // Test positif où RacineCarree doit fonctionner correctement. x = 2.0f; y = RacineCarree(x); z = y * y; if ((z - x) <= EPSILON && (z - x) >= - EPSILON) cout << "succes\n"; else cout << "echec\n"; // Test frontière où RacineCarree doit fonctionner correctement. x = 0.0f; if ((z - x) <= EPSILON && (z - x) >= -EPSILON) cout << "succes\n"; x = EPSILON;
18
Test unitaire d’une fonction
// Test négatif où la fonction RacineCarree doit échouer. try { x = -1.0f; y = RacineCarree(x); cout << "echec\n"; } catch(int i) // Prise en compte d’une exception. cout << i << " : succes\n";
19
Test unitaire d’une fonction
#include <iostream.h> #include <string.h> /* Le président d'un syndicat local possède l'information suivante sur ses membres (dont le nombre ne dépassera jamais 100) : - le numéro d'employé (lequel est une identification unique de l'employé correspondant à un nombre de 4 chiffres dont le 1er est 6), - son nom, - son prénom, - son adresse, - son traitement annuel - sa cotisation syndicale annuelle (entre 0 et 2%). */ struct employe { int numero_employe; char nom[20+1], prenom[20+1], adresse[40+1]; float traitement_annuel; float cotisation_annuelle; } ensemble_membres[100]; int Nombre_de_membres; /* Renferme le nombre de syndiqués.*/
20
Test unitaire d’une fonction
void syndicat() // Permet de créer un syndicat avec aucun membre. { Nombre_de_membres = 0; } int Nombre_de_membres_du_syndicat() /* Permet de connaître le nombre de membres faisant partie du syndicat. Valeur de renvoi : le nombre de membres du syndicat. */ return Nombre_de_membres;
21
Test unitaire d’une fonction
float Calcule_cotisation_annuelle_moyenne() /* Fournit la moyenne des cotisations annuelles des membres. Le syndicat possède au moins un membre. Valeur de renvoi : la moyenne des cotisations annuelles. */ { float somme = 0.0f; if (Nombre_de_membres == 0) throw(3); for (int i = 0; i < Nombre_de_membres; i++) somme += ensemble_membres[i].cotisation_annuelle; return somme / Nombre_de_membres; }
22
Test unitaire d’une fonction
void Inserer_nouveau_membre( int numero_employe, char * nom, char * prenom, char * adresse, float traitement_annuel, float cotisation_annuelle) /* Permet d'ajouter un nouveau membre syndiqué dont les caractéristiques sont passées en paramètres. Le nombre d'employés est moindre que 100. L'employé ayant ce numéro n'est pas syndiqué jusqu'à maintenant. Paramètres d'entrée : numero_employe : le no. de l'employé entre 6000 et 6999, nom : le nom de l'employé, prenom : le prénom de l'employé, adresse : son adresse, traitement_annuel : le traitement annuel de l'employé, cotisation_annuelle : la cotisation annuelle entre 0 et */
23
Test unitaire d’une fonction
{ if (Nombre_de_membres == 100) throw(4); for (int i = 0; i < Nombre_de_membres; i++) if (ensemble_membres[i].numero_employe == numero_employe) throw(4); if ((numero_employe < 6000) | (numero_employe > 6999)) throw(4); if((cotisation_annuelle < 0) | (cotisation_annuelle > 2)) throw(4); ensemble_membres[Nombre_de_membres].numero_employe = numero_employe; strcpy(ensemble_membres[Nombre_de_membres].nom, nom); strcpy(ensemble_membres[Nombre_de_membres].prenom, prenom); strcpy(ensemble_membres[Nombre_de_membres].adresse, adresse); ensemble_membres[Nombre_de_membres].traitement_annuel = traitement_annuel; ensemble_membres[Nombre_de_membres].cotisation_annuelle = cotisation_annuelle; Nombre_de_membres += 1; }
24
Test unitaire d’une fonction
void main() { // La fonction "syndicat". // Test positif. syndicat(); if (Nombre_de_membres_du_syndicat() == 0) cout << "Reussite.\n"; else cout << "echec\n"; // La fonction "Nombre_de_membres_du_syndicat". Inserer_nouveau_membre(6234, "Rioux", "Luc", "3456 rue Lavoie", 65000,1.50); Inserer_nouveau_membre(6500, "Roy", "Peter", "3429 rue du Lac", 35050,1.75); if (Nombre_de_membres_du_syndicat() == 2) cout << "Reussite.\n";
25
Test unitaire d’une fonction
// La fonction "Calcule_cotisation_annuelle_moyenne". // Test positif. if (Calcule_cotisation_annuelle_moyenne() == 1.625f) cout << "Reussite.\n"; else cout << "echec\n"; // Test négatif try { syndicat(); cout << Calcule_cotisation_annuelle_moyenne(); cout << "echec\n"; } catch(int i) cout << i << " : succes\n";
26
Test unitaire d’une fonction
// La fonction "Inserer_nouveau_membre". // Test négatif. try { Inserer_nouveau_membre(5234, "Roy", "Luc", "3456 Lavoie", 65000, 1.50); cout << "echec\n"; } catch(int i) cout << i << " : succes\n"; Inserer_nouveau_membre(6234, "Roy", "Luc", "3456 Lavoie", 65000, 2.50);
27
Test unitaire d’une fonction
try { Inserer_nouveau_membre(6234, "Roy", "Luc", "3456 Lavoie", 65000, 1.50); cout << "echec\n"; } catch(int i) cout << i << " : succes\n";
28
Test de la boîte blanche
Il est impossible de garantir avec certitude qu’un programme fonctionne correctement dans tous les cas possibles. Le test unitaire comprend un nombre fini de tests. Tout test ne peut prouver que la présence de bogues, non leur absence. Dijkstra Il s’agit ici de considérer le fonctionnement interne de la fonction en effectuant une trace de la fonction. La trace peut se faire via le débogueur ou à l’aide d’opérations de sortie.
29
Test de la boîte blanche
float RacineCarree(float a) { if (a < 0.0f) throw(1); // Lancement d’une exception. if (a == 0.0f) return 0.0f; float xnouveau = a; float xvieux; do xvieux = xnouveau; xnouveau = (xvieux + a / xvieux) / 2.0f; cout << "xvieux = " << xvieux << " xnouveau = " << xnouveau << endl; } while ( (xnouveau - xvieux) >= EPSILON || (xnouveau - xvieux) <= - EPSILON ); return xnouveau;
Présentations similaires
© 2024 SlidePlayer.fr Inc.
All rights reserved.