Exceptions et Assertions


Problème de Fiabilité

Tout programme comporte des erreurs (bugs) ou est susceptible de générer des erreurs (e.g suite à une action de l’utilisateur, de l’environnement, etc …).

La fiabilité d’un logiciel peut se décomposer en deux grandes propriétés :

  • la robustesse qui est la capacité du logiciel à continuer de fonctionner en présence d’événements exceptionnels tels que la saisie d’informations erronées par l’utilisateur
  • la correction qui est la capacité d’un logiciel à donner des résultats corrects lorsqu’il fonctionne normalement.

 

Exceptions

Comment traiter les Erreurs

Une première méthode consiste

  • à prévoir les érreurs possibles.
  • associer chaque erreur possible avec un code d’erreur
  • Lorsqu’une erreur est detectée, la fonction retourne le code correspondant

 

Chaque appel de fonction doit être vérifié. Un oubli peut entrainer d’autres erreurs pouvant avoir des conséquences tout en passant inaperçu.

Comment traiter les Erreurs


f = openFile(filename); // Comment signaler une erreur ?
                        

f = openFile(filename); // Comment signaler une erreur ?
if(f == null) {
    // il y a eu une erreur, mais laquelle?
}
                        

f = openFile(filename); // Comment signaler une erreur ?
if(f == null) {
    if(getLastError() == 12) {
        // fichier inexistant par exemple
    }
}
                        

f = openFile(filename); // Comment signaler une erreur ?
if(f == null) {
    if(getLastError() == 12) {
        // fichier inexistant par exemple
    }
}
else {
    // pas d'erreur
}
                        

autre exemple, la librarie FMOD en C. Toutes les fonctions retournent un code pour le resultat.


FMOD_RESULT result = FMOD_System_PlaySound(system, FMOD_CHANNEL_FREE, unSon, 0, NULL);
if(result = FMOD_OK) {
    // ok
} else if(result == FMOD_ERR_FILE_NOTFOUND) {
    ...
} else if(result == FMOD_ERR_INVALID_SPEAKER) {
    ...
}
                        

Exceptions Java

Java propose un mécanisme : les exceptions

  • Les différent types d’erreurs sont modélisés par des classes.
  • La gestion des erreurs est séparée des instructions normales du programme. Il y a un bloc d’essai et un bloc de traitement d’erreur.

 

Exceptions Java

Définition : Le terme exception désigne tout événement arrivant durant l’exécution d’un programme interrompant son fonctionnement normal.

En java, les exceptions sont matérialisées par des instances de classes héritant de la classe java.lang.Throwable.

A chaque évènement correspond une sous-classe précise, ce qui peut permettre d’y associer un traitement approprié.

Le bloc try

Définition : La clause try s’applique à un bloc d’instructions correspondant au fonctionnement normal mais pouvant générer des erreurs. (exemple : l’ouverture d’un fichier).


try {
    ...
    ...
}
                        

Attention : un bloc try ne compile pas si aucune de ses instructions n’est susceptible de lancer une exception.

Le bloc catch

Définition : La clause catch s’applique à un bloc d’instructions définissant le traitement d’un type d’erreur. Ce traitement sera lancé sur une instance de la classe d’exception passée en paramètre.


try{
    ...
}
catch(TypeErreur1 e) {
    ...
}
catch(TypeErreur2 e) {
    ...
}
                        

Blocs try-catch

Tout bloc try doit être suivit par au moins un bloc catch ou par un bloc finally (étudié plus loin).

Tout bloc catch doit être précédé par un autre bloc catch ou par un bloc try.

Un ensemble composé d’un bloc try et d’au moins un bloc catch est communément appelé bloc try-catch.

Lever une exception

Définition : Lorsqu’une instruction du bloc d’essai génère une erreur et y associe une exception, on dit qu’elle lève (lance) cette exception.

Définition : Lorsqu’un bloc de traitement d’erreur est déclenché par une exception, on dit qu’il traite (capture) cette exception.

Fonctionnement

Fonctionnement

Fonctionnement

