1. Avant-propos

Nous allons nous intéresser dans ce cours aux Méthodologies de la Production d'Applications.

Ce module, qui fait suite aux modules de :

  • programmation (M2103),

  • conception (M2104) et

  • d’IHM (M2105).

Il est fortement corrélé au module de conception avancé (M3105).

Nous suivons (comme tous les DUT informatique) le programme pédagogique national (PPN - disponible ici).

M3301
Figure 1. Le contenu officiel

2. Principes

Ce module s’intéresse aux Méthodes et outils de la Production d'Applications (MPA).

Application

Dans notre cas, du logiciel.

Production

Cela signifie que c’est le résultat d’une fabrication ⇒ build

Méthodes

On illustrera certaines méthodes et on travaillera Scrum en particulier

Outils

On continuera l’apprentissage des outils de base

3. Contenu et Progression

  • Cours SCRUM, Cycle -1 et Cycle 0, Tests

  • Cours Gestion des Modèles au cours du développement et à la livraison

  • Cours Intégration continue, documentation

  • Cours Modèles UML complémentaire (Activité, État)

  • Cours Maintenance, refactoring, évolutions

3.1. Organisation et emploi du temps

Au lieu de faire 2 modules en // (comme POO et COO l’an dernier), vous aurez ce semestre 2 fois plus d’heures, mais d’un seul module, l’un après l’autre (MPA puis CPOA).

# Semaine Cours

1

36

Introduction

2

37

MPA2agile

3

38

MPA3manifesto

4

40

MPA4testing

5

41

ci

3.2. Projet fil rouge

  • TD1 : Comprendre l’objectif, planifier les tâches

  • TD2 : Développer à plusieurs et intégrer

  • TP1 : Développer seul et intégrer

  • TP2 : Développer à plusieurs et intégrer

  • TD3 : Evaluer le travail restant et les améliorations possibles de l’équipe, planifier les tâches

  • etc.

3.3. Développer une application java/web

  • Analyse

  • Conception ou évolution

  • Cycle de vie complet dont maintenance et exploitation

  • Normes logicielle/métriques qualité

  • Mise en production, documentation

4. Gestion de projet, Agilité, Tests

4.1. Gestion de projets

4.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

4.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)

4.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

4.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.

4.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.

4.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

4.2. Produire des applications

BUILD

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

cf. cours suivants…​

Gestion de projets

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

4.3. Méthodes

4.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/.

4.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) ?

4.3.3. Du cycle en V aux cycles en W

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

Bilan de l’exemple trivial
w5rb2
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 ???

4.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

4.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

4.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.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.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.)

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

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

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.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.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

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

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

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.
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

4.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.

4.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)

Axiomes
  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 operations 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

4.5.2. Tests d’intégration

titanic

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é

4.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

4.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é!

4.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

4.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.

Chaque groupe dispose de ses force vives et de 4 TD/TP par semaine pour réaliser le sprint courant (fournir tous les livrables).

5. Les principes de l'Agilité

Cette partie du cours est fortement inspirée par le MOOC Agile de Bertrand Meyer.

agileMeyer

5.1. Le manifeste Agile

