À l’initiateur de ce cours, Jean-Michel Inglebert…​

blasonJMI
Figure 1. Blason des étudiants DUT Info 2016

1. Gestion de projets

1.1. C’est quoi 1 projet ?

On appelle projet un ensemble finalisé d’activités et d’actions entreprises dans le but de répondre à un besoin défini dans des délais fixés et dans la limite d’une enveloppe budgétaire allouée.
— 2014
Wikipedia
Répondre au client

(qui paye)

dans la vraie vie

(les ressources sont limitées ET les obstacles matériels et humains sont sans limite)

— 2014
JMI

1.2. OK, allons-y …​

  • DEBUT : la demande est exprimée/fournie comment ?

    1. cahier des charges,

    2. liste de fonctionnalités attendues

    3. …​

  • FIN : le résultat sera livré quand ?

Eviter de répondre : hier (Sinon, c’est un processus de sauvetage et plus 1 projet)

1.3. …​ entre DEBUT et FIN, comment FAIRE ?

Option 1

se débrouiller (comme dans 'FAIRE les soldes')

Option 2

dérouler un process (comme dans '(RE)FAIRE son passeport')

Option 3

réaliser un projet

1.4. Depuis le temps que la notion de projet existe …​

7 projets sur 10 arrivent en retard ou jamais !

— Janvier 2014
Revue Programmez!

Les causes :

  • la demande client est floue

  • surestimation des compétences disponibles

  • dérive au démarrage

  • les demandes de modification se sont perdues

  • les équipes sont surchargées

  • perte de compétences au cours du projet

  • les risques sont mal évalués

  • les réunions sont improductives

Dans tous les cas, c’est l’équipe de développement qui est fautive.

1.5. Pourquoi est-ce si difficile ?

Pourtant :

  • L’objectif est connu (fourni par le client)

  • L’atteinte de l’objectif est mesurable (évaluable)

Le problème

On n’a pas de méthode infaillible qui garantisse qu’une équipe projet atteindra les objectifs.

Pourquoi ?

Il s’agit de gérer une complexité métier, technique et humaine avec des ressources limitées (temps, moyens …​)

Seule certitude prévisible : il faudra s’adapter aux imprévus.

1.6. Le découpage en phases d’un projet logiciel

Ça n’empêche pas de définir de grandes étapes génériques que le projet devra franchir : ses PHASES

Un projet de développement logiciel se terminera de 'manière satisfaisante' si l’équipe fournit une application fiable, robuste et maintenable qui répond aux exigences du client.

fiable

fait ce qu’on attend d’elle dans les conditions fixées

robuste

supporte la charge, les erreurs des utilisateurs, les pannes etc

maintenable

ne demande pas d’être réécrite pour la moindre évolution

2. Produire des applications

BUILD

make, ant, Maven, Ivy, Gradle, etc.

cf. cours suivants…​

Gestion de projets
  • tuleap, le remplaçant de redmine, en version gratuite pour les académiques.

  • GitLab, le site pour héberger vos codes en utilisant .

3. Méthodes

3.1. R.A.C.H.E

La méthode préférée des étudiants La RACHE!

Voir l’excellent site parodique http://www.la-rache.com/.

3.2. Méthodes en Cascade ou Cycle en V

waterfall
Figure 2. Les méthodes en cascade (waterfall)

Le cycle en V adopte :

  • une approche descendante pour l’analyse et la conception

  • suivie d’une phase montante pour le codage, les tests et l’intégration.

v123
Figure 3. Les méthodes de type Cycle en V

Un projet de développement de type cycle en V se terminera de manière satisfaisante si l’équipe arrive à remonter le 'V' dans de 'bonnes conditions'.

Arrêt du projet
  • l’application n’est pas livrable

  • au mieux des modules/classes de base peuvent être réutilisés

Dérive temporelle
  • la phase de codage et d’intégration est raccourcie

  • le produit livré a été 'trop peu' testé

En résumé :

  • illustre une approche cartésienne de réduction de la complexité

  • produit les plans de tests dans les phases descendantes

  • vérifie les plans de tests dans les phases ascendantes

Limites connues
  • ne décrit pas les phases de maintenance et d’évolution

  • ne prévoit pas qu’on revienne sur les étapes de conception ?

  • courbe a posteriori : quelle est la complexité estimée du projet à son début ?

  • l’application livrée est-elle conforme à sa documentation (et vice versa) ?

3.3. Du cycle en V aux cycles en W

3.3.1. Partons d’un exemple trivial

Example 1. Une application web de gestion d’urls

Développer une application web capable de collecter et d’afficher les urls collectées.

Cycle 0

Mise en place d’une infrastructure d’intégration

Solution retenue :

  • Langage de développement : PHP

  • Infrastructure d’intégration : serveur LAMP

Cycle 1

Afficher les urls collectées

Solution retenue :

  • le serveur maintient un tableau d’urls

  • la page retournée en mode GET affiche les urls sous forme de liste

Cycle 2

Saisie et collecte des urls

Solution retenue :

  • la page propose un formulaire de saisie de nouvelles urls

3.3.2. Bilan de l’exemple trivial

w5rb2

3.3.3. Les bénéfices reconnus

  • livre une application qui marche dès le cycle 1

  • gère la complexité et les problèmes d’intégration à chaque cycle

  • s’applique à un développement nouveau comme à une évolution

  • permet au client d’affiner ses exigences

Rappel :

Un projet de développement logiciel se terminera de 'manière satisfaisante' si l’équipe fournit une application fiable, robuste et maintenable qui répond aux exigences du client.

