La présentation est en train de télécharger. S'il vous plaît, attendez

La présentation est en train de télécharger. S'il vous plaît, attendez

GL & POO Phases Phases de Programmation et de test Join Us Start Here

Présentations similaires


Présentation au sujet: "GL & POO Phases Phases de Programmation et de test Join Us Start Here"— Transcription de la présentation:

1 GL & POO Phases Phases de Programmation et de test Join Us Start Here
Test et maintenance Start Here LMD informatique – Université de Tébessa Phases Phases de Programmation et de test Join Us Derdour Makhlouf

2 Programmation

3 Définition L'activité de programmation est une activité de communication entre deux personnes : l'auteur et le lecteur. Pendant le développement d'un logiciel, le programmeur doit d'abord penser au lecteur du programme pour s'assurer que son programme sera lisible par ce lecteur. La programmation est l’activité qui transforme la conception détaillée vers une implémentation dans un langage de programmation.

4 But de la programmation
Objectif de la programmation en tant qu'activité? Pour le débutant, cette question semble un peu bizarre, car après tout, le but de la programmation est d'écrire un programme (une suite d'instructions) qui soit conforme à sa spécification, à savoir que le programme fait ce qu'il doit faire. Pourtant, la réponse n'est pas si simple. La programmation a plusieurs objectifs : le programme doit être correct (conforme à sa spécification), il doit être efficace (utiliser des algorithmes efficaces), il doit être maintenable et réutilisable, il doit être facile à utiliser (à ne pas confondre avec ``facile à comprendre''), il doit avoir un coût de création faible, il doit contenir le moins de défauts possibles (qualité élevée), il doit prendre peu de temps à écrire. Ces objectifs sont souvent contradictoires. Des méthodes pour déterminer un compromis (arrangement) raisonnable sont étudiées par la discipline de l'informatique nommée génie logiciel (en anglais : software engineering).

5 Déterminer une façon générale pour la programmation
Il semble difficile de déterminer si un programme est lisible quand on ne sait pas qui et quelles sont les qualifications de la personne qui va le lire. Le plus souvent on ne connait pas cette personne a priori. Au moment de la création initiale du logiciel, on n'a pas encore choisi la ou les personnes (s) qui maintiendront le programme. Pour compliquer encore la maintenance du logiciel, les créateurs initiaux du programme ont souvent changé de poste ou d'entreprise quand le logiciel arrive en phase de maintenance. Ceci est dû à la rotation habituelle du personnel dans le secteur du logiciel. Il est relativement rare qu'un développeur reste plus de quatre à cinq ans dans la même société. Dans ces conditions, comment déterminer des règles universelles pour une programmation lisible? La réponse s'appelle la culture partagée des développeurs. En fait, tout développeur doit avoir une formation de base commune. Cette formation est partiellement donnée sous la forme d'unités d'enseignement enseignées à l'université ou dans les écoles d'ingénieurs. Mais la formation universitaire n'est pas suffisante pour créer une culture partagée par les développeurs de logiciels.

6 Exemple  Pour le comprendre, faisons la comparaison entre un développeur et un artiste (un écrivain par exemple). Les deux sont en fait assez similaires. La formation d'un écrivain comprend la langue utilisée pour écrire les œuvres, différentes façons de développer une histoire et ses personnages, etc. Mais un livre écrit par un écrivain ayant seulement suivi ces formations serait probablement assez mauvais. La formation de l'écrivain est complétée par une lecture régulière de livres d'autres auteurs, souvent des dizaines de livres par an. Il en est de même pour un développeur de logiciels. Sa formation initiale comprend un ou plusieurs langages de programmation, la façon d'organiser les instructions pour obtenir des programmes efficaces (théorie algorithmique), ainsi que des règles de base nécessaires pour la maintenabilité et la réutilisabilité. Mais comme dans le cas d'un écrivain, un programme écrit par un développeur ayant seulement suivi ces formations serait probablement assez mauvais. La formation du développeur doit être complétée par la lecture régulière de programmes écrits par des développeurs experts, de préférence des dizaines par an, de préférence utilisant différents langages de programmation. De cette façon, il est possible de créer une culture partagée, une façon de s'exprimer commune à l'ensemble des développeurs. Une grande partie de cette culture sera constituée de ce que nous appelons idiomes de programmation. Pour expliquer ce qu'est un idiome de programmation, il est encore utile de regarder la comparaison avec les langues naturelles. La grammaire d'une langue comme le français, détermine (par définition) si une phrase est grammaticale ou non. Mais ce n'est pas parce qu'une phrase est grammaticale qu'elle est idiomatique, à savoir réellement utilisée par les locuteurs de cette langue. Il y a beaucoup moins de phrases utilisées que de phrases grammaticales possibles. De plus, les phrases utilisées dépendent fortement de la langue en question. Par exemple, pour dire ``j'ai faim'' en Anglais, on dit ``I am hungry''. Bien que la phrase ``I have hunger'' (la traduction immédiate de ``j'ai faim'') soit grammaticale, elle n'est pas idiomatique. C'est aussi le cas de la phrase ``je suis affamé'', qui n'exprime pas la même chose que la phrase ``I am hungry'', alors que c'est sa traduction littérale.