Fonctionnement

run it!

Fonctionnement

Le fonctionnement d’un bloc try-catch est le suivant :

  1. si aucune des instructions du bloc d’essai ne lance d’exception, il est entièrement exécuté et les blocs de traitement d’erreur sont ignorés.
  2. si une des instructions du bloc d’essai lance une exception, alors toutes les instructions du bloc d’essai après elle sont ignorées et le premier bloc de traitement d’erreur correspondant au type de d’exception lancée est exécuté. Tous les autres blocs de traitement d’erreur sont ignorés.

Les Types d’Exceptions

Lever une exception

Définition : Les exceptions de type Error sont réservées aux erreurs qui surviennent dans le fonctionnement de la JVM. Elles peuvent survenir dans toutes les portions du codes.

  • OutOfMemoryError : survient lorsque la machine virtuelle n’a plus de place pour faire une allocation et que le GC ne peut en libérer.
  • NoSuchMethodError : survient lorsque la machine virtuelle ne peut trouver l’implémentation de la méthode appelée.
  • StackOverflowError : survient lorsque la pile déborde après une série d’appel récursif trop profond.
  • etc…

Les Types d’Exceptions

Lever une exception

Définition : Les exceptions de type RuntimeException correspondent à des erreurs qui peuvent survenir dans toutes les portions du codes.

  • ArithmeticException : division par zéro (entiers), etc …
  • IndexOutOfBoundsException : dépassement d’indice dans un tableau.
  • NullPointerException : référence null alors qu’on attendait une référence vers une instance.
  • etc…

int a = 0;
try {
    int x = 1 /a;
    System.out.println(" x = " + x); }
catch (ArithmeticException e)
{
    System.out.println("division par 0 : 1 / " + a);
}
                        

Les Types d’Exceptions

Lever une exception

Définition : On appelle exception contrôlée, toute exception qui hérite de la classe Exception et qui n’est pas une RuntimeException. Elle est dite contrôlée car le compilateur vérifie que toutes les méthodes l’utilisent correctement.

  • EOFException : fin de fichier …
  • FileNotFoundException : fichier inexistant.
  • ClassNotFoundException : erreur dans le chargement d’une classe.
  • etc…

Exceptions Controlées

Toute exception contrôlée, du JDK ou personnalisée, pouvant être émise dans une méthode doit être :

 

  • soit capturée dans cette méthode. Elle est alors lancée dans un bloc try auquel est associé un catch lui correspondant.
  • soit être indiquées dans le prototype de la méthode à l’aide du mot clé throws.

 

Exemple

On veut faire une fonction qui supprime le fichier “/temp.txt”

On peut utiliser la méthode Files.delete(Path p) documentation.


public static void f() {
    Path file = new File("/temp.txt").toPath();
    Files.delete(file);
}
public static void main(String[] args) {
f();
}
                        

public static void f() {
    Path file = new File("/temp.txt").toPath();
    Files.delete(file); // peut lancer une exception!!
                        // NoSuchFileException ou
                        // DirectoryNotEmptyException ou
                        // IOException ou
                        // SecurityException ou
}
public static void main(String[] args) {
f();
}
                        

public static void f() {
    Path file = new File("/temp.txt").toPath();
    try {
    	Files.delete(file);
    } catch (NoSuchFileException x) {
    	System.err.format("no such file: %s%n", file);
    }
}
public static void main(String[] args) {
f();
}
                        

public static void f() {
    Path file = new File("/temp.txt").toPath();
    try {
    	Files.delete(file);
    } catch (NoSuchFileException x) {
    	System.err.format("no such file: %s%n", file);
    }
    // Que ce passe-t'il si un autre type est lancé :
    // DirectoryNotEmptyException ou
    // IOException ou
    // SecurityException ou
}
public static void main(String[] args) {
f();
}
                        

public static void f() {
    Path file = new File("test.txt").toPath();
    try {
    	Files.delete(file);
    } catch (NoSuchFileException x) {
    	System.err.format("no such file: %s%n", file);
    } catch (IOException x) {
    	System.err.format("delete error: %s%n", x);
    }
}
public static void main(String[] args) {
f();
}
                        

