Bonne pratique de développement JUnit

Résolu/Fermé
Jithel Messages postés 843 Date d'inscription mercredi 20 juin 2018 Statut Membre Dernière intervention 31 août 2021 - Modifié le 7 nov. 2018 à 14:50
Jithel Messages postés 843 Date d'inscription mercredi 20 juin 2018 Statut Membre Dernière intervention 31 août 2021 - 7 nov. 2018 à 16:14
Bonjour,

J'aurais une question vis-à-vis des bonnes pratiques de développement pour les tests unitaires avec JUnit. Bien que je n'ai trouvé aucun article à ce sujet (il existe sans doute des livres), je suis face à un problème :

Il est souvent nécessaire d'écrire plusieurs tests pour un même bloc unitaire (que l'on appellera ici fonction). Plutôt que de tout mettre dans une fonction de test, il est judicieux d'en écrire plusieurs pour avoir une meilleure séparation des tests et un meilleur suivi en cas d'échec de l'un d'eux.

Exemple :
class Addition {
  public int additionner(int a, int b){
    return a + b;
  }
}

class AdditionTest {
  private Addition addition;

  @Before
  public void setup(){
   this.addition = new Addition();
  }

  @Test
  public void additionnerDeuxPositifs(){
    // Test addition deux nombres positifs
    assertEqual(2, this.addition.additionner(1,1);
  }

  @Test
  public void additionnerDeuxNegatifs(){
    // Test addition deux nombres négatifs
    assertEqual(-2, this.addition.additionner(-1, -1);
  }

  @Test
  public void additionnerPositifEtNegatif(){
    // Test addition un nombre négatif et positif
    assertEqual(0, this.addition.additionner(-1, 1);
  }
}


Maintenant, considérons que je développe 5 tests unitaires par fonction pour une classe qui contient une dizaine de fonction. Cela fait une cinquantaine de tests unitaires dans la même classe. Ne serait-il pas plus judicieux de créer une classe par test de fonction ? Plutôt que d'avoir une classe avec 50 tests unitaires, ne serait-il pas mieux d'avoir 10 classes avec 5 tests unitaires ? Par "mieux", j'entend la maintenance et l'évolutivité du code.

Exemple :
class Operation {
  public int additionner(int a, int b){
    return a + b;
  }

  public int soustraire(int a, int b){
    return a - b;
  }
}

class OperationAdditionnerTest{
   private Operation operation;

  @Before
  public void setup(){
   this.operation = new Operation();
  }

  @Test
  public void additionnerDeuxPositifs(){
    // Test addition deux nombres positifs
    assertEqual(2, this.addition.additionner(1,1);
  }

  @Test
  public void additionnerDeuxNegatifs(){
    // Test addition deux nombres négatifs
    assertEqual(-2, this.addition.additionner(-1, -1);
  }

  @Test
  public void additionnerPositifEtNegatif(){
    // Test addition un nombre négatif et positif
    assertEqual(0, this.addition.additionner(-1, 1);
  }
}

class OperationSoustraireTest{
   private Operation operation;

  @Before
  public void setup(){
   this.operation = new Operation();
  }

  @Test
  public void soustraireDeuxPositifs(){
    // Test soustraction deux nombres positifs
    assertEqual(0, this.operation.soustraire(1,1);
  }

  @Test
  public void soustraireDeuxNegatifs(){
    // Test soustraction deux nombres négatifs
    assertEqual(0, this.operation.soustraire(-1, -1);
  }

  @Test
  public void soustrairePositifEtNegatif(){
    // Test soustraction un nombre négatif et positif
    assertEqual(-2, this.operation.soustraire(-1, 1);
  }
}


Il y a des inconvénients à faire ça : nom de classe trop long pour les décrire précisément, peut-être problème de performance (car 10 instances de classe plutôt qu'une), des classes parfois trop courtes (si j'écris un test unitaire pour une fonction).




A voir également:

1 réponse

KX Messages postés 16734 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 24 avril 2024 3 015
7 nov. 2018 à 15:06
Bonjour,

Je pense que chacun doit voir midi à sa porte, il y a différents "courants de pensées", notamment les puristes qui te diront qu'il faut tout atomiser au plus fin possible, en ce qui me concerne je serais plus pragmatique, les qualités que tu cites "maintenance et l'évolutivité" ne sont pas à négliger, loin de là.

Toutefois, je garderais une seule classe de test par classe de code, parce que lorsque tu utilises des outils d'analyse des résultats de tests, on s'y retrouve plus facilement. Sachant qu'il n'y a pas que des tests unitaires, il y a aussi les tests d'intégrations, moins atomiques mais avec des scénarios plus complexes.

Remarque : dans ton petit code exemple il y aurait déjà bien des choses à améliorer et tester ;-)
1
Jithel Messages postés 843 Date d'inscription mercredi 20 juin 2018 Statut Membre Dernière intervention 31 août 2021 171
7 nov. 2018 à 15:10
Un des autres avantages de cette méthodologie que j'ai présenté, c'est que l'on peut séparer tests unitaires et tests d'intégration. En effet, les tests unitaires sont dans les classes qui porte le nom de la méthode associée (par exemple, OperationAdditionnerTest) et les tests d'intégration (scénarios) serait dans la classe qui porte le nom de la classe cible (OperationTest par exemple).

J'aurais voulu avoir une réponse sur l'aspect performance aussi notamment. Mieux vaut créer une classe de 50 méthodes ou 10 classes de 5 méthodes ?
0
KX Messages postés 16734 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 24 avril 2024 3 015 > Jithel Messages postés 843 Date d'inscription mercredi 20 juin 2018 Statut Membre Dernière intervention 31 août 2021
7 nov. 2018 à 15:32
Dans les conventions de nommage, la classe de tests d'intégration est suffixée par IT (Integration Test), tu aurais donc AdditionTest d'une part et AdditionIT de l'autre (même si la plupart du temps on aura que l'une des deux car ce ne sont pas les mêmes classes qui sont testées en unitaires et en intégration).

En terme de performance ça ne changera rien, 1 classe de 50 méthodes ou 10 classes de 5 méthodes, ça fera toujours 50 tests à exécuter... Il faut surtout penser à la lisibilité de l'analyse qui en ressort et avoir un rapport par classe de code ça permet de mieux comprendre les résultats.

Exemple d'illustration avec Jenkins :



Exemple d'illustration avec Sonar :

0
Jithel Messages postés 843 Date d'inscription mercredi 20 juin 2018 Statut Membre Dernière intervention 31 août 2021 171
Modifié le 7 nov. 2018 à 15:43
Encore une fois, ça améliorera la lisibilité de l'outil si j'ai 10 classes à la place d'une seule ? On comprendra mieux l'origine de l'échec d'un test si on lit OperationAdditionnerTest a échoué un test plutôt que OperationTest a échoué un test.
0
KX Messages postés 16734 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 24 avril 2024 3 015 > Jithel Messages postés 843 Date d'inscription mercredi 20 juin 2018 Statut Membre Dernière intervention 31 août 2021
7 nov. 2018 à 16:12
Cela dépend des outils, mais si on parle de Sonar par exemple, il regroupera de toute façon les résultats par classe de code, peu importe le nombre de classes de tests qui sont derrière.
En terme de lisibilité j'aurait donc un test qui plante pour Operation, mais il faudra farfouiller dans le détail pour savoir si c'est un test de OperationTest ou OperationAdditionnerTest qui a planté dans le cas où tu ais les deux.
Et en terme de maintenance, il est plus simple de penser à changer une classe qu'à en changer plusieurs.

Remarque : J'ai déjà eu des problèmes avec des agrégation de rapport Test+IT (sur une version boguée de JaCoCo) et ça aurait sûrement été la même chose avec une agrégation de deux rapports Test d'une même classe, même si ça ne change rien en terme de performance, c'est quand même plus complexe d'avoir plusieurs classes.
0
Jithel Messages postés 843 Date d'inscription mercredi 20 juin 2018 Statut Membre Dernière intervention 31 août 2021 171
7 nov. 2018 à 16:14
OK merci pour ton temps et ton opinion. Sujet résolu
0