manifesto
Figure 4. Le manifeste Agile (http://agilemanifesto.org/)
il date de février 2001!

5.2. Les 17 auteurs

Les plus connus :

  • Ward Cunningham (Wiki)

  • Kent Beck (XP, JUnit)

  • Ken Schwaber et Jeff Sutherland (Scrum)

  • Alistair Cockburn

  • Martin Fowler

  • …​

5.3. Les 12 principes

12principles

Vous trouverez une version actualisée des principes sur Wikipedia :

  • fr https://fr.wikipedia.org/wiki/Manifeste_agile#Les_12_principes

  • https://en.wikipedia.org/wiki/Agile_software_development

    1. Customer satisfaction by early and continuous delivery of valuable software

    2. Welcome changing requirements, even in late development

    3. Working software is delivered frequently (weeks rather than months)

    4. Close, daily cooperation between business people and developers

    5. Projects are built around motivated individuals, who should be trusted

    6. Face-to-face conversation is the best form of communication (co-location)

    7. Working software is the principal measure of progress

    8. Sustainable development, able to maintain a constant pace

    9. Continuous attention to technical excellence and good design

    10. Simplicity—the art of maximizing the amount of work not done—is essential

    11. Best architectures, requirements, and designs emerge from self-organizing teams

    12. Regularly, the team reflects on how to become more effective, and adjusts accordingly

    13. Notre plus haute priorité est de satisfaire le client en livrant rapidement et régulièrement des fonctionnalités à grande valeur ajoutée.

    14. Accueillez positivement les changements de besoins, même tard dans le projet.

    15. Livrez fréquemment un logiciel opérationnel avec des cycles de quelques semaines à quelques mois et une préférence pour les plus courts.

    16. Les utilisateurs ou leurs représentants et les développeurs doivent travailler ensemble quotidiennement tout au long du projet.

    17. Réalisez les projets avec des personnes motivées. Fournissez-leur l’environnement et le soutien dont elles ont besoin et faites-leur confiance pour atteindre les objectifs fixés.

    18. Privilégiez la co-location de toutes les personnes travaillant ensemble et le dialogue en face à face comme méthode de communication.

    19. Un logiciel opérationnel est la principale mesure de progression d’un projet.

    20. Les processus agiles encouragent un rythme de développement soutenable. Ensemble, les commanditaires, les développeurs et les utilisateurs devraient être capables de maintenir indéfiniment un rythme constant.

    21. Une attention continue à l’excellence technique et à un bon design.

    22. La simplicité – c’est-à-dire l’art de minimiser la quantité de travail inutile – est essentielle.

    23. Les meilleures architectures, spécifications et conceptions émergent d’équipes auto-organisées.

    24. À intervalles réguliers, l’équipe réfléchit aux moyens possibles pour devenir plus efficace. Puis elle s’adapte et modifie son mode de fonctionnement en conséquence.

5.4. Les valeurs agiles

Idées générales, qui précèdent aux principes.

Du manifesto lui-même :

  • Les individus et leurs interactions plus que les processus et les outils

  • Du logiciel qui fonctionne plus qu’une documentation exhaustive

  • La collaboration avec les clients plus que la négociation contractuelle

  • L’adaptation au changement plus que le suivi d’un plan

Ne pas oublier la petite phrase qui va avec :

Nous reconnaissons la valeur des seconds éléments, mais privilégions les premiers.
— http://agilemanifesto.org/

Du MOOC Agile :

  • Nouveau rôle pour le manager (rôle réduit)

  • Pas d’étapes trop tôt ou trop importante (longues)

  • Développement itératifs (et donc continu)

  • Nouvelle façon de négocier (trade off)

  • Focus sur la qualité (et donc les tests)

5.5. Les principes

Plusieurs types :

  • Organisationnels / Managériaux

  • Techniques

5.5.1. Les bons principes NON AGILES!

  • Process, procédure et méthodes (critique ⇒ statique, imposée, ruptures entre phases)

  • Insister sur les exigences et leur qualités (critiques ⇒ ils évoluent, on ne les livrent pas, ils sont souvent des solutions plus que des besoins)

5.5.2. Les principes organisationnels

  • Le client au centre

  • Accepte le changement

  • Laisser l’équipe s’organiser

  • Maintenir un rythme durable

5.5.3. Les principes organisationnels (suite)

  • Produire du logiciel "minimaliste"

    • Les fonctionalités requises

    • et uniquement elles

    • ne développer que le code et les tests

YAGNI: You Ain’t Gonna Need It

5.5.4. Les principes techniques

  • Développer itérativement

  • Mettre en avant les tests (TDD)

    • Non regression

    • Test first (TDD)

  • Exprimer les exigences comme des scénarios

    • User stories

5.6. Les rôles

  • Product Owner

  • SCRUM Master

  • Team

Mais où est passé le chef de projet?!
Dans Scrum ⇒ pas de chef !

5.6.1. L’auto-organisation (dans l’équipe)

  • Spécialistes mais pas que

  • Transversalité : n’importe qui peut prendre n’importe quelle tâche

  • Sélection collective des objectifs pour l’itération

  • Affectation collective des tâches

  • Démonstration collective des résultats

5.6.2. Product Owner

  • Interface avec le client

  • Défini les caractéristiques du produit (features)

  • Priorise les features

  • Participe aux présentations du produit

5.6.3. Scrum master

  • S’assure que l’équipe applique correctement la méthode

  • S’assure que l’équipe est fonctionnelle

  • Facilite la coopération

  • Protège l’équipe

  • Aide à résoudre les problèmes et blocages

5.7. Les pratiques

  • Plannings

  • Meetings & Scrums

  • Retrospectives

  • TDD

5.7.1. Plannings

  • Planning poker

  • Scrum of Scrums

  • Storyboards

5.7.2. Meetings

  • Daily meetings

    • Matin généralement

    • "Stand-up" (<15')

    • Tout le monde participe

    • Engagements/Empêchements

  • Planning meetings

    • Sprint Backlog

  • Retrospective meetings

    • Qu’est-ce qui a marché?

    • Qu’est-ce qui peut être amélioré?

  • Review meetings

    • On implique le client

5.7.3. Focus sur le Daily meeting

Les 3 questions classiques :

  • Qu’as-tu fait hier?

  • Que vas-tu faire aujourd’hui?

  • Vois-tu des obstacles à venir?

5.7.4. Développement

  • Pair programming

  • Mentor

  • Une seule base de code (vs. branching)

  • Code partagé

  • Garder l’optimisation pour la fin

  • Conception simple

  • Conception incrémentale

  • Refactoring

5.7.5. Release

  • Tôt et souvent

  • Continuous Integration

  • Petite, Incrémentale

  • Cycles hebdomadaires

5.7.6. Tests

  • Utiliser les standards

  • Systématiser les Tests Unitaires

  • TDD

5.8. Les artefacts

  • Product Backlog

  • User stories

  • Sprint Backlog

  • Burdown

5.8.1. Product Backlog

  • Propriété du Product Owner

  • Maintenu et "vivant" tout au long du projet

  • Ouvert

  • Contiens des backlog items

  • Inclue des estimations des business values

  • Inclue des estimations des efforts de développement

5.8.2. User stories

As a

…​

I want to

…​

So that

…​

Example of Story Card (source: https://mazoea.wordpress.com/agile/)

storycard

Bonne pratique (XP ⇒ INVEST):

  • Indépendante des autres histoires d’utilisateur (dans la mesure du possible)

  • Négociable (discutée avec l’équipe, notamment lors de l’estimation)

  • source de Valeur (porteuse d’une valeur client)

  • Estimable (elle peut être estimée par l’équipe)

  • (S)Courte (généralement une ou trois phrases environ)

  • D’une Taille appropriée (pouvoir être développée et testée au sein d’une itération)

Attributs :

  • Un numéro (Id)

  • Une importance/priorité client

  • Une estimation du coût (en points, temps, …​)

  • Une expression de la forme "En tant que …​ je souhaite …​ pour pouvoir …​

US
Figure 5. Exemple de carte pour User Story (http://www.agiliste.fr/guide-de-demarrage-scrum/)

Une activité populaire consiste à organiser les Stories sous forme d’une matrice et non d’une simple liste : c’est le Story Mapping.

Must, Should, Could, Wont ⇒ MoSCoW

storymap
Figure 6. Exemple de Story Map (http://www.agilegarden.fr/un-lancement-de-projet-ludique-et-productif/)

L’autre dimenstion de la matrice :

  • par flot (par dépendances entre stories)

mvp priorities
Figure 7. Exemple de Story Map par flot (http://blog.cayenneapps.com/2014/11/25/5-steps-to-building-minimum-viable-product-with-story-mapping/)

5.8.3. Storyboard

Example of storyboard (https://www.vikingcodeschool.com/software-engineering-basics/agile-development-with-xp-and-scrum)

agile story board

5.8.4. Vélocité

Attention, ce n’est pas une vitesse!
  • Number of items delivered

  • Burndown chart

5.8.5. Sprint Backlog

Juste un regroupement de User Stories, prisent dans le Product Backlog et traitées pour ce Sprint là.

5.8.6. Burndown

burndown inputs
Figure 8. Exemple d’inputs pour mon Burn-down (http://www.agiliste.fr/instruments-pilotage-projet/)
burndown
Figure 9. Exemple de Burn-down résultant (http://www.agiliste.fr/instruments-pilotage-projet/)

5.9. Le déroulement

anime scrum overview blue
Figure 10. Déroulement type d’une approche agile (http://scrumprimer.org)

6. Tests en intégration continue

J.-M. Bruel <jbruel@gmail.com> v1.0 2019-10-03 :imagesdir: images

6.1. Pourquoi tester ?

A majority of the production failures (77%) can be reproduced by a unit test.
— Yuan et al. OSDI 2014
tweet tests
Figure 11. Un tweet récent!

6.1.1. Pour livrer le bon produit

why1
Figure 12. Un produit qui fait ce qu’il est censé faire (crédit photo http://www.te52.com/testtalk/2014/08/07/5-reasons-we-need-software-testing/)

6.1.2. Ce qui marche pour 1 ne marche pas nécessairement pour 100

why2
Figure 13. Passage à l’échelle (crédit photo http://www.te52.com/testtalk/2014/08/07/5-reasons-we-need-software-testing/)

6.1.3. La loi de Murphy

Tout ce qui est susceptible de mal tourner tournera nécessairement mal.
— Edward A. Murphy Jr.
why3
Figure 14. Murphy’s law (crédit photo http://www.te52.com/testtalk/2014/08/07/5-reasons-we-need-software-testing/)

6.1.4. Différents OS ou différents terminaux

why4
Figure 15. Diversité (crédit photo http://www.te52.com/testtalk/2014/08/07/5-reasons-we-need-software-testing/)

6.1.5. Pour donner le meilleur

why5
Figure 16. Faire de son mieux (crédit photo http://www.te52.com/testtalk/2014/08/07/5-reasons-we-need-software-testing/)

6.2. Un exemple concret de test obligatoire

danAllen
Figure 17. Autour d'une bière avec Dan Allen, à Denver, Colorado #ILoveMyJob
  1. Fork the repository.

  2. Run bundle to install development dependencies.

  3. Create a topic branch

  4. Add tests for your unimplemented feature or bug fix. (See [writing-and-executing-tests])

  5. Run bundle exec rake to run the tests. If your tests pass, return to step 4.

  6. Implement your feature or bug fix.

  7. Run bundle exec rake to run the tests. If your tests fail, return to step 6.

  8. Add documentation for your feature or bug fix.

  9. If your changes are not 100% documented, go back to step 8.

  10. Add, commit, and push your changes.

  11. Submit a pull request.

6.3. Un exemple concret de documentation obligatoire

gaelBlondelle
Figure 18. Après un footing avec Gaël Blondel, à Saint-Malo #ILoveMyJob
[…​] an Eclipse project is providing extensible frameworks and applications accessible via documented APIs.
— Eclipse Development Process

6.4. Typologie des tests

Table 1. Différences entre Vérification et Validation (source https://www.tutorialspoint.com/software_testing/software_testing_quick_guide.htm)

Vérification

Validation

Le produit est-il bon ?

Le produit est-il le bon ?

Are you building it right?

Are you building the right thing?

Réalisée par le développeur

Réalisée par le testeur

En premier

Après la vérification

6.5. JUnit etc.

6.5.1. Quoi tester ?

Les exceptions

@Test (expected = Exception.class)

Le temps d’exécution

@Test(timeout=100)

Uniquement certains environnement

System.getProperty("os.name").contains("Linux")); Attention cette instruction n’est pas une annotation.

S’exécute avant les autres tests (e.g., accès à une base)

@BeforeClass public static void method()

6.5.2. Assertions

fail([message])

On force le test à échouer

assertTrue([message,] condition)

La condition est vraie

assertFalse([message,] condition)

La condition est fausse

assertEquals([message,] attendu, actuel)

Les deux valeurs sont égales

assertNull([message,] object)

Objet nul

assertSame([message,] expected, actual)

Objets identiques (même réf.)

6.5.3. Stratégie de tests

Considérons une fonction int add(int,int); d’une classe myClass.

Définir le comportement normal de la fonction (sortie normale pour des paramètres corrects).

//for normal addition
@Test
public void testAdd1Plus1() {
  int x  = 1 ; int y = 1;
  assertEquals(2, myClass.add(x,y));
}

Ajouter des tests pour les cas particuliers :

  • aucune exception non capturée en cas d'overflow

  • les paramètres null sont gérés, e.g., :

//if you are using 0 as default for null, make sure your class works in that case.
@Test
public void testAdd1Plus1() {
  int y = 1;
  assertEquals(1, myClass.add(null,y));
}

6.5.4. L’ordre des tests

Surtout aucun!!

JUnit assumes that all test methods can be executed in an arbitrary order. Well-written test code should not assume any order, i.e., tests should not depend on other tests.
— JUnit manual

6.5.5. Sous Eclipse

  • Pour une classe existante : Right-click (dans le Package Explorer et New  JUnit Test Case).

  • Utiliser le JUnit wizards (File  New  Other…​  Java  JUnit).

  • Il n’y a plus qu’à faire Run-as  JUnit Test.

Pensez à utiliser le plug-in infinitest.

6.5.6. Et pour les interfaces graphiques?

Exemple de la librairie Robot :

Robot bot = new Robot();
bot.mouseMove(10,10);
bot.mousePress(InputEvent.BUTTON1_MASK);
//add time between press and release or the input event system may
//not think it is a click
try{Thread.sleep(250);}catch(InterruptedException e){}
bot.mouseRelease(InputEvent.BUTTON1_MASK);

Exemple du plugin Eclipse swingcoder :

swingTest
Figure 19. Simulation d’utilisation d’interface (source https://marketplace.eclipse.org/content/swingcorder)

6.5.7. Couverture des tests

Il existe des outils pour aller plus loin :

coverage
Figure 20. Couverture des tests (source http://www.eclemma.org/)

6.6. Application concrète pour ce cours

6.6.1. De To Be Done à On going

Table 2. Mettre à jour votre gestionnaire de tâche

tuleap1

tuleap2

tuleap3
Figure 21. Confirmation par email

6.6.2. Créer une branche spécifique (si nouvelle feature)

bruel (master) $ git checkout -b US-15378
Switched to a new branch 'US-15378'
bruel (US-15378) $

6.6.3. Ecrire un test qui échoue

Etape 0 : Bien comprendre ce qu’on doit faire

Exemmple : Objectif de la tâche = créer une classe Pile.

Rappels sur les propriétés d’une Pile (opérations)
Opérations
CréerPile :  -> Pile
estVide   :  Pile ->  Booléen
Empiler   :  Pile * Elément -> Pile
Dépiler   :  Pile -> Pile
Sommet     :  Pile ->  Elément
Rappels sur les propriétés d’une Pile (préconditions)
Préconditions
Sommet(p) valide Si et Seulement Si estVide(p) == FAUX
Dépiler(p) valide Si et Seulement Si estVide(p) == FAUX
Rappels sur les propriétés d’une Pile (axiomes)
Axiomes
(1) estVide(CréerPile())
(2) estVide(Empiler(p,e)) == FAUX
(3) estVide(Dépiler(Empiler(p,e))) Si et Seulement Si estVide(p)
(4) Sommet(Empiler(p,e)) == e
(5) !estVide(p) => Sommet(Dépiler(Empiler(p,e))) == Sommet(p)
Etape 1 : Ecrire un test simple
import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.api.Test;

public class PileTest {

		@Test
		public void test_type_new_Pile() throws Exception {
                Pile pile = new Pile() ;

                assertEquals("Pile", pile.getClass().getName(),"new Pile() retourne une Pile");
        }
}
pile2
Figure 22. Oups, JUnit n'est pas dans le path...
pile3
Figure 23. Création rapide de la classe `Pile`
pile1
Figure 24. Run as JUnit Tests
Etape 2 : écrire un test qui passe
@Test
public void test_type_empiler() throws Exception {
  Pile pile = new Pile() ;

  assertEquals("Pile", pile.empiler("XXX").getClass().getName(),"empiler(pile,'XXX') retourne une Pile");
}
pile4
Figure 25. Erreur de syntaxe
public class Pile {

	public Object empiler(String string) {
		// TODO Auto-generated method stub
		return this;
	}
}

La méthode générée par défaut retourne null ce qui provoque une NullPointerException. Nous avons modifié la méthode en conséquence.

public class Pile {

	public Pile empiler(String string) {
		// TODO Auto-generated method stub
		return new Pile();
	}

}
Etape 2 : écrire un test qui échoue
@Test
	public void test_axiome1() {
		  Pile pile = new Pile() ;

		  assertTrue(pile.estVide(pile),"Une nouvelle pile est vide");
		}
Méthode ajoutée par défaut
	public boolean estVide(Pile pile) {
		// TODO Auto-generated method stub
		return false;
	}
pile7
Figure 26. Passage du test

Junit n’exécute que les fonctions qui commencent pas @Test.

Etape 3 : On fait passer le test
public boolean estVide(Pile pile) {
  // Smartly modified by JMB to pass the test!
  return true;
}
pile8
Figure 27. Passage du test

Bien sûr le code n’est pas correcte pour l’instant (on s’en rendra compte dès les tests suivants)! Une meilleure solution pourrait être :

public class Pile {
	int count;
  ...
	public boolean estVide(Pile pile) {
		return (count == 0);
	}
}

6.6.4. Essai de merge pour voir si tout le reste marche encore

bruel (US-15378) $ git commit -am "Adding push feature. Tests OK"
[US-15378 78f3242] Adding push feature. Tests OK
 1 file changed, 2 insertions(+), 3 deletions(-)
bruel (US-15378) $ git checkout devs
Switched to branch 'devs'
bruel (devs) $ git merge US-15378

6.6.5. Commit & Push dans devs

bruel (devs) $ git commit -am "..."
...
bruel (devs) $ git push origin devs
...
bruel (devs) $ git branch -D US-15378
Deleted branch US-15378 (was f392a73).

6.6.6. De On going à Review

review
Figure 28. Penser à mettre à jour le tableau de bord