public static void f() {
    Path file = new File("test.txt").toPath();
    try {
    	Files.delete(file);
    } catch (NoSuchFileException x) {
    	System.err.format("no such file: %s%n", file);
    } catch (IOException x) {
            // Les Exceptions DirectoryNotEmptyException
            // héritent de IOException donc seront capturées ici
    	System.err.format("delete error: %s%n", x);
    }

}
public static void main(String[] args) {
f();
}
                        

public static void f() {
    Path file = new File("test.txt").toPath();
    try {
    	Files.delete(file);
    } catch (NoSuchFileException x) {
    	System.err.format("no such file: %s%n", file);
    } catch (IOException x) {
    	System.err.format("delete error: %s%n", x);
    }

    // Les Exceptions SecurityException héritent de
    //  RuntimeException donc ce n'est pas obligatoire de les attraper
}
public static void main(String[] args) {
f();
}
                        

run it

Solution 2


public static void f() throws IOException, NoSuchFileException {
    Path file = new File("test.txt").toPath();
    Files.delete(file);
}
public static void main(String[] args) {
f();
}
                        

public static void f() throws IOException, NoSuchFileException {
    Path file = new File("test.txt").toPath();
    Files.delete(file);
}
public static void main(String[] args) {
f(); // peut jeter une exception
}
                        

public static void f() throws IOException, NoSuchFileException {
    Path file = new File("test.txt").toPath();
    Files.delete(file);
}
public static void main(String[] args) {
    try {
        f(); // peut jeter une exception
    }
    catch(IOException e) {
            // NoSuchFileException hérite de IOException
            // Donc sera capturée ici
        System.out.println(e);
    }
}
                        

public static void f() throws IOException //pas besoins de préciser les type dérivé
{
    Path file = new File("test.txt").toPath();
    Files.delete(file);
}
public static void main(String[] args) {
    try {
        f(); // peut jeter une exception
    }
    catch(IOException e) {
            // NoSuchFileException hérite de IOException
            // Donc sera capturée ici
        System.out.println(e);
    }
}
                        

public static void f() throws IOException
{
    Path file = new File("test.txt").toPath();
    Files.delete(file);
}
public static void main(String[] args) {
    try {
        f(); // peut jeter une exception
    } catch (NoSuchFileException x) {
        // C'est parfois quand même utile de spécialiser
        // la gestion des exceptions
        System.err.format("no such file: %s%n", file);
    }
    catch(IOException e) {
        System.out.println(e);
    }
}
                        

Exceptions Personnalisées

Exceptions Personnalisées

On peut définir ses propres exceptions en définissant une sous-classe de la classe Exception.


public class MonException extends Exception {
  private int x;
  public MonException(int x) {
    this.x = x;
  }
  public String toString() {
    return "Valeur incorrecte : " + x;
  }
}
                      

Lancer une exception

Définition : Pour lancer une exception, on peut utiliser la clause throw.


public class Main {
  public static void main (String[] args) {
    try {
      int size = Integer.parseInt(args[1]);
      if (size < 0) {
        throw new MonException(size);
      }
      int[] tab = new int[size] ;
      System.out.println("Taille du tableau :" + tab.length);
    } catch (MonException e) {
      System.err.println(e);
    }
  }
}

                      

Lancer une exception


public class Main {
  public static void main (String[] args) {
    try {
      int size = Integer.parseInt(args[1]);
      if (size < 0) {
        throw new MonException(size);
      }
      int[] tab = new int[size] ;
      System.out.println("Taille du tableau :" + tab.length);
    } catch (MonException e) {
      System.err.println(e);
    }
  }
}

                      

>> java TestTableau 3
Taille du tableau : 3
                      

>> java TestTableau -1
Valeur incorrecte : -1
                      

Exceptions Personnalisées


public class ExceptionCordiale extends Exception {
  public ExceptionCordiale(String err) {
    super("Je suis au regret de vous annoncer que "+err+". Veuillez nous excuser pour la gène occasionnée.");
  }
}
                      