Les cas où ça ne marche pas
  • le client est (trop) absent

  • la conduite de la méthode choisie est approximative (dérive des livraisons)

  • les tests sont à l’abandon

Les problèmes nouveaux
  • qu’elle est la capacité de l’infrastructure initiale (cycle 0)

  • automatiser les tests de non régression : XP, T/BDD …​

  • approche ascendante ⇒ refactoring …​

  • comment gérer les livraisons fréquentes : intégration continue …​

  • que devient la documentation ???

3.4. V versus W

V
  • Expression des besoins

  • Spécifications

  • Conception générale et détaillée

  • Codage et tests

  • Intégration et tests

  • Livraison (recette)

W
  • Cycle 0

  • Cycle 1 .. N

    • choisir des fonctionnalités

    • …​ coder/tester …​

    • livrer

    • ajuster la liste des demandes

3.5. Ce qui est toujours fait

  • Planifier (tout ou partie) des prochaines actions

  • Réaliser les actions planifiées

  • Evaluer la distance au but

3.6. Evaluer l’atteinte des objectifs

L’atteinte de tous les objectifs (et sous-objectifs) d’un projet doit être évaluée.

Quelle que soit la 'méthode de développement' employée, tout développement logiciel doit réaliser des 'tests unitaires' et des 'tests d’intégration'.

Aujourd’hui, ON DOIT
  • écrire systématiquement des programmes de test

  • exécuter très régulièrement ces programmes de tests

4. La méthode Agile SCRUM

  • Scrum est un processus agile qui permet de produire la plus grande valeur métier dans la durée la plus courte.

  • Du logiciel qui fonctionne est produit à chaque sprint (toutes les 2 à 4 semaines).

  • Le métier définit les priorités.

  • L’équipe s’organise elle-même pour déterminer la meilleure façon de produire les exigences les plus prioritaires.

  • À chaque fin de sprint, tout le monde peut voir fonctionner le produit courant et décider soit de le livrer dans l’état, soit de continuer à l’améliorer pendant un sprint supplémentaire.

4.1. Les Acteurs SCRUM

Les acteurs dans Scrum sont :

le Product Owner

le client ou son représentant qui fait partie de l’équipe

le SCRUM Master

un animateur, facilitateur plutôt qu’un chef (de projet)

l'équipe

capable de mettre en oeuvre toutes les compétences (architecture, conception, développement, IHM, tests, documentation, etc.)

4.1.1. Rôles du Product Owner

  • Définit les fonctionnalités du produit

  • Choisit la date et le contenu de la release (version livrée aux utilisateurs réalisée en plusieurs sprints)

  • Est responsable du retour sur investissement (de la valeur métier)

  • Définit les priorités dans le backlog en fonction de la valeur « métier »

  • Ajuste les fonctionnalités et les priorités à chaque sprint si nécessaire

  • Accepte ou rejette les résultats

4.1.2. Rôles du SCRUM Master

  • Assure le management du projet

  • Fait appliquer par l’équipe les valeurs et les pratiques de Scrum

  • Résout des problèmes

  • S’assure que l’équipe est complètement fonctionnelle et productive

  • Facilite une coopération poussée entre tous les rôles et fonctions

  • Protège l’équipe des interférences extérieures

4.1.3. L’équipe SCRUM

  • Comprend de 5 à 10 personnes, de préférence à plein temps sur le projet

  • Regroupe tous les rôles Architecte, concepteur, développeur, spécialiste IHM, testeur, etc.

  • S’organise par elle-même

  • La composition de l’équipe ne doit pas changer pendant un Sprint

4.2. Déroulement d’un projet SCRUM

scrum1
  • Planification du sprint suivant

    • Choix des items du backlog de produit qui seront réalisés durant le sprint

    • Identification des tâches et définition du backlog de sprint estimé en heures

  • SCRUM quotidien (15mn)

    • qu’ai-je fait hier ?

    • que vais-je faire aujourd’hui ?

    • y-a-t-il des problèmes particuliers ?

  • Revue de SPRINT (15-30mn)

    • Présentation de l’application à l’équipe et à toute personne invitée

    • Calcul de la vélocité de l’équipe

  • Rétrospective de SPRINT (15mn)

    • L’équipe (client compris) revient sur ses modes de fonctionnement

    • Qu’est-ce qui marche : on continue à l’identique

    • Qu’est-ce qui ne marche pas : comment fait-on désormais ?

    • Y-a-t-il des choses à ne plus faire ?

  • Planification de Release

    • Définit les exigences du backlog de produit qui constitueront la release

    • Définit le nombre de sprints et la date de livraison de la release

    • Effectuée avant le premier sprint si la vélocité de l’équipe est connue

    • Actualisée à chaque fin de sprint

4.3. Les artefacts SCRUM

Le backlog de produit

la liste des exigences métier ordonnée par la valeur métier (l’importance métier pour le Product Owner)

Les backlogs de sprint

les listes des tâches à réaliser pour répondre aux exigences métier choisies pour le sprint

Les burndowns du projet

les graphiques montrant l'avancement du projet

4.3.1. Le backlog de produit

  • Il est fourni par le Product Owner au démarrage du projet

  • Il est une liste ordonnée d’exigences (stories) classées selon leur importance métier

  • Le classement de chaque exigence pourra être révisé à chaque revue de sprint par le Product Owner

  • Des exigences pourront être ajoutées/retirées à chaque revue de sprint par le Product Owner

4.3.2. Les backlogs de sprint

  • Définis lors de la planification des sprints

  • Ensemble des tâches à effectuer pour réaliser les exigences choisies pour le sprint dans le backlog de produit

  • Les tâches ont estimées en heures

  • L’estimation des tâches sont réestimées à chaque SCRUM (mélée quotidienne)

  • La définition des tâches conduit souvent à réaliser collectivement une conception de haut niveau

