Examen GL1
Questions de réflexion Question 1 - Objectif et arrêt des tests L'objectif principal des tests de programmes est de détecter le maximum d'erreurs (ou "bugs") avant la mise en production du logiciel, afin de garantir qu'il répond correctement aux exigences fonctionnelles et non-fonctionnelles définies dans le cahier des charges.
D'après le document Examen GL1
Cet article a été rédigé automatiquement à partir du document source, puis vérifié avant publication.

Document source
Programming and Software Engineering · PDF · 3 pages · 2014
Afficher l'aperçu du document
Questions de réflexion
Question 1 - Objectif et arrêt des tests
L'objectif principal des tests de programmes est de détecter le maximum d'erreurs (ou "bugs") avant la mise en production du logiciel, afin de garantir qu'il répond correctement aux exigences fonctionnelles et non-fonctionnelles définies dans le cahier des charges. Tester permet de vérifier la fiabilité, la sécurité et les performances de l'application.
Il est théoriquement impossible de prouver l'absence totale d'erreurs (sauf pour des programmes triviaux). L'arrêt des tests est donc une décision pragmatique basée sur des critères prédéfinis :
- Critères de couverture : Le pourcentage de code, de branches ou de chemins testés a atteint le seuil exigé (par exemple, 100% des instructions couvertes).
- Critères de qualité : Le taux de découverte de nouvelles anomalies diminue drastiquement et aucun bug critique ou bloquant n'est ouvert.
- Critères de gestion : Le temps, le budget ou les ressources alloués à la phase de test sont épuisés (bien que ce soit le moins idéal d'un point de vue qualité).
Question 2 - Définition et objectif de la décomposition modulaire
Un module logiciel est un composant logique et indépendant d'un programme, qui regroupe un ensemble de données et d'instructions visant à accomplir une tâche spécifique. Il possède une interface bien définie qui lui permet de communiquer avec d'autres modules tout en masquant les détails de son implémentation interne (encapsulation).
L'objectif de la décomposition modulaire est de maîtriser la complexité d'un grand système logiciel en le divisant en sous-systèmes plus petits et gérables (principe de "diviser pour régner"). Cela permet :
- De faciliter la compréhension du système.
- De permettre le travail en équipe (développement parallèle).
- De faciliter la maintenance et la localisation des erreurs.
- De favoriser la réutilisation du code.
Question 3 - Critères de décomposition et décision d'arrêt
Les critères fondamentaux à respecter dans la décomposition modulaire sont :
- La haute cohésion : Chaque module doit avoir une responsabilité unique et bien ciblée. Tous les éléments d'un module doivent concourir à accomplir cette tâche unique.
- Le faible couplage : Les interdépendances entre les modules doivent être réduites au minimum. Un module doit pouvoir fonctionner ou être modifié avec un minimum d'impact sur les autres.
- Le masquage d'information : Un module doit cacher ses détails internes et n'exposer que ce qui est strictement nécessaire via son interface.
Décision de l'arrêt de la décomposition : On arrête de décomposer lorsqu'un module atteint un niveau où il réalise une tâche conceptuelle unique et atomique, et que sa complexité est facilement compréhensible par un seul développeur. Poursuivre la décomposition au-delà de ce point réduirait la cohésion et augmenterait inutilement le couplage et le nombre d'interfaces à gérer.
Question 4 - Architecture MVC et 3-tiers
- L'architecture 3-tiers est un style d'architecture logicielle de déploiement et de structuration globale qui divise le système en trois couches logiques et souvent physiques : la couche présentation (interface utilisateur), la couche de logique métier (règles de l'application), et la couche d'accès aux données (base de données).
- Le MVC (Modèle-Vue-Contrôleur) est un patron de conception (design pattern) principalement utilisé pour organiser l'interface utilisateur. Il sépare la représentation de l'information (Vue), la gestion des données et de la logique associée (Modèle), et le routage des actions de l'utilisateur (Contrôleur).
Choix et combinaison : On choisit le MVC lorsqu'on développe une application nécessitant une interface utilisateur riche et interactive, pour séparer clairement l'affichage de la logique de contrôle. On choisit le 3-tiers lorsqu'on développe une application d'entreprise nécessitant de la sécurité, de la montée en charge (scalabilité) et le partage de données entre de multiples clients. Exemple : Les deux sont souvent utilisés ensemble. Une application de commerce en ligne peut utiliser une architecture 3-tiers où le serveur web (couche de présentation) utilise le patron MVC (comme Spring MVC ou Ruby on Rails) pour générer les pages web, qui communique ensuite avec un serveur d'application (logique métier) et un serveur de base de données (données).
Question 5 - Cohésion et emplacement des méthodes
Une méthode doit traiter une tâche unique liée à l'entité que représente la classe. Si une tâche concerne conceptuellement l'objet mais relève d'une responsabilité technique différente (comme le stockage, l'affichage réseau ou la sérialisation), elle ne doit pas se trouver dans la classe de l'entité.
Exemple : Prenons une classe Etudiant avec les attributs nom et matricule. La méthode pour sauvegarder cet étudiant dans une base de données relationnelle (sauvegarderEnBase()) concerne directement l'étudiant, mais elle implique de la logique SQL et de connexion réseau. Pour respecter la cohésion, on ne la met pas dans la classe Etudiant (qui ne doit gérer que la logique métier de l'étudiant). On crée une classe séparée dédiée à la persistance, par exemple EtudiantDAO (Data Access Object), qui contiendra la méthode sauvegarder(Etudiant e).
Exercice 1 - Modélisation de l'application de réservation
Question 1a - Diagramme de contexte
Puisqu'il n'est pas possible de dessiner graphiquement ici, voici la spécification textuelle exacte du diagramme de contexte selon le formalisme des Diagrammes de Flots de Données (DFD).
- Processus central (Niveau 0) : Système de réservation de vols (Bulle centrale).
- Entités externes (Terminaux) :
- Utilisateur (Internet ou Agence)
- Gérant
- Flots de données entrants (vers le système) :
- Depuis Utilisateur : Demande de réservation, Demande d'annulation.
- Depuis Gérant : Informations du nouveau vol, Demande de suppression de vol, Nouveaux tarifs.
- Flots de données sortants (depuis le système) :
- Vers Utilisateur : Confirmation de réservation, État de la réservation (annulée, confirmée, date limite), Prix de la place calculé.
Question 1b - Diagramme raffiné (Gestion des réservations)
Pour raffiner le processus, nous zoomons sur le système pour faire apparaître la gestion des réservations, les autres processus internes et les stockages de données (Bases de données / Fichiers).
- Stockages de données (Fichiers) :
- D1 : Fichier des Vols.
- D2 : Fichier des Réservations.
- Processus internes :
- P1 : Gestion des vols (Reçoit les infos du Gérant, met à jour D1 : ajout/suppression, tarifs, actualise l'état complet/non complet).
- P2 : Gestion des réservations (Processus demandé). Reçoit la demande de l'Utilisateur. Lit D1 pour vérifier la disponibilité et obtenir les règles tarifaires. Lit/Écrit dans D2 pour enregistrer, annuler ou modifier l'état de la réservation. Envoie l'état de réservation à l'Utilisateur.
- P3 : Calcul des prix (Sous-processus ou fonction liée à P2). Lit la période et le tarif de base depuis D1, applique la règle, et retourne le prix final.
- Flots internes autour de la "Gestion des réservations" :
- Flot entrant de Utilisateur ->
Gestion des réservations: Données passager, choix du vol. - Flot
Gestion des réservations-> StockageD1 (Vols): Requête de disponibilité. - Flot
Gestion des réservations->Calcul des prix: Demande de tarification avec date. - Flot
Calcul des prix->Gestion des réservations: Tarif calculé. - Flot
Gestion des réservations-> StockageD2 (Réservations): Enregistrement des données. - Flot sortant
Gestion des réservations-> Utilisateur : Billet / État actualisé.
- Flot entrant de Utilisateur ->
Question 2 - Proposition d'architecture
Architecture proposée : Architecture 3-tiers (Client-Serveur à 3 niveaux).
Justification des choix : Le cahier des charges précise que l'application est utilisée à la fois en agence et via Internet, et par des utilisateurs différents (Clients, Gérants) effectuant des opérations concurrentes (réserver, annuler, modifier les prix).
- Couche Présentation (Tier 1) : Permet d'avoir des interfaces adaptées (un navigateur web pour l'accès Internet, un client lourd ou portail dédié pour les agences et le gérant).
- Couche Métier / Serveur d'application (Tier 2) : Centralise la logique métier (calcul du prix selon la période, actualisation des états de vols pour éviter le surbooking, gestion des dates limites). Placer cela sur un serveur dédié évite de dupliquer la logique sur les postes des agences et sécurise les règles de gestion (le client ne peut pas trafiquer le calcul du prix).
- Couche Données (Tier 3) : Un serveur de base de données relationnelle (ex: MySQL, PostgreSQL) qui stocke les vols et les réservations de manière centralisée, gérant les accès concurrents grâce aux transactions (ACID) cruciales pour un système de réservation.
Exercice 2 - Analyse et refactoring de code Java
Question 1 - Problèmes de couplage et de cohésion
La classe Java Anonyme présente de graves défauts de conception :
- Faible Cohésion : La classe est ce qu'on appelle un "God Object" (objet à tout faire). Elle regroupe plusieurs responsabilités qui n'ont aucun rapport entre elles :
- La gestion technique de la connexion à la base de données (méthodes
ConnexionBDetarrêt). - La logique de sécurité et d'authentification d'un utilisateur (méthode
authentificationavec login/psw). - La logique métier de gestion du personnel (méthodes
AjouterPersonneetSupprimerPersonneavec id/Nom).
- La gestion technique de la connexion à la base de données (méthodes
- Fort Couplage :
- Le code métier est fortement couplé à l'API JDBC (
java.sql.*). Les requêtes SQL (chaînes de caractères en dur) sont mélangées aux méthodes métier, rendant tout changement de base de données ou de structure de table très difficile. - Les variables d'instance (
connexion,instruction,résultat) sont partagées globalement par toutes les méthodes, créant des effets de bord imprévisibles (si deux méthodes sont appelées successivement, l'état deinstructionest écrasé).
- Le code métier est fortement couplé à l'API JDBC (
- Remarque sur la syntaxe (réparation du code source) : Le code d'origine comportait des erreurs de casse (
Public,Return,Import), des erreurs de conception (instancier des variables de connexion en public), et une erreur de type critique (importation dejava.beans.Statementau lieu dejava.sql.Statement, ce qui obligeait le développeur à faire des "casts" hasardeux :(java.sql.Statement) instruction).
Question 2 - Solution proposée
Pour résoudre ces problèmes, nous devons décomposer ce code en appliquant le principe de responsabilité unique (SRP) de l'architecture MVC ou 3-tiers :
- Une classe pour la connexion à la base de données (Utilitaires).
- Des classes "Modèle" pour représenter les entités (
Personne,Utilisateur). - Des classes "DAO" (Data Access Object) pour isoler les requêtes SQL.
Voici le code corrigé, structuré et fonctionnel :
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
// 1. Classe de gestion de la connexion (Singleton ou Utilitaire)
class DBManager {
public static Connection getConnection() throws SQLException {
try {
Class.forName("com.mysql.jdbc.Driver");
return DriverManager.getConnection("jdbc:mysql://localhost:3306/Projet", "root", "admin");
} catch (ClassNotFoundException ex) {
throw new SQLException("Problème de pilote de base de données");
}
}
}
// 2. Modèles de données (Entités de base)
class Personne {
private int id;
private String nom;
public Personne(int id, String nom) {
this.id = id;
this.nom = nom;
}
public int getId() { return id; }
public String getNom() { return nom; }
}
// 3. Classes d'accès aux données (DAO) - Haute cohésion, gère uniquement le SQL
class PersonneDAO {
// Utilisation de PreparedStatement pour corriger les failles d'injection SQL du code original
public void ajouterPersonne(Personne p) {
String sql = "INSERT INTO Personnels (matricule, nom) VALUES (?, ?)";
try (Connection conn = DBManager.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, p.getId());
pstmt.setString(2, p.getNom());
pstmt.executeUpdate();
} catch (SQLException ex) {
System.err.println("Erreur lors de l'ajout: " + ex.getMessage());
}
}
public void supprimerPersonne(int id) {
String sql = "DELETE FROM Personnels WHERE matricule = ?";
try (Connection conn = DBManager.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, id);
pstmt.executeUpdate();
} catch (SQLException ex) {
System.err.println("Erreur lors de la suppression: " + ex.getMessage());
}
}
}
class AuthentificationDAO {
public boolean authentifier(String login, String psw) {
String sql = "SELECT psw FROM Identification WHERE login = ?";
try (Connection conn = DBManager.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, login);
try (ResultSet rs = pstmt.executeQuery()) {
if (rs.next()) {
String motDePasseBase = rs.getString(1);
return motDePasseBase.equals(psw);
}
}
} catch (SQLException ex) {
System.err.println("Erreur d'authentification: " + ex.getMessage());
}
return false;
}
}
Exercice 3 - Graphe de contrôle et tests (Recherche dichotomique)
Question 1 - Graphe de contrôle
Le code source a été décomposé en nœuds (blocs de base) pour établir le graphe de flux de contrôle (Control Flow Graph). Voici la numérotation logique des instructions :
- Nœud 1 : Initialisations :
int m; int g = 0; int d = tab_trie.length-1; boolean trouv = false;(Ligne 8) - Nœud 2 : Évaluation condition de boucle :
while (g <= d && !trouv)(Ligne 9) - Nœud 3 : Calcul du milieu :
m = (d+g)/2;(Ligne 11) - Nœud 4 : Test d'égalité :
if (tab_trie[m] == cle)(Ligne 12) - Nœud 5 : Succès :
trouv = true;(Ligne 13) - Nœud 6 : Test de supériorité :
else if (tab_trie[m] > cle)(Ligne 14) - Nœud 7 : Réduction à gauche :
d = m-1;(Ligne 15) - Nœud 8 : Réduction à droite :
g = m+1;(Ligne 17) - Nœud 9 : Fin de la méthode :
return trouv;(Ligne 19)
(Note : Dans de nombreuses conventions, la fin du bloc conditionnel rejoint l'évaluation de la boucle, créant des arcs vers le Nœud 2).
Arcs (Transitions du graphe) :
- (1) -> (2)
- (2) -> (3) [si condition boucle Vraie]
- (2) -> (9) [si condition boucle Fausse]
- (3) -> (4)
- (4) -> (5) [si tab_trie[m] == cle]
- (4) -> (6) [si tab_trie[m] != cle]
- (6) -> (7) [si tab_trie[m] > cle]
- (6) -> (8) [si tab_trie[m] < cle]
- (5) -> (2)
- (7) -> (2)
- (8) -> (2)
Question 2 - Données de test pour la couverture des instructions
Pour couvrir toutes les instructions, chaque nœud de 1 à 9 doit être visité au moins une fois. Nous devons trouver la clé au milieu (Nœud 5), chercher à gauche (Nœud 7) et chercher à droite (Nœud 8). Un seul tableau avec deux appels de tests différents suffit :
- Tableau :
tab_trie = {10, 20, 30} - Test A :
cle = 10- Itération 1 :
m = 1,tab_trie[1]est 20. 20 > 10, exécuted = 0(Couvre le Nœud 7). - Itération 2 :
m = 0,tab_trie[0]est 10. 10 == 10, exécutetrouv = true(Couvre le Nœud 5).
- Itération 1 :
cle = 30
- Itération 1 :
m = 1,tab_trie[1]est 20. 20 n'est pas > 30, exécuteg = 2(Couvre le Nœud 8).
L'ensemble des tests (A et B) garantit que les instructions 1, 2, 3, 4, 5, 6, 7, 8 et 9 sont exécutées au moins une fois.
Question 3 - Couverture de tous les arcs
Analyse des arcs couverts par les tests A et B :
Les tests A et B sortent de la boucle car la variable trouv passe à true (l'évaluation !trouv de la boucle while devient fausse).
L'arc (2) -> (9) représentant la sortie de boucle est donc visité suite à la falsification de !trouv.
Cependant, l'arc où la boucle s'arrête parce que le tableau entier a été parcouru sans succès (c'est-à-dire quand la condition g <= d devient fausse) n'est pas strictement validé s'il on considère les conditions multiples. De plus, il est de bonne pratique de tester le chemin où l'élément est absent pour s'assurer de parcourir l'arc (2) -> (9) par épuisement des index.
Nouvelle donnée de test à ajouter :
- Test C (élément introuvable) :
tab_trie = {10, 20, 30},cle = 50.- Le programme va réduire les bornes jusqu'à ce que
gdevienne strictement supérieur àd, forçant la conditiong <= dà s'évaluer à faux. Avec les tests A, B et C, 100% des arcs (branches vraies et fausses de chaque condition, y compris la condition de sortie de boucle par non-trouvaille) sont couverts.
- Le programme va réduire les bornes jusqu'à ce que
Question 4 - Données de test pour le passage dans la boucle
Pour valider le fonctionnement de la boucle while, il faut tester ses limites d'itération :
- Zéro (0) passage dans la boucle : Il faut que la condition
g <= dsoit fausse dès l'initialisation. Cela se produit avec un tableau vide.- Données :
tab_trie = {}(tableau vide),cle = 5. - Explication :
g = 0,d = -1.0 <= -1est faux, la boucle ne s'exécute pas.
- Données :
- Exactement un (1) passage dans la boucle : Il faut que l'élément soit trouvé directement au milieu dès la première itération.
- Données :
tab_trie = {10, 20, 30},cle = 20. - Explication :
m = 1,tab_trie[1] == 20.trouv = true, la boucle s'arrête à l'évaluation suivante.
- Données :
- Deux (2) passages ou plus : L'élément doit nécessiter au moins une réduction des bornes.
- Données :
tab_trie = {10, 20, 30},cle = 10. - Explication : (Déjà démontré au Test A). Le premier passage modifie
d, le second passage trouve la clé.
- Données :
Méthode
Face à ce type de sujet de Génie Logiciel, la démarche est la suivante :
- Questions de cours (Réflexion) : Soyez précis et utilisez le vocabulaire exact du cours (Cohésion, Couplage, MVC, 3-Tiers). Ne rédigez pas de longues dissertations, allez droit à la définition et à l'objectif de chaque concept.
- Modélisation (DFD) : Identifiez strictement les acteurs (entités externes) et ce qu'ils envoient/reçoivent. Un processus ne doit jamais créer d'information magiquement : tout ce qui en sort doit avoir été calculé à partir de ce qui y entre ou des bases de données qu'il lit.
- Analyse de code : Cherchez toujours les "code smells" classiques. Des requêtes SQL mélangées à du code métier indiquent toujours un problème de couplage. Résolvez cela mentalement en séparant "qui stocke" de "qui calcule". Corrigez les erreurs de syntaxe flagrantes lors de la réécriture.
- Tests logiciels (Graphe de contrôle) : La rigueur est essentielle. Numérotez les blocs de code en distinguant bien les évaluations de conditions (les
if, leswhile) et les blocs d'instructions. Pour trouver les données de test, simulez l'exécution pas à pas (trace manuelle) avec des valeurs simples et de petits tableaux jusqu'à passer par tous les embranchements de votre graphe. Pensez toujours aux cas limites (tableau vide, élément non trouvé).
Commentaires
Aucun commentaire pour le moment. Posez la première question.