Exceptions Personnalisées


class TestTableau {
  private static double sqrt(int i) {
    if(i < 0) throw new ExceptionCordiale("le nombre ne doit pas être négatif");
    return Math.sqrt(i);
  }
  public static void main (String args) {
    int i = Integer.parseInt(args[1]);
    System.out.println(sqrt(i));
  }
}
                      

  class TestTableau {
    private static double sqrt(int i) {
      if(i < 0) throw new ExceptionCordiale("le nombre ne doit pas être négatif"); // Problème, exception non traitée.
      return Math.sqrt(i);
    }
    public static void main (String args) {
      int i = Integer.parseInt(args[1]);
      System.out.println(sqrt(i));
    }
  }
                        

class TestTableau {
  private  static double sqrt(int i) throws ExceptionCordiale {
    if(i < 0) throw new ExceptionCordiale("le nombre ne doit pas être négatif");
    return Math.sqrt(i);
  }
  public static void main (String[] args) {
    int i = Integer.parseInt(args[1]);
    try {
      System.out.println(sqrt(i));
    } catch(Exception e) {
      System.out.println(e);
    }
  }
}
                        

class TestTableau {
  private  static double sqrt(int i) throws ExceptionCordiale {
    if(i < 0) throw new ExceptionCordiale("le nombre ne doit pas être négatif");
    return Math.sqrt(i);
  }
  public  static void main (String[] args) {
    try {
      int i = Integer.parseInt(args[1]); // Peut aussi lancer une exception
      System.out.println(sqrt(i));
    } catch(Exception e) {
      System.out.println(e);
    }
  }
}
                          

Le bloc finally

Définition : La clause finally définit un bloc d’instruction qui sera exécuté même si une exception est lancée dans le bloc d’essai. Elle permet de forcer la bonne terminaison d’un traitement en présence d’erreur, par exemple : la fermeture des fichiers ouverts.


try {
  ...
}
catch (...) {
  ...
}
finally {
  ...
}
                        

Le bloc finally

Le code de la clause finally sera toujours exécuté :

  • si la clause try ne lance pas d’exception : exécution après le try (même s’il contient un return).
  • si la clause try lance une exception traitée par un catch : exécution après le catch (même s’il contient un return).
  • si la clause try lance une exception non traitée par un catch : exécution après le lancement de l’exception.

 

Attention : un appel à la méthode System.exit() dans le bloc try ou dans un bloc catch arrête l’application sans passer par la clause finally

La clause finally : exemple.


class TestTableau b {

  public static void main (String[] args) {
    try{
      int x = Integer.parseInt(args[1]);
      if (x < 0) {  throw new MonException(x);  }
      System.out.println(x);
    }
    catch (MonException e) {
      System.err.println(e);
    }
    finally {
      System.out.println("Tout est bien qui ...");
    }
  }
}
                      

>> java TestTableau -1
Valeur incorrecte : -1
Tout est bien qui ...

                      

Pourquoi propager les exceptions ?

Plus une méthode est éloignée de la méthode main dans la pile d’exécution, moins elle a de vision globale de l’application. Une méthode peut donc avoir du mal à savoir traiter une exception.

Il est souvent difficile pour une méthode effectuant un petit traitement de décider si une exception est critique :

“Il vaut mieux laisser remonter une exception que de décider localement d’interrompre l’ensemble du programme.”

Comment faire remonter une exceptions ?

Pour traiter une exception, on distingue finalement trois méthodes :

  • Traitement local : l’exception est traitée dans la méthode.
  • Traitement délégué : l’exception, qui n’est pas traitée dans la méthode, est transmise à la méthode appelante.
  • Traitement local partiel : l’exception est traitée dans la méthode, puis re-lancée pour être transmise à la méthode appelante.

 

Traitement délégué



public void f() throws FileNotFoundException {
  monFichier = new FileInputStream("./essai.txt");
}
public void g() {
  try {
    f();
  } catch (FileNotFoundException e) {
    System.err.println("Traitement délégué à g");
  }
}

                      