4.3.3. Les burndowns du projet

  • Les valeurs du backlog de produit et les estimations du backlog de sprint permettent de produire les courbes d’avancement du projet

Burndown de sprint
  • Actualisé chaque jour

  • Indique le nombre d’heures restantes pour toutes les tâches du sprint

sprint burndown
le burndown de sprint n’est pas nécessairement décroissant si des tâches sont ajoutées ou bien réévaluées à la hausse pour leur durée.
Burndown de produit (ou de release)
  • Évalue à chaque fin de sprint la quantité du backlog de produit qui reste à faire

productBurndown
le backlog de release pouvant évoluer, la courbe n’est pas nécessairement décroissante.
permet d’en déduire une estimation de la date de fin de release ou une estimation de la quantité du backlog de produit qui ne sera pas réalisée à une date donnée.

4.3.4. Indicateurs

Vélocité

quantité du backlog de produit réalisée par l’équipe pendant un sprint (calculée en points)

Capacité

quantité du backlog de produit qui sera réalisée lors du prochain sprint

5. Les Tests

Quelle que soit la méthode de développement choisie, les Tests sont le seul moyen de garantir que le produit livré est conforme aux exigences du client.

5.1. Tests unitaires

Ce sont les plus simples. Et pourtant il s’agit …​

  • d’oublier les approches 'manuelles'

  • d’expliciter les 'limites'

  • de traiter le 'qualitatif' et le 'quantitatif'

Exemple connu (cf DUT/INFO S1)]

Exemple: implémenter le type abstrait MatriceEntier

Spécifications
Opérations
  1. creerMatrice : Entier * Entier → MatriceEntier

  2. getNbLignes : MatriceEntier → Entier

  3. getNbColonnes : MatriceEntier → Entier

  4. getElement : MatriceEntier * Entier * Entier → Entier

  5. somLigne : MatriceEntier * Entier → Entier

  6. somColonne : MatriceColonne * Entier → Entier

  7. estCarree : MatriceEntier → Booleen

  8. estDiagonale : MatriceEntier → Booleen

  9. setElement : MatriceEntier * Entier * Entier * Entier → MatriceEntier

  10. setPremiereDiagonale : MatriceEntier * Entier → MatriceEntier

  11. setSecondeDiagonale : MatriceEntier * Entier → MatriceEntier

  12. mulMatNombre : MatriceEntier * Entier → MatriceEntier

Préconditions
  1. creerMatrice(l,c) valide SI ET SEULEMENT SI ( l > 0 ) ET ( c > 0 )

  2. getElement(m,i,j) valide SI ET SEULEMENT SI ( 0 < = i < getNbLignes(m) ) ET ( 0 < = j < getNbColonnes(m))

  3. setElement(m,i,j,n) valide SI ET SEULEMENT SI ( 0 < = i < getNbLignes(m) ) ET ( 0 < = j < getNbColonnes(m))

  4. somLigne(m,i) valide SI ET SEULEMENT SI 0 < = i < getNbLignes(m)

  5. somColonne(m,j) valide SI ET SEULEMENT SI 0 < = j < getNbColonnes(m)

  6. setPremiereDiagonale(m,n) valide SI ET SEULEMENT SI estCarree(m)

  7. setSecondeDiagonale(m,n) valide SI ET SEULEMENT SI estCarree(m)

  8. estDiagonale(m) valide SI ET SEULEMENT SI estCarree(m)

  1. getNbLignes(creerMatrice(l,c)) = l

  2. getNbColonnes(creerMatrice(l,c)) = c

  3. getElement(creerMatrice(l,c),i,j) = 0

  4. somLigne(creerMatrice(l,c),i) = 0

  5. somColonne(creerMatrice(l,c),j) = 0

  6. estCarree(creerMatrice(l,c)) SI ET SEULEMENT SI l = c

  7. estDiagonale(creerMatrice(lc,lc)) = VRAI

  8. getNbLignes(setPremiereDiagonale(m,n)) = getNbLignes(m)

  9. getNbColonnes(setPremiereDiagonale(m,n)) = getNbColonnes(m)

  10. getElement(setPremiereDiagonale(m,n),ij,ij) = n

  11. getElement(setPremiereDiagonale(m,n),i,j) = getElement(m,i,j)

  12. somLigne(setPremiereDiagonale(m,n),i) = somLigne(m,i) ` n - getElement(m,i,i)

  13. somColonne(setPremiereDiagonale(m,n),j) = somColonne(m,j) ` n - getElement(m,j,j)

  14. somLigne(setSecondeDiagonale(m,n),i) = somLigne(m,i) ` n - getElement(m,i,getNbColonnes(m) - 1 - i)

  15. somColonne(setSecondeDiagonale(m,n),j) = somColonne(m,j) ` n - getElement(m,j,getNbLignes(m) -1 - j)

  16. somLigne(mulMatNombre(setPremiereDiagonale(m,1),n),i) = somLigne(m,i) ` n - getElement(m,i,i)

  17. somColonne(mulMatNombre(setPremiereDiagonale(m,1),n),j) = somColonne(m,j) ` n - getElement(m,j,j)

Programme de Test des Opérations
import junit.textui.TestRunner;
import junit.framework.TestSuite;
import junit.framework.TestCase;

public class MatriceEntierOperationsTest extends TestCase {
  static int totalAssertions = 0;
  static int bilanAssertions = 0;