7 La façon de programmation
La programmation a ses idiomes, à savoir des phrases réellement utilisées par les développeurs. Ce n'est pas parce qu'une phrase (une instruction dans un langage de programmation) est grammaticale (correcte d'après les règles du langage) qu'elle est idiomatique. Pour produire un programme lisible, le programmeur doit donc éviter des instructions non idiomatiques. Exemple : pour exprimer une boucle qui sera exécutée N fois en langage C, on écrit toujours une phrase comme : for (i = 0; i < n; i++) ... Une faute fréquente par des débutants en langage C est de l'écrire comme ceci : for (i = 1; i <= n; i = i + 1) ... Le résultat est le même (sous certaines restrictions), mais la deuxième phrase n'est pas idiomatique. D'ailleurs, le lecteur expert de cette phrase constate immédiatement un ``accent étranger''; il soupçonne que le langage ``maternel'' de l'auteur est Pascal ou Fortran.

8 Règles de base de la programmation
Espacement uniforme Indentation uniforme et économique Par espacement on comprend le nombre de caractères d'espacement à mettre : avant ou après un opérateur comme ``+'' ou ``='', avant ou après une virgule dans une liste (d'arguments par exemple), avant ou après une parenthèse ouvrante ou fermante (EX: d'une liste d'arguments). Certains développeurs pensent que l'espacement est une question de goût personnel. En réalité, il s'agit d'un élément de la culture partagée des développeurs. Un développeur qui ne respecte pas l'espacement de la communauté ou qui écrit des programmes dont l'espacement n'est pas uniforme, se fait remarquer par ses collègues plus expérimentés comme un mauvais développeur. Par indentation il faut comprendre le nombre de caractères d'espacement au début de la ligne. Le développeur expérimenté utilise l'indentation pour communiquer au lecteur du programme la structure générale de celui-ci. Il est par exemple normal d'augmenter l'indentation de toutes les instructions du corps d'une boucle, afin de montrer clairement au lecteur que toutes ces instructions sont exécutées à chaque itération.

9 Choix des identificateurs
Commentaires Abstractions et duplications Portées des identificateurs Les identificateurs d'un programme sont les noms des variables, procédures et fonctions, types, etc. Ces identificateurs doivent être immédiatement compréhensibles par le lecteur. En mathématique (et en informatique théorique), il est courant d'utiliser des identificateurs d'une seule lettre comme Tous les langages de programmation offrent la possibilité d'insérer dans le texte du programme des messages destinés exclusivement au lecteur du programme (le compilateur ne les regarde même pas). Ce sont des commentaires. Il est courant de croire que la lisibilité d'un programme augmente avec le nombre de lignes de commentaires. Ceci est faux en général. Une partie importante de la lisibilité du code concerne l'abstraction. On peut définir l'abstraction comme étant le fait de donner un nom d'un morceau de programme afin d'éviter sa duplication dans le code. Par portée d'un identificateur, on entend la partie du programme pouvant faire référence à l'identificateur. Par exemple, un identificateur utilisé pour une variable locale d'une fonction a comme portée le corps de la fonction.

10 Test et maintenance Définition Objectif de test Type de test
Test descendant vs ascendant  Méthode de test Test statique Test dynamique