Traitement local partiel



public void f() throws FileNotFoundException {
  try {
    monFichier = new FileInputStream("./essai.txt");
  } catch (FileNotFoundException e) {
    System.err.println("Traitement local par f");
    throw e;
  }
}
public void g() {
  try {
    f();
  } catch (FileNotFoundException e) {
    System.err.println("Traitement délégué à g");
  }
}
                      

Traitement local partiel 1



public void f() throws FileNotFoundException {
  try {
    monFichier = new FileInputStream("./essai.txt");
  } catch (FileNotFoundException e) {
    System.err.println("Traitement local par f");
    throw e;
  }
}
public void g() {
  try {
    f();
  } catch (FileNotFoundException e) {
    System.err.println("Traitement délégué à g");
  }
}
                      

Traitement local partiel 2



public void f() throws MonException {
  try {
    monFichier = new FileInputStream("./essai.txt");
  } catch (FileNotFoundException e) {
    System.err.println("Traitement local par f");
    throw new MonException();
  }
}
public void g() {
  try {
    f();
  } catch (MonException e) {
    System.err.println("Traitement délégué à g");
  }
}
                      

Aucun traitement?

Si une exception remonte jusqu’à la méthode main sans être traitée par cette méthode :

  • l’exécution du programme est stoppée.
  • le message associé à l’exception est affiché, avec une description de la pile des méthodes traversées par l’exception

 

Remarque : Seul le thread qui a généré l’exception non traitée meurt ; les autres threads continuent leur exécution.

Assertions

Principe de la programmation par contrats

Pour vérifier la correction d’un programme lors de son développement, il est utile de vérifier certains invariants/conditions à des endroits bien choisis du programme.

Cette approche a été formalisée et généralisée dans la programmation par contrats.

En Java, elle peut s’appuyer sur le mécanisme des assertions qui permet au programmeur de vérifier dynamiquement des conditions

Localisation des points critiques

Lors du développement d’une application, plusieurs points critiques peuvent être à l’origine de bug. On s’attachera à contrôler :

  • les pré-conditions, au début d’une méthode.
  • les post-conditions, à la fin d’une méthode.
  • les invariants de classe, qui doivent être préservés avant et après l’appel des méthodes de la classe.
  • les invariants de boucle.

 

Le mot clé assert

Java 1.4 introduit un nouveau mot-clé assert qui permet d’insérer dans le code des assertions qui :

  • exécutent une condition et vérifient qu’elle retourne true.
  • lancent une erreur AssertionError (fille de la classe Error), si la condition est false.

assert x < y ;

Une expression peut suivre l’assertion. Facultative, elle ne sert qu’a définir le message d’erreur lié à l’AssertionError.


assert x == 0 : "x n’est pas nul" ;

 

Les invariants logiques

Les invariants logiques sont tous les invariants induits naturellement par la logique sémantique du code. Sous forme d’assertions, ils permettent de se prémunir contre toute erreur dans les futures modifications.


coef = (x * x) / (1 + (x * x));
assert coef < 1 : "coef est compris entre 0 et 1";
assert 0 <= coef : "coef est compris entre 0 et 1";
                      

Les invariants de contrôle de flux

Les invariants de contrôle de flux sont essentiels au bon déroulement des programmes. Ils assurent le bon enchainement des instructions. Pour en vérifier le respect, il suffit de placer des assert sur des lignes qui ne devraient pas être atteintes


switch(x) {
  case -1 : ...
  case 0 : ...
  case 1 : ...
  case default : assert false : x;
}
                    

void foo() {
  for (...) {
    if (...) return;
  }
  assert false : "On devrait être sortie de foo";
}
                    

Les préconditions

Lorsque l’on code une méthode privée, on peut faire la supposition que toutes ses invocations respecteront certaines conditions implicites appelées préconditions.

Cette supposition est possible car on garde le contrôle sur tous les appels à cette méthode. L’ajout d’assert offrent une méthode simple pour vérifier le respect des préconditions.