  /*
   Types des opérations du type MatriceEntier
  */
  public void test_type_new_MatriceEntier() throws Exception {
    MatriceEntier m = new MatriceEntier(3,3) ;

    totalAssertions++ ;
    assertEquals("new MatriceEntier(3,3) retourne une MatriceEntier", "MatriceEntier", m.getClass().getName());
    bilanAssertions++ ;
  }

  public void test_type_get() throws Exception {
    MatriceEntier m = new MatriceEntier(3,4) ;

    totalAssertions++ ;
    assertTrue("getNbLignes() > 0", m.getNbLignes() > 0);
    bilanAssertions++ ;

    totalAssertions++ ;
    assertTrue("getNbColonnes() > 0", m.getNbColonnes() > 0);
    bilanAssertions++ ;

    for (int i=0; i<m.getNbLignes(); i++) {
     for (int j=0; j<m.getNbColonnes(); j++) {
      totalAssertions++ ;
      assertTrue("getElement() retourne un entier", (m.getElement(i,j) >= 0) || (m.getElement(i,j) < 0));
      bilanAssertions++ ;
     }
    }
  }

  public void test_type_som() throws Exception {
    MatriceEntier m = new MatriceEntier(3,4) ;

    for (int i=0; i<m.getNbLignes(); i++) {
      totalAssertions++ ;
      assertTrue("somLigne("+i+") >= 0", m.somLigne(i) >= 0);
      bilanAssertions++ ;
    }
    for (int j=0; j<m.getNbColonnes(); j++) {
      totalAssertions++ ;
      assertTrue("somColonne("+j+") >= 0", m.somColonne(j) >= 0);
      bilanAssertions++ ;
    }
  }

  public void test_type_est() throws Exception {
    MatriceEntier m = new MatriceEntier(3,3) ;
    totalAssertions++ ;
    assertTrue("estCarree() retourne un booleen", (m.estCarree() == true) || (m.estCarree() == false));
    bilanAssertions++ ;

    totalAssertions++ ;
    assertTrue("estDiagonale() retourne un booleen", (m.estDiagonale() == true) || (m.estDiagonale() == false));
    bilanAssertions++ ;

    m.setElement(0,0,1) ;
    totalAssertions++ ;
    assertTrue("estDiagonale() retourne un booleen", (m.estDiagonale() == true) || (m.estDiagonale() == false));
    bilanAssertions++ ;

    m = new MatriceEntier(3,4) ;
    totalAssertions++ ;
    assertTrue("estCarree() retourne un booleen", (m.estCarree() == true) || (m.estCarree() == false));
    bilanAssertions++ ;
  }

  public void test_type_set_mul() throws Exception {
    MatriceEntier m = new MatriceEntier(3,3) ;

    for (int i=0; i<m.getNbLignes(); i++) {
     for (int j=0; j<m.getNbColonnes(); j++) {
      totalAssertions++ ;
      assertEquals("setElement() retourne une MatriceEntier", "MatriceEntier", m.setElement(i,j,i+j).getClass().getName());
      bilanAssertions++ ;
     }
    }

    totalAssertions++ ;
    assertEquals("setPremiereDiagonale(99) retourne une MatriceEntier", "MatriceEntier", m.setPremiereDiagonale(99).getClass().getName());
    bilanAssertions++ ;

    totalAssertions++ ;
    assertEquals("setSecondeDiagonale(99) retourne une MatriceEntier", "MatriceEntier", m.setSecondeDiagonale(99).getClass().getName());
    bilanAssertions++ ;

    totalAssertions++ ;
    assertEquals("mulMatNombre() retourne une MatriceEntier", "MatriceEntier", m.mulMatNombre(33).getClass().getName());
    bilanAssertions++ ;
  }

  /*
   main() de la classe de Test
  */
  public static void main(String[] args) {
    junit.textui.TestRunner.run(new TestSuite(MatriceEntierOperationsTest.class));
    if (bilanAssertions == totalAssertions) { System.out.print("Bravo !"); }
    else  { System.out.print("OUPS !"); }
    System.out.println(" "+bilanAssertions+"/"+totalAssertions+" assertions verifiees");
  } // fin main

} // fin MatriceEntierOperationsTest
Programme de Test des Préconditions
import junit.textui.TestRunner;
import junit.framework.TestSuite;
import junit.framework.TestCase;

public class MatriceEntierPreconditionsTest extends TestCase {
  static int totalAssertions = 0;
  static int bilanAssertions = 0;

  /*
   Préconditions du type Pile
  */
  public void test_precondition1() {
    MatriceEntier m ;
    boolean exception = false ;
    try { m = new MatriceEntier(0,1) ; }
    catch (Exception e) { exception = true ; };

    totalAssertions++ ;
    assertTrue("new MatriceEntier(0,1) leve une exception", exception);
    bilanAssertions++ ;

    exception = false ;
    try { m = new MatriceEntier(1,0) ; }
    catch (Exception e) { exception = true ; };

    totalAssertions++ ;
    assertTrue("new MatriceEntier(1,0) leve une exception", exception);
    bilanAssertions++ ;

    exception = false ;
    try { m = new MatriceEntier(0,0) ; }
    catch (Exception e) { exception = true ; };

    totalAssertions++ ;
    assertTrue("new MatriceEntier(0,0) leve une exception", exception);
    bilanAssertions++ ;
  }