11 Une activité de validation est réalisée durant (ou après) la phase de programmation.
En informatique, un test (anglicisme) désigne une procédure de vérification partielle d'un système informatique : le but est de s'assurer que le système informatique réagit de la façon prévue par ses concepteurs. Valider et Vérifier : Détecter et corriger les erreurs au plus tôt, pour éviter leur propagation et amplification. Tester : Chercher à détecter les erreurs résiduelles, en faisant fonctionner le système. Exécuter le logiciel, pour trouver les erreurs qu’il contient. Validation = confirmation par examen et apport de preuves que les exigences requises pour l'usage prévu sont satisfaites Vérification = confirmation par examen et apport de preuves que les propriétés et caractéristiques attendues sont satisfaites Les tests sont le moyen de s’assurer que ce que l’on a programmé est correct, c’est-à-dire que, si l’on fournit au programme des données qui respectent son protocole d’appel, celui-ci restituera en sortie les résultats attendus, dans le temps imparti, en ne consommant que les ressources autorisées, ou bien diagnostiquera une erreur et donnera les raisons de l’erreur.

12 Objectifs Montrer la présence d’erreur dans un programme, on ne montre pas que le programme et correcte durant le test, on ne corrige pas les erreurs détectées. Faire en sorte que le Logiciel soit de qualité. « La qualité est l’ensemble des caractéristiques d’une entité qui lui confèrent l’aptitude à satisfaire des besoins, exprimés ou implicites » Point de vue Client : Il cherche à savoir s’il peut avoir confiance dans le produit qui lui est livré, déjà réalisé; Point de vue fabriquant : Il cherche à savoir s’il peut avoir confiance dans le produit en cours de fabrication et qui n’existe pas encore, achevé et à livrer mais non encore utilisé;

13 Type de test Test unitaire : tester les procédures et fonction.
Test de module : tester la coopération entre les procédures et fonction qui compose un certain module. Test des sous systèmes : tester les interfaces entre les différents modules. Test d’intégration : voir des erreurs en relation avec la définition des besoins. Ces premiers tests sont réalisés sur des données fictives. Test d’acceptation : tester le système avec des données réelles provenant de l’utilisateur.

14 Classification selon le niveau de détail
Il existe différentes façons de classer les tests informatiques. On peut classer ces façons selon trois axes : le niveau de détail (qui dépend essentiellement de où on en est dans le cycle de vie), le niveau d'accessibilité (boîte noire ou blanche) et la caractéristique (fonctionnelle ou performance etc.). Classification selon le niveau de détail  Tests unitaires : vérification des fonctions une par une, On teste un seul module, isolément. Validation du codage des modules. Le codage des pilotes et modules fictifs fait partie des «échafaudages logiciels» non livrés Les modules d’interface (avec l’utilisateur, l’environnement système, d’autres applications) sont difficiles à simuler => on ne les code pas de façon fictive. Tests d'intégration : vérification du bon enchaînement des fonctions et des programmes, Une fois que l’on est assuré que chaque module fonctionne bien, on s’assure qu’il en est de même de leur composition. Validation de la conception du logiciel. Réalisé incrémentalement : on ne teste qu’une seule intégration à la fois en se basant sur le graphe d’appel entre les modules tests de non régression : vérification qu'il n'y a pas eu de dégradation des fonctions par rapport à la version précédente, Après chaque correction, pour vérifier que l'on n'a pas introduit de nouveaux défauts.

15 Classification selon le niveau d'accessibilité :
boite noire : à partir d'entrée définie on vérifie que le résultat final convient, boite blanche : on a accès à l'état complet du système que l'on vérifie à chaque ligne. Classification selon la caractéristique : On ne peut pas être exhaustif, on se contentera de quelques exemples : tests de performance : vérification que les performances annoncées dans la spécification sont bien atteintes. test fonctionnel : vérification que les fonctions sont bien atteintes. test de robustesse : vérification de la robustesse du logiciel.

16 Test descendant vs ascendant
Comme pour le codage, on peut tester le système de manière descendante ou ascendante Test descendant : commencer par tester les composants de haut niveau pour ensuite descendre aux composant de bas niveau cette approche exige la création préalable de module provisoires, pour pouvoir tester de composants de haut niveau. Test ascendant : commencer par tester les composant de plus bas niveau, ensuite passer au composant de haut niveau. En réalité, les deux approches peuvent être combinées.