private int updateEtu(Etudiant etu, int note) {
  assert 0 <= note && note <= 20 ;
  ...
}

Les postconditions

Les postconditions expriment des propriétés qui doivent être satisfaites en fin d’exécution d’une méthode. Elles permettent de vérifier que le traitement de la méthode s’est bien déroulé et que la valeur de retour est conforme aux spécifications.


public int sum (int a, int b) {

  int result = a + b ;

  return result;
}

public int sum (int a, int b) {

  int result = a + b ;
  assert result - a == b : "Sum of " + a + " + " + b + " returned " + result;
  return result;
}

public int sum (int a, int b) {
  assert (Integer.MAX_VALUE - a >= b) : a + " + " + b + " is too large.";
  int result = a + b ;
  assert result - a == b : "Sum of " + a + " + " + b + " returned " + result;
  return result;
}

Les invariants de classe

Imaginons que l’on construise une classe stockant des objets qui les garde toujours triés.


public void insert(Object o) {
assert this.isSorted() : "Il y a du désordre ici";
...
}


public void add(Object o) {
  int _old_size = maCollection.size();
  this.maCollection.add(o);
  assert this.maCollection.size() == _old_size + 1;
}

Exécution avec ou sans assertions

Les vérifications liée aux assertions, utiles en période de test, ralentissent l’exécution de l’application. Elles sont donc désactivées par défaut.

Pour activer les assertions (sauf dans les classes système) :
java -ea MonProgrammeAvecAssert

Pour activer seulement pour une classe :
java -ea:NomDuneClasse MonProgrammeAvecAssert

Pour activer seulement pour un paquetage et ses sous-paquetages (les … sont à mettre tels quels) :
java -ea:nom.dun.package... MonProgrammeAvecAssert

Pour désactiver seulement pour un paquetage (ou une classe) :
java -ea -da:nom.dun.package... MonProgrammeAvecAssert

 

Mauvais pratiques

Une mauvaise pratique avec les assertions, peut être la plus courante, consiste à les utiliser pour vérifier une précondition sur une méthode public.

En effet le contrat ne sera pas vérifié quand les assertions seront désactivées en production, or le programmeur n’a pas le contrôle sur toutes les invocations de la méthode. Dans ce cas il vaut mieux utiliser les exceptions à la place des assertions.

Mauvais pratiques


public float inverseAuCarre (int x) {
  assert x == 0 : "Le paramètre est nul";
  ...
}

NON

Bonnes pratiques


public float inverseAuCarre (int x) {
  if (x == 0) {
    throw new ArithmeticException("Le paramètre est nul");
  }
  ...
}

Oui!!

Mauvais pratiques


assert connect() : "La connection est impossible";

NON. En effet que se passe-t-il si on désactive les assertions?

Bonnes pratiques


  final boolean ok = connect();
    assert ok : "La connection est impossible";

mieux!

Mauvais pratiques


assert r1 == r2 : "r1 et r2 devraient être égaux";

NON. Attention à l’égalité entre nombres flottants.

Bonnes pratiques


assert abs(r1 - r2) < 0.0001 : "r1 et r2 devraient être égaux";

mieux!

Tests Unitaires

Dans les applications non critiques, l’écriture des tests a longtemps été considérée comme une tâche secondaire.

Maintenant, il est conseillé d’écrire des tests en même temps, voire même avant la méthode à tester (Test Driven Development)

Ici, on s’intéresse au tests unitaires qui permettent la vérification des fonctions une par une.

Définition : La couverture de code consiste à s’assurer que le test conduit à exécuter l’ensemble des instructions présentes dans le code à tester.

Le but est de créer des tests qui permettent d’exécuter le plus d’instructions possibles.

JUnit

Nous allons utiliser le framework JUnit 4

JUnit et les annotations