  public void test_precondition2() throws Exception {
    MatriceEntier m = new MatriceEntier(2,3);
    boolean exception = false ;
    try { m.getElement(-1,1) ; }
    catch (Exception e) { exception = true ; };

    totalAssertions++ ;
    assertTrue("getElement(-1,1) leve une exception", exception);
    bilanAssertions++ ;

    exception = false ;
    try { m.getElement(2,2) ; }
    catch (Exception e) { exception = true ; };

    totalAssertions++ ;
    assertTrue("getElement(2,2) leve une exception", exception);
    bilanAssertions++ ;

    exception = false ;
    try { m.getElement(1,-1) ; }
    catch (Exception e) { exception = true ; };

    totalAssertions++ ;
    assertTrue("getElement(1,-1) leve une exception", exception);
    bilanAssertions++ ;

    exception = false ;
    try { m.getElement(1,3) ; }
    catch (Exception e) { exception = true ; };

    totalAssertions++ ;
    assertTrue("getElement(1,3) leve une exception", exception);
    bilanAssertions++ ;
  }

  public void test_precondition3() throws Exception {
    MatriceEntier m = new MatriceEntier(2,3);
    boolean exception = false ;
    try { m.setElement(-1,1,99) ; }
    catch (Exception e) { exception = true ; };

    totalAssertions++ ;
    assertTrue("setElement(-1,1,99) leve une exception", exception);
    bilanAssertions++ ;

    exception = false ;
    try { m.setElement(2,2,99) ; }
    catch (Exception e) { exception = true ; };

    totalAssertions++ ;
    assertTrue("setElement(2,2,99) leve une exception", exception);
    bilanAssertions++ ;

    exception = false ;
    try { m.setElement(1,-1,99) ; }
    catch (Exception e) { exception = true ; };

    totalAssertions++ ;
    assertTrue("setElement(1,-1,99) leve une exception", exception);
    bilanAssertions++ ;

    exception = false ;
    try { m.setElement(1,3,99) ; }
    catch (Exception e) { exception = true ; };

    totalAssertions++ ;
    assertTrue("setElement(1,3,99) leve une exception", exception);
    bilanAssertions++ ;
  }

  public void test_precondition4() throws Exception {
    MatriceEntier m = new MatriceEntier(2,3);
    boolean exception = false ;
    try { m.somLigne(-1) ; }
    catch (Exception e) { exception = true ; };

    totalAssertions++ ;
    assertTrue("somLigne(-1) leve une exception", exception);
    bilanAssertions++ ;

    exception = false ;
    try { m.somLigne(2) ; }
    catch (Exception e) { exception = true ; };

    totalAssertions++ ;
    assertTrue("somLigne(2) leve une exception", exception);
    bilanAssertions++ ;
  }

  public void test_precondition5() throws Exception {
    MatriceEntier m = new MatriceEntier(2,3);
    boolean exception = false ;
    try { m.somColonne(-1) ; }
    catch (Exception e) { exception = true ; };

    totalAssertions++ ;
    assertTrue("somColonne(-1) leve une exception", exception);
    bilanAssertions++ ;

    exception = false ;
    try { m.somColonne(3) ; }
    catch (Exception e) { exception = true ; };

    totalAssertions++ ;
    assertTrue("somColonne(3) leve une exception", exception);
    bilanAssertions++ ;
  }

  public void test_precondition6() throws Exception {
    MatriceEntier m = new MatriceEntier(2,3);
    boolean exception = false ;
    try { m.setPremiereDiagonale(99) ; }
    catch (Exception e) { exception = true ; };

    totalAssertions++ ;
    assertTrue("setPremiereDiagonale(99) leve une exception", exception);
    bilanAssertions++ ;
  }

  public void test_precondition7() throws Exception {
    MatriceEntier m = new MatriceEntier(2,3);
    boolean exception = false ;
    try { m.setSecondeDiagonale(99) ; }
    catch (Exception e) { exception = true ; };

    totalAssertions++ ;
    assertTrue("setSecondeDiagonale(99) leve une exception", exception);
    bilanAssertions++ ;
  }

  public void test_precondition8() throws Exception {
    MatriceEntier m = new MatriceEntier(2,3);
    boolean exception = false ;
    try { m.estDiagonale() ; }
    catch (Exception e) { exception = true ; };

    totalAssertions++ ;
    assertTrue("estDiagonale() leve une exception", exception);
    bilanAssertions++ ;
  }

  /*
   main() de la classe de Test
  */
  public static void main(String[] args) {
    junit.textui.TestRunner.run(new TestSuite(MatriceEntierPreconditionsTest.class));
    if (bilanAssertions == totalAssertions) { System.out.print("Bravo !"); }
    else  { System.out.print("OUPS !"); }
    System.out.println(" "+bilanAssertions+"/"+totalAssertions+" assertions verifiees");
  } // fin main

} // fin MatriceEntierPreconditionsTest
Programme de Test des Axiomes
import junit.textui.TestRunner;
import junit.framework.TestSuite;
import junit.framework.TestCase;

public class MatriceEntierAxiomesTest extends TestCase {
  static int totalAssertions = 0;
  static int bilanAssertions = 0;