17 Exemple Stratégie descendante
On commence par les modules appelants, pour terminer par les feuilles tests unitaires de M1, M2, M3, M4 et M5 M1 + M2 M1 + M2 + M3 //on procède en largeur M1 + M2 + M3 + M4 M1 + M2 + M3 + M4 + M5 Stratégie ascendante On commence par les feuilles tests unitaires de M1, M2, M3, M4 et M5 M5 + M3 M5 + M3 + M4 M5 + M3 + M4 + M2 M5 + M3 + M4 + M2 + M1 Stratégie ascendante paresseuse Tests unitaires de M4 et M5 M5 + M3 // test simultané de M5 et de son intégration avec M3

18 Le processus de test Sélection
Déterminer le jeu de tests, ou jeu d’essais. Un essai fixe : la valeur des entrées, les conditions d’exécution (pour la détection des pannes opérationnelles). Les essais sont choisis en fonction de ce que l’on cherche à tester. Soumission Exécuter chacun des essais. Dépouillement Examiner le résultat de chacun des essais, et déterminer les pannes. Pannes d’exécution : facilement observable; Pannes fonctionnelles : comparer les résultats observés avec ceux attendus il faut un oracle, indiquant ce que doit être le résultat; Pannes opérationnelles : Observer le comportement interne du logiciel, Déterminer s’il est conforme au comportement attendu; Un même essai peut donner lieu à plusieurs pannes. Analyse Etudier chaque panne, identifier le (ou les) défaut qui en est la cause. Critère d’arrêt La mesure de la fiabilité obtenue, Le taux de couverture de test, L’urgence des délais ...

19 Méthode de test On à deux grandes stratégies pour tester un logiciel :
Le test statique : consiste à tester le logiciel sans avoir à l’exécuter sur des données. Le test dynamique : consiste à tester le logiciel en l’exécutant sur des échantillons des données.

20 Test statique Dans cette stratégie on s’intéresse à chercher des erreurs dans le programme en se basant sur le test du programme (ou sur une description de ce teste). On trouve ici deux méthodes : Lectures croisées et inspection La lecture croisée consiste révision collégiale des codes, Un programmeur peut lire le module réalisé par son collègue pour identifier des erreurs, cette lecture peut être réalisée durant même la phase de programmation. Inspection plus formelle que les lectures croisées, Elle consiste à définir un jury de test, Le jury de test se compose de Le programmeur. Le chef de la conception. Le chef de l’opération du test. Une personne responsable du test.

21 Analyse d’anomalie L’objectif de cette méthode est de détecter des anomalies possible (erreur de typage, erreur dans la manipulation des variables, …) L’analyse d’anomalie se bas une description du programme (un graphe de contrôle ou un flot de donnée). Chaque description permet de détecter des anomalies de certain type. Graphe de contrôle Un ensemble de nœud lié par des arcs, Il modélise le contrôle dans le programme. Comment construire un graphe de contrôle : Diviser le programme en un ensemble de blocs. Ces blocs représentent les portions maximums du programme qui s’exécutent séquentielle : du haut vers le bas. Nœud du graphe = {les blocs séquentiels identifiés} {les instructions de rupture de séquence (if then, if then else, whil do,…)}. Les arcs représentent le transfert de contrôle d’un bloc vers un autre. Un graphe de contrôle doit posséder un seul point d’entrée et un seul point de sortie.

22 Exemple Le graphe de contrôle Soit le code suivent :
Bloc 1 : Open file (f1); Read (x, f1); Read (y, f1); z = 0; Bloc 2 : While (x>=y) do Bloc 3 : { x = x-y; z = z+1; }; Bloc 4: Write (y, f2); Close file (f1); Close file (f2); Exemple Le graphe de contrôle 1 Soit le code suivent : 4 3 2 1 x>=y Open file (f1) Read (x, f1) Read (y, f1) z = 0; While x>= y do { x = x-y; z = z+1; }; Write (y, f2); Close file (f1); Close file (f2); Division en blocs Anomalie : chemins : 1,2,4 ou 1,2,3,4 : on utilise f2 dans bloc 4 avant de l’ouvrir. Anomalies détectables à l’aide d’un graphe de contrôle : Partie isolée du code (jamais exécutée). Mauvais enchaînement d’opération utilisée un fichier avant son ouverture, un fichier non fermé. Un code trop complexe (beaucoup de boucles imbriquées). Comment détecter des anomalies : Etiqueter par différentes informations, selon les anomalies qu’on cherche à détecter. on peut l’étiqueter par : des condition de branchement, les opérations d’ouverture d’utilisation et de fermeture de fichier, … Suivre tout les chemins d’exécutions possibles pour détecter les anomalies.