import org.junit.*;
import static org.junit.Assert.*; // import toutes les fonctions static de la class org.junit.Assert
public class TestFoobar {
  @BeforeClass
  public void setUpClass() {
    // Code exécuté avant l’exécution du premier test
    // et de la premiere exécution de la méthode @Before
  }
  @AfterClass
  public void tearDownClass() {
    // Code exécuté après l’exécution des tous les tests
  }
  @Before
  public void setUp() {
    // Code exécuté avant chaque test
  }
  @After
  public void tearDown() {
    // Code exécuté après chaque test
  }
  @Test
  public void test() {
    assertTrue(true);
  }
}

Assertions JUnit

Avec JUnit, la plus petite unité de tests est l’assertion dont le résultat de l’expression booléenne indique un succès ou une erreur. Il définit une série d’assertion nommées assertXXX() qui sont héritées de la classe junit.framework.Assert :

  • assertTrue() : vérifie que la valeur fournie en paramètre est vraie
  • assertFalse() : vérifie que la valeur fournie en paramètre est fausse
  • assertNull() : vérifie que l’objet fourni en paramètre soit null
  • assertNotNull() : vérifie que l’objet fourni en paramètre ne soit pas null
  • assertSame() : vérifie que les deux objets fournis en paramètre font référence à la même entité
  • assertNotSame() : vérifie que les deux objets fournis en paramètre ne font pas référence à la même entité
  • assertEquals() : vérifie l’égalité de deux valeurs de type primitif ou objet (en utilisant la méthode equals())

Exemple


import org.junit.*;
import static org.junit.Assert.*;
public class TestPerson {
  private Person p1;
  private UPMCPerson p2;


  @Before
  public void setUp() {
    p1 = new Person("alice", 1977) ;
    p2 = new UPMCPerson("bob", 1978) ;
  }
  @After
  public void tearDown() {
    p1 = null;
    p2 = null;
  }
  @Test
  public void testGetName() {
    assertEquals("person should have the given name", p1.getName(), "alice");
    assertEquals("person should have the given name", p2.getName(), "bob");
  }
  @Test
  public void testEquals() {
    assertFalse("different person should be different", p1.equals(p2));
  }
}

Exécution de JUnit


javac -cp .:junit-4.XX.jar TestPerson.java

java -cp .:junit-4.XX.jar:hamcrest-core-1.3.jar org.junit.runner.JUnitCore TestPerson

Exemple 2


import org.junit.*;
import static org.junit.Assert.*;
public class SampleTest {
  private java.util.List emptyList;

  @Before
  public void setUp() {
    emptyList = new java.util.ArrayList();
  }
  @After
  public void tearDown() {
    emptyList = null;
  }
  @Test
  public void testSomeBehavior() {
    assertEquals("Empty list should have 0 elements", 0, emptyList.size());
  }
  @Test(expected=IndexOutOfBoundsException.class)
  public void testForException() {
    Object o = emptyList.get(0);
  }
}

Commentaires et Documentation

Les commentaires

Les commentaires sont des portions du code source ignorées par le compilateur ou l’interpréteur, car ils ne sont pas nécessaires à l’exécution du programme. Ils sont souvent utilisés pour expliquer : la structure code, le fonctionnement des algorithmes, le sens des variables, les optimisations, …

Un commentaire ne doit pas paraphraser le code. Il est inutile si le code est déjà assez clair.

Les Tags

Il existe deux types de tags :

  • @tag : les tags standards
  • {@tag} : les tags qui seront remplacés par une valeur.

 

Ces tags peuvent être utilisés pour documenter :

  • globalement le programme (fichier overview.html)
  • des packages (fichier package.html ou package-info.java)
  • des classes et interfaces
  • des constructeurs et méthodes
  • des attributs

 

packages, classes et interfaces

Pour documenter les packages, on peut utiliser :

  • @author texte : précise le ou les auteur(s)
  • @version texte : numéro de version du packages

 

Pour documenter les classes et interfaces, on peut utiliser :

  • @author texte : précise le ou les auteur(s)
  • @version texte : numéro de version de l’élément
  • @deprecated texte : permet de préciser qu’un élément est déprécié.

 