  /*
   Axiomes du type MatriceEntier
  */
  public void test_get() throws Exception {
    MatriceEntier m = new MatriceEntier(3,4) ;

    totalAssertions++ ;
    assertEquals("getNbLignes() == 3", 3, m.getNbLignes());
    bilanAssertions++ ;

    totalAssertions++ ;
    assertEquals("getNbColonnes() == 4", 4, m.getNbColonnes());
    bilanAssertions++ ;

    for (int i=0; i<m.getNbLignes(); i++) {
     for (int j=0; j<m.getNbColonnes(); j++) {
      totalAssertions++ ;
      assertEquals("getElement("+i+","+j+") == 0", 0, m.getElement(i,j));
      bilanAssertions++ ;
     }
    }

    m = new MatriceEntier(3,3) ;
    totalAssertions++ ;
    assertEquals("setPremiereDiagonale(99).getNbLignes() == getNbLignes()", m.setPremiereDiagonale(99).getNbLignes(), m.getNbLignes());
    bilanAssertions++ ;

    totalAssertions++ ;
    assertEquals("setSecondeDiagonale(99).getNbLignes() == getNbLignes()", m.setSecondeDiagonale(99).getNbLignes(), m.getNbLignes());
    bilanAssertions++ ;

    m = new MatriceEntier(3,3) ;
    m.setPremiereDiagonale(99) ;
    for (int i=0; i<m.getNbLignes(); i++) {
     for (int j=0; j<m.getNbColonnes(); j++) {
      totalAssertions++ ;
      if ( i == j ) {
        assertEquals("getElement("+i+","+i+") == 99", 99, m.getElement(i,i));
      } else {
        assertEquals("getElement("+i+","+j+") == 0", 0, m.getElement(i,j));
      }
      bilanAssertions++ ;
     }
    }

  } // fin test_get

  public void test_som() throws Exception {
    MatriceEntier m = new MatriceEntier(3,4) ;

    for (int i=0; i<m.getNbLignes(); i++) {
      totalAssertions++ ;
      assertEquals("somLigne("+i+") == 0", 0, m.somLigne(i));
      bilanAssertions++ ;
    }
    for (int j=0; j<m.getNbColonnes(); j++) {
      totalAssertions++ ;
      assertEquals("somColonne("+j+") == 0", 0, m.somColonne(j));
      bilanAssertions++ ;
    }

    m = new MatriceEntier(3,3) ;
    m.setPremiereDiagonale(99) ;
    for (int ij=0; ij<m.getNbLignes(); ij++) {
      totalAssertions = totalAssertions + 2 ; ;
      assertEquals("setPremiereDiagonale(99).somLigne("+ij+") == 99", 99, m.somLigne(ij));
      bilanAssertions++ ;
      assertEquals("setPremiereDiagonale(99).somColonne("+ij+") == 99", 99, m.somColonne(ij));
      bilanAssertions++ ;
    }

    m = new MatriceEntier(3,3) ;
    m.setSecondeDiagonale(99) ;
    for (int ij=0; ij<m.getNbLignes(); ij++) {
      totalAssertions = totalAssertions + 2 ; ;
      assertEquals("setSecondeDiagonale(99).somLigne("+ij+") == 99", 99, m.somLigne(ij));
      bilanAssertions++ ;
      assertEquals("setSecondeDiagonale(99).somColonne("+ij+") == 99", 99, m.somColonne(ij));
      bilanAssertions++ ;
    }

  } // fin test_som

  public void test_est() throws Exception {
    MatriceEntier m = new MatriceEntier(3,3) ;
    totalAssertions++ ;
    assertTrue("estCarree() == true", m.estCarree());
    bilanAssertions++ ;

    m = new MatriceEntier(2,3) ;
    totalAssertions++ ;
    assertFalse("estCarree() == false", m.estCarree());
    bilanAssertions++ ;

    m = new MatriceEntier(3,3) ;
    totalAssertions++ ;
    assertTrue("estDiagonale() == true", m.estDiagonale());
    bilanAssertions++ ;

    m.setPremiereDiagonale(99);
    totalAssertions++ ;
    assertTrue("setPremiereDiagonale(99).estDiagonale() == true", m.estDiagonale());
    bilanAssertions++ ;

    m.setSecondeDiagonale(99);
    totalAssertions++ ;
    assertFalse("setSecondeDiagonale(99).estDiagonale() == false", m.estDiagonale());
    bilanAssertions++ ;

  } // fin test_est

  public void test_mul() throws Exception {
    MatriceEntier m = new MatriceEntier(3,3) ;
    m.setPremiereDiagonale(1).mulMatNombre(99) ;
    for (int ij=0; ij<m.getNbLignes(); ij++) {
      totalAssertions = totalAssertions + 2 ; ;
      assertEquals("setPremiereDiagonale(1).mulMatNombre(99).somLigne("+ij+") == 99", 99, m.somLigne(ij));
      bilanAssertions++ ;
      assertEquals("setPremiereDiagonale(1).mulMatNombre(99).somColonne("+ij+") == 99", 99, m.somColonne(ij));
      bilanAssertions++ ;
    }

    m = new MatriceEntier(3,3) ;
    m.setSecondeDiagonale(1).mulMatNombre(99) ;
    for (int ij=0; ij<m.getNbLignes(); ij++) {
      totalAssertions = totalAssertions + 2 ; ;
      assertEquals("setSecondeDiagonale(1).mulMatNombre(99).somLigne("+ij+") == 99", 99, m.somLigne(ij));
      bilanAssertions++ ;
      assertEquals("setSecondeDiagonale(1).mulMatNombre(99).somColonne("+ij+") == 99", 99, m.somColonne(ij));
      bilanAssertions++ ;
    }

    MatriceEntier m_init = new MatriceEntier(3,3) ;
    m = new MatriceEntier(3,3) ;
    // Initialise m_init et m à {{0,1,2}{3,4,5}{6,7,8}}
    int k = 0 ;
    for (int i=0; i<m.getNbLignes(); i++) {
     for (int j=0; j<m.getNbColonnes(); j++) {
	m_init.setElement(i,j,k) ;
	m.setElement(i,j,k) ;
	k = k + 1 ;
     }
    }
    m.mulMatNombre(3) ;
    for (int i=0; i<m.getNbLignes(); i++) {
     for (int j=0; j<m.getNbColonnes(); j++) {
      totalAssertions++ ;
      assertEquals("m.mulMatNombre(3).getElement("+i+","+j+") == m.getElement("+i+","+j+") * 3", m_init.getElement(i,j) * 3, m.getElement(i,j));
      bilanAssertions++ ;
     }
    }

  } // fin test_mul