23 Exemple : soit le code suivant avec son flot de donnée.
Flot de données  C’est le graphe de contrôle étiqueté par des informations pertinentes à la manipulation des variables du programme (définition « par lecture ou affectation» et utilisation des variables). Bloc 1 : Open file (f1); Read (x, f1); Read (y, f1); z = 0; Bloc 2 : While (x>=y) do Bloc 3 : { x = x-y; z = z+1; }; Bloc 4: Write (y, f2); Close file (f1); Close file (f2); Exemple  : soit le code suivant avec son flot de donnée. 1 Déf x ; y, z Flot de Données 2 util x, y x>=y 3 4 util y Chemin : 1, 2,4 : z défini et jamais utilisé. Chemin : 1, 2, 3,4 : z redéfini et ensuite jamais utilisé.

24 Anomalies détectées par les flots de donnée :
Utiliser une variable sans l’avoir définir. Définir variable sans utiliser. Redéfinir une variable sans l’avoir utiliser au préalable. Comment détecter ces anomalies : Exploration des différents chemins d’exécution.

25 Test dynamique Consiste à tester le programme, en l’exécutant sur jeux sélectionnés de données. Ceci exige. Choisir les données du test. Exécuter le programme sur ces données choisies. Selon la manière avec laquelle on choisit les données de test, on rencontre deux types de test. Le test structural : les données seront choisies en basant sur la structure du test du programme. Le test fonctionnel : les données sont choisies en se basant sur une spécification du programme.

26 Test structurel (white box)
Principe : ce test est bas sur la structure du programme. Il utilise une description de cette structure pour choisir les données du test. La description utilisée peut être : un graphe de contrôle ou un flot de données. Comment choisir les données du test : Trouver tous les chemins d’exécution possible. Choisir des données qui déclenchent ces différents chemins. Exemple  : /*Scanf(“%d”, , &x) ;*/ If x>0 { y=x; … } Else {y=x-1; …} 1 2 3 x<0 x<=0 Chemins possible: 1,2 et les donnée qui déclenchent ce chemin c’est : x>0 Et 1,3 et les donnée qui déclenchent ce chemin c’est : x<=0 Remarque : quelques fois il est impossible de trouver tous les chemins possibles. Dans ce cas. On considère seulement couverture de ces chemins (certains chemins seulement).

27 Couvertures possibles : Une couverture possible est un sous ensemble de tous les chemins d’exécution possible. Selon la description utilisée (graphe de contrôle ou flot de données) on peut avoir différentes calasses de couvertures : Pour un graphe de contrôle : on peut avoir les couvertures suivantes : Couverture de toutes les instructions : chaque instruction doit être atteinte par au moins un chemin. Exemple : Pour le code : If x=0then Inst1; Inst2; Nous avons un seul chemin et qui couvre toutes les instructions. On s’intéresse seulement aux données qui déclenchent ce chemin donc x=0. Couverture de tous les arcs : chaque arc doit être utilisé par au moins un chemin. Couverture de tous les i- chemins : tous les chemins qui comprennent l’exécution de 0 à I fois, chaque boucle du programme. Couverture de tous les chemins : tous les chemins possibles.

28 Pour un flot de données : parmi les couvertures :
Couverture de toutes les définitions : chaque définition doit être exécutée par au moins un chemin. Ce dernier atteint ensuite une utilisation de cette variable. Couverture de toutes les utilisations : chaque définition doit être exécutée au moins une fois pour toute les utilisations qu’elle atteint ; de plus tous les arcs issus de ces utilisations doivent être couverts. Problème avec cette méthode : Dépendante du texte du programme : une modification du programme nécessite de refaire le test à 0. On ne détecte pas des erreurs d’incohérence avec la spécification. Celle-ci sera détectée par un test fonctionnel.

