System.exit et test unitaire

Comme beaucoup de « bons » programmeurs, vous prenez soins d’écrire des tests unitaires pour vos applications. vous vous assurez qu’elle a toujours le comportement attendu. Et puis un jour, c’est le drame, il faut tester

Si le fichier de configuration est incorrect, l’application retourne -1, si une erreur inconnue se produit, elle retourne -2 et si tout se passe bien, elle retourne 0.

Mais comment faire? En effet, si on laisse ceci se produire, le test s’arrête, l’intégrateur maven ne comprend plus rien et des pandas sont morts.

Je ne sais pas vous, mais personellement, pendant tout un temps, j’ai agit de deux manières possibles. Soit j’exportai le main dans une méthode secondaire, méthode qui retournait un int. Mon main au final ressemblait à ça

1
2
3
public static void main (String argv){
    System.exit(realMain(argv));
}

et il me suffisait dans le test unitaire de vérifier la sortie de realMain suivant les conditions. Bref je laisse la crasse à realMain, débrouille toi pour gérer la sortie. Pas top mais efficace sur les programmes en ligne de commande. Et bien sûr, inutilisable sur une application graphique, multithreadée ou extrêmement complexe.
L’autre option me restant étant le trop classique

Ce n’est pas possible alors on teste pas.

La vache, qu’est-ce que je ne l’ai pas déjà utilisée cette excuse :)

Mais alors?

Et bien aujourd’hui j’ai enfin trouvé une solution qui m’attendais depuis des années. La bibliothèque http://stefanbirkner.github.com/system-rules/ s’adapte parfaitement à junit et permet de tester les interactions avec la classe System de java.
Mettons que j’ai une classe Main, avec un main prenant en argument un fichier. Si tout va bien, l’application fait son travail normalement. Si le fichier n’existe pas, l’application fait un System.exit(-1). Et bien, le code du test est finalement très simple:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package be.meteo.get;

import static org.junit.Assert.*;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.ExpectedSystemExit;

public class MainTest {

    @Rule
    public final ExpectedSystemExit exit = ExpectedSystemExit.none();

    @Test
    public void testNormal() {
       
        Main.main(new String[] { "fichierQuiExiste" });
        // tout un tas d'assert pour être sur qu'on a bien fonctionné
    }

    @Test
    public void testWrongConfig()  {
        String config = "/jenexistepas";
        exit.expectSystemExitWithStatus(-2);
        Main.main(new String[] { config });
    }


}

Et voilà, ça marche.
Petite particularité. Même si le System.exit n’arrête plus la jvm, il arrêt toujours le test (ici avec un succès ou une erreur suivant ce qu’on a mis dans la règle). Ce qui a pour conséquence de rendre impossible des assert après l’appel faisant le System.exit (ici, mon main). Si vous devez tester des choses supplémentaires (par exemple, que le programme a bien créé un fichier /var/log), il faut le rajouter dans un bloc finally:

1
2
3
4
5
6
7
8
9
10
@Test
public void testWrongConfig()  {
    String config = "/jenexistepas";
    exit.expectSystemExitWithStatus(-2);
    try{
        Main.main(new String[] { config });
    } finally {
        Assert.assertTrue(new File("/var/log/myapp.error").exists());
    }
}

Bon code à tous.

Une réflexion au sujet de « System.exit et test unitaire »

  1. Ping : Recap java, semaine 36, année 2012 | Blog de la rubrique java

Laisser un commentaire