  /*
   main() de la classe de Test
  */
  public static void main(String[] args) {
    junit.textui.TestRunner.run(new TestSuite(MatriceEntierAxiomesTest.class));
    if (bilanAssertions == totalAssertions) { System.out.print("Bravo !"); }
    else  { System.out.print("OUPS !"); }
    System.out.println(" "+bilanAssertions+"/"+totalAssertions+" assertions verifiees");
  } // fin main

} // fin MatriceEntierAxiomesTest
Programme de Test des Opérations supplémentaires
import junit.textui.TestRunner;
import junit.framework.TestSuite;
import junit.framework.TestCase;

public class MatriceEntierOpSupTest extends TestCase {
  static int totalAssertions = 0;
  static int bilanAssertions = 0;

  /*
   Opérations supplémentaires du type MatriceEntier
  */
  public void test_toString() throws Exception {
    MatriceEntier m = new MatriceEntier(3,3) ;
    m.setPremiereDiagonale(1).setSecondeDiagonale(2) ;

    String ln = System.getProperty("line.separator") ;
    String attendu = "1 0 2 " + ln + "0 2 0 " + ln + "2 0 1 " + ln ;
    totalAssertions++ ;
    assertEquals("toString() == ", attendu, m.toString());
    bilanAssertions++ ;
  }

  public void test_toHTML() throws Exception {
    MatriceEntier m = new MatriceEntier(3,3) ;
    m.setPremiereDiagonale(1).setSecondeDiagonale(2) ;

    String ln = System.getProperty("line.separator") ;
    String attendu = "<table border=\"1\">" + ln ;
    attendu += "<tr><td>1</td><td>0</td><td>2</td></tr>" + ln + "<tr><td>0</td><td>2</td><td>0</td></tr>" + ln + "<tr><td>2</td><td>0</td><td>1</td></tr>" + ln ;
    attendu += "</table>" + ln ;
    totalAssertions++ ;
    assertEquals("toHTML() == ", attendu, m.toHTML());
    bilanAssertions++ ;
  }

  /*
   main() de la classe de Test
  */
  public static void main(String[] args) {
    junit.textui.TestRunner.run(new TestSuite(MatriceEntierOpSupTest.class));
    if (bilanAssertions == totalAssertions) { System.out.print("Bravo !"); }
    else  { System.out.print("OUPS !"); }
    System.out.println(" "+bilanAssertions+"/"+totalAssertions+" assertions verifiees");
  } // fin main

} // fin PileTest

Le programme de test fourni est :

  • un outil de détection des régressions

    • qui pourraient intervenir à la suite d’une modification de la classe MatriceEntier

  • une documentation de spécification

    • précise et concise

  • une documentation pour les programmeurs

    • opérationnelle

5.2. Tests d’intégration

Plus délicat, il s’agit :

  • de tester les exigences du client

  • de tester les intéractions système

  • de ne pas refaire les tests unitaires

Programme de Test du programme JourSuivantAvecLibDate.class
import junit.textui.TestRunner;
import junit.framework.TestSuite;
import junit.framework.TestCase;
import java.io.*;

public class JourSuivantAvecLibDateTest extends TestCase {
  static String programmeATester = "JourSuivantAvecLibDate" ;  (1)
  Process executionProgrammeATester ;                          (2)
  BufferedReader ecranProgrammeATester ;                       (3)
  BufferedWriter clavierProgrammeATester ;                     (4)

  String finDeLigne = System.getProperty("line.separator") ;   (5)

  public static void main(String[] args) {
    if ( args.length > 0 ) { programmeATester = args[0] ; }
    System.out.println("Tests du programme : " + programmeATester);
    junit.textui.TestRunner.run(new TestSuite(JourSuivantAvecLibDateTest.class)); (6)
  }

  protected void setUp() throws IOException {  (7)
    executionProgrammeATester = Runtime.getRuntime().exec("java -cp . "+programmeATester); (8)
    ecranProgrammeATester = new BufferedReader(new  InputStreamReader( executionProgrammeATester.getInputStream() )); (9)
    clavierProgrammeATester  = new BufferedWriter(new OutputStreamWriter( executionProgrammeATester.getOutputStream() )); (10)
  }

  // Saisies valides
  public void test_31_1_2013() throws IOException {
    assertEquals("Affiche : 'Saisir une date : jour mois annee ? '","Saisir une date : jour mois annee ? ",ecranProgrammeATester.readLine()); (11)
    clavierProgrammeATester.write("31 1 2013"+finDeLigne); (12)
    clavierProgrammeATester.flush();                       (13)
    assertEquals("Affiche : 'Le lendemain du 31/1/2013'","Le lendemain du 31/1/2013",ecranProgrammeATester.readLine());
    assertEquals("Affiche : 'sera le 1/2/2013.'","sera le 1/2/2013.",ecranProgrammeATester.readLine()); (14)
  }

  public void test_28_2_2013() throws IOException {
    String messageSaisie = "Saisir une date : jour mois annee ? " ;
    String[] ligneJeuDEssai = {"28 2 2013","Le lendemain du 28/2/2013","sera le 1/3/2013."} ;

    assertEquals("Affiche : "+messageSaisie,messageSaisie,ecranProgrammeATester.readLine());
    clavierProgrammeATester.write(ligneJeuDEssai[0]+finDeLigne); clavierProgrammeATester.flush();
    assertEquals("Affiche : "+ligneJeuDEssai[1],ligneJeuDEssai[1],ecranProgrammeATester.readLine());
    assertEquals("Affiche : "+ligneJeuDEssai[2],ligneJeuDEssai[2],ecranProgrammeATester.readLine());
  }