Méthodes et constructeurs

 

  • @version texte : numéro de version de l’élément
  • @deprecated texte : permet de préciser que la méthode est dépréciée
  • @param nom_paramètre description du paramètre : défini un paramètre d’une méthode
  • @exception nom_exception description : exception pouvant être levée par une méthode
  • @throws nom_exception description : identique à @exception
  • @return texte : description de la valeur de retour de la méthode
  • {@inheritDoc} : recopie de la documentation de l’entité de la classe mère

I I I I I I I 

Les tags génériques

En plus des tags spécifiques aux différents éléments, on peut utiliser :

  • @docRoot : chemin relatif de la racine de la documentation
  • {@link} : insère un lien vers un élément de la documentation
  • {@linkplain} : identique à link dans une police normale
  • @see entité : référence à un autre élément
  • @since texte : version où l’élément a été ajouté
  • {@literal texte} : permet d’insérer plus facilement les caractères spéciaux sans les interpréter

 

Remarque

Les textes correspondant aux différents tags sont libres, mais les caractères liés au HTML comme < ou > sont interprétés spécialement par javadoc. Pour écrire <, on doit donc utiliser &lt;.

Génération de la documentation

Pour générer la documentation, on utilise la commande javadoc suivie d’une liste de fichiers java.

Les options les plus couramment utilisées :

  • -d <dir> : permet de choisir le répertoire dans lequel sera générée la documentation ;
  • -author : permet de prendre en compte les tag @author ;
  • -version : permet de prendre en compte les tag @version ;
  • -public : limite la documentation aux classes publiques ;
  • -protected : limite la documentation aux classes et aux membres publics et protégés (par défaut) ;
  • -package : limite la documentation aux classes et aux classes et aux membres publics, protégés et ceux accessibles aux classes d’un même package ;
  • -private : génère la documentation pour toutes les classes et tous les membres.

 

Attention, pour utiliser correctement les accents avec la javadoc, il faut préciser les encodages avec les options suivantes :

  • -encoding : spécifie l’encodage utilisé dans les sources java.
  • -docencoding : spécifie l’encodage qui sera utilisé pour générer les fichiers de documentation HTML.
  • -charset : permet d’ajouter dans le code source HTML l’header Content-Type avec l’encodage spécifié, ce qui permet au navigateur d’utiliser directement le bon encodage.

 

Pour obtenir une doc HTML en utf8 avec des sources en latin1 :


javadoc -encoding ISO-8859-1 -docencoding utf8 -charset utf8 *.java

Exemple : Documentation d’un package

Depuis Java 5.0 on peut utiliser directement la syntaxe javadoc pour documenter les packages.
Pour cela, il faut ajouter dans le répertoire correspondant au package un fichier package-info.java qui contient une seule ligne java package <nomPackage> ;.


  /**
  * Package contenant un super jeu
  * @author Quentin Bramas
  * @version 2.0
  */
  package supergame;

 

Exemple : Documentation d’une classe


package supergame ;
/**
* Classe générique pour une IA
* @see wikipedia
* @author Quentin Bramas
* @version 1.00
*/
public class IA {

  int pos=0;
  /**
  * Méthode simulant la course de l'IA
  * @param dist distance parcourue par l'IA
  * @return retourne la position actuelle
  */
  public int courir (int dist) {
    return pos += dist ;
  }
}
                      

Exemple : Documentation d’une classe


package supergame ;

/**
* Classe d'IA difficile
* @author Quentin Bramas
* @version 1.00
*/
public class IAHard extends IA {
  private boolean visible = true ;
  /**
  * Teste la visibilité de l'IA
  * @return true si l'IA est visible
  */
  public boolean isVisible () {
    return visible ;
  }
  /** {@inheritDoc} et cache l'IA */
  @Override
  public int courir (int dist) {
    this.hide();
    return super.courir(dist);
  }
  /**
  * cache l'IA (du coup elle est vachement difficile à battre)
  */
  private void hide() {
    this.visible = false;
  }
}
                      

Commande javadoc


javadoc -author -private -version  -encoding utf8 -docencoding utf8 -charset utf8 -d doc supergame/*.java