29 Test fonctionnel (black box)
Principe : Le choix des données du test est basé sur la spécification du programme et non le texte du programme est vue comme une boite noire. On regroupe les données d’entrée et de sortie en des classes d’équivalence. Le programme doit avoir le même comportement pour toutes les données d’une même classe. En général, nous avons deux familles de classe des données d’entrée : Classes des données provoquant des erreurs. Classe des données non provoquant des erreurs. Et nous avons deux familles de classe des données de sortie : Classes des données révélant la présence d’erreurs. Les autre classes des données. CDNPE CDPE CDNRE CDRE Pgm

30 Démarche : Identifier les différentes classes des données d’entrée. Choisir un représentant pour chaque classe. Identifier les différentes classes des données d’entrée qui génèrent des résultats dans les différentes classes des données de sortie. Effectuer le test avec les représentants choisis. Exemple : Soit la spécification suivante : Procédure recherche_dichotomique (x : integer ; T : tableau ; ind_max ; ind_min : integer ; trouve : in out boolean ; indice : in out integer) ; Remarque : Le langage utilisé pour l’implémentation du logiciel peut minimiser les situations à tester : Si un langage est fortement typé, nous n’avons pas de souci sur les types des données. Si des combinaisons (des séquences) de données de test provoquent des erreurs, cette méthode ne peut pas détecter de telles erreurs.

31 Les tests orientés objet
Les caractéristiques Premièrement vérifier l’exactitude et la cohérence des modèles OOA et OOD. Changement des stratégies de tests: Le concept d’unitaire s’élargit en fonction du concept d’encapsulation ; L’intégration porte surtout sur les classes et leur exécution sur un “thread” ou dans un scénario d’utilisation ; Les tests de validation utilisent les techniques “boîte noire” conventionnelles. Stratégies de tests OO Les tests de classe sont équivalents aux tests unitaires : Les méthodes de la classe sont testés ; L’état des objets (attributs) est examiné. L’intégration applique trois stratégies (sur un ensemble de classes) : “ Thread-based ” : répondre à une entrée ou un événement ; “Used-based” : une certaine fonctionnalité ; “Cluster” : démontrer la collaboration entre celle-ci.

32 Design de cas de test: Chaque cas est associé explicitement à la classe qui est testée et son objectif est donné. Une liste des étapes contenant les points suivants doit être développée pour chaque cas de test: États vérifiés des objets ; Liste des méthodes appelées ; Liste des exceptions possibles ; Environnement externe pour effectuer le test. Méthode OOT : aléatoire Identifier les opérations applicables à une classe. Définir les contraintes sur leur utilisation. Identifier une séquence minimale de tests : Séquence minimale d’opérations qui définit le cycle de vie minimal d’un objet de cette classe. Générer une variété de séquences de tests aléatoires mais valides. Méthode OOT : partition Réduire le nombre de cas requis pour tester la classe à l’aide de groupes d’équivalence. Les groupes sont formés selon : leur capacité à modifier l’état d’une classe ; les attributs qu’ils utilisent ; la fonctionnalité qu’ils effectuent. Méthode OOT : interclasses Pour chaque classe client, utiliser la liste des opérations pour générer des séquences aléatoires, (ces opérations envoient des messages aux classes serveur). Pour chaque message, déterminer la classe serveur. Pour chaque opération activée par le message reçu par la classe serveur, déterminer les messages transmis. Pour chacun des messages, déterminer le prochain niveau d’opérations appelé et l’inclure dans la séquence de tests.

33 Exemple Contraintes Le fichier doit être ouvert avant que toute opération puisse être effectuées. Le fichier doit être fermé après que les opérations sont complétées. Le fichier doit être initialisé avant toute opération de lecture ou d’écriture. Exemple : méthode aléatoire Séquence minimale Ouvrir – Initialiser – Lire – Écrire - Fermer Séquences possibles Ouvrir – Initialiser - [Lire | Écrire]n - Fermer Cas 1 : Ouvrir – Initialiser – Lire – Lire – Fermer Cas 2 : Ouvrir – Initialiser – Écrire – Lire – Écrire – Écrire - Fermer Exemple : partition Capacité à modifier l’état de la classe Écrire modifie l’état, Lire ne le modifie pas. Cas 2 : Ouvrir – Initialiser – Écrire – Écrire - Fermer Fichier Ouvrir Initialiser Lire Écrire Fermer

34 See you soon…


Télécharger ppt "GL & POO Phases Phases de Programmation et de test Join Us Start Here"

Présentations similaires


Annonces Google