  protected void assertsPourSaisieValide(String messageSaisie,String saisie,String affichage1,String affichage2) throws IOException {
     assertEquals("Affiche : "+messageSaisie,messageSaisie,ecranProgrammeATester.readLine());
     clavierProgrammeATester.write(saisie+finDeLigne); clavierProgrammeATester.flush();
     assertEquals("Affiche : "+affichage1,affichage1,ecranProgrammeATester.readLine());
     assertEquals("Affiche : "+affichage2,affichage2,ecranProgrammeATester.readLine());
  }
  public void test_31_3_2013() throws IOException {
    String messageSaisie = "Saisir une date : jour mois annee ? " ;
    String[] ligneJeuDEssai = {"31 3 2013","Le lendemain du 31/3/2013","sera le 1/4/2013."} ;
    assertsPourSaisieValide(messageSaisie,ligneJeuDEssai[0],ligneJeuDEssai[1],ligneJeuDEssai[2]);
  }

  // Saisies invalides
  protected void assertsPourSaisieInvalide(String messageSaisie,String saisie,String affichage) throws IOException {
     assertEquals("Affiche : "+messageSaisie,messageSaisie,ecranProgrammeATester.readLine());
     clavierProgrammeATester.write(saisie+finDeLigne); clavierProgrammeATester.flush();
     assertEquals("Affiche : "+affichage,affichage,ecranProgrammeATester.readLine());
  }

  public void test_1_1_1581() throws IOException {
    String messageSaisie = "Saisir une date : jour mois annee ? " ;
    String[] ligneJeuDEssai = {"1 1 1581","La date du 1/1/1581 n'est pas une date valide."} ;
    assertsPourSaisieInvalide(messageSaisie,ligneJeuDEssai[0],ligneJeuDEssai[1]);
  }

  public void test_32_1_2013() throws IOException {
    String messageSaisie = "Saisir une date : jour mois annee ? " ;
    String[] ligneJeuDEssai = {"32 1 2013","La date du 32/1/2013 n'est pas une date valide."} ;
    assertsPourSaisieInvalide(messageSaisie,ligneJeuDEssai[0],ligneJeuDEssai[1]);
  }

} // fin class
1 nom de l’application (du programme) à tester
2 Process (Processus) = programme en cours d’exécution
3 lien vers l’écran du programme en cours d’exécution
4 lien vers le clavier du programme en cours d’exécution
5 récupération portable du retour à la ligne
6 lancement de toutes les fonctions débutant par 'test'
7 fonction (ré-)exécutée avant chaque fonction de test et qui exécute le programme à tester
8 lance le programme
9 se connecte à l’écran (sortie standard) du programme lancé
10 se connecte au clavier (entrée standard) du programme lancé
11 lit une ligne sur l’écran du programme lancé
12 écrit une ligne au clavier du programme lancé
13 force l’envoi de la ligne au clavier (vide le tampon de sortie)
14 lit une autre ligne sur l’écran du programme lancé

5.3. Les tests implémentent des algorithmes simples

given/when/then
public void test_dates_invalides() {
  int[][] tabJeuDEssaiDatesInvalides = {  (1)
      {1,1,1581},{0,1,2013},{99,99,2099},
      {32,1,2013},{29,2,2013},{32,3,2013},
      {31,4,2013},{32,5,2013},{31,6,2013},
      {32,7,2013},{32,8,2013},{31,9,2013},
      {32,10,2013},{31,11,2013},{32,12,2013},
      {29,2,1900},{30,2,2000}
  } ;
  for ( int indice = 0, taille = tabJeuDEssaiDatesInvalides.length;
        indice < taille ;
        indice = indice + 1){
    int[] date = tabJeuDEssaiDatesInvalides[indice] ;
    assertFalse(date[0]+"/"+date[1]+"/"+date[2]+" est invalide"
    , LibDate.dateValide(date[0],date[1],date[2]));  (2) (3)
  }
  bilanAssertions = bilanAssertions + tabJeuDEssaiDatesInvalides.length ;
}
1 given : dans les situations suivantes
2 when : quand on vérifie la validité de la date
3 then : on doit obtenir false

5.4. Tout est-il testable ?

  • les librairies

  • les intéractions systèmes (concurrence, etc.)

  • les services réseau

  • les interfaces graphiques (html, java, flash, etc.)

  • …​

  • PEUT-ETRE PAS, mais seulement après avoir essayé!

5.5. Toutes les manières de faire sont exploitables

  • Ecrire le programme qui fasse passer les tests fournis

  • Ecrire les tests d’un programme fourni

  • Coder une fonctionnalité et ajouter le test correspondant

  • Ajouter un test et intégrer la fonctionnalité correspondante (TDD)

  • Retrouver l’équilibre du couple Programme / Programme de Test

6. Au menu du module M3301/MPA

Réaliser le développement d’une application logicielle en utilisant la méthode SCRUM

et qui se termine dans de bonnes conditions

6.1. Modalités

Chaque groupe dispose de ses force vives et de 2 TD + 2 TP par semaine :

  • pour réaliser le sprint courant (fournir tous les livrables)

Quizz

QUESTION
  • Connectez-vous sur : http://www.socrative.com/ (student login)

  • Ou téléchargez l’application pour étudiant socrative2

  • Choisissez la room 44918d67

socrative1