User Tools

Site Tools


2019_2020:s3:methodo:td:env1:tests

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
2019_2020:s3:methodo:td:env1:tests [2019/08/25 12:05]
blay [Retour sur l'indépendance des tests]
2019_2020:s3:methodo:td:env1:tests [2019/08/25 16:24] (current)
blay [Eclipse et couverture de tests]
Line 2: Line 2:
  
 ====== Tests en Java - Rappel ou Apprentissage ====== ====== Tests en Java - Rappel ou Apprentissage ======
-Voir pour plus de détail le livre p. 96 de ISBN 978-3-030-24094-3,​ Introduction to Software Design with Par  Robillard, Martin P. +
-Il devrait être disponible à la bibliothèque et vous trouverez une version électronique en ligne.+<note warning>Voir pour plus de détail le livre à partir de la page 96 de ISBN 978-3-030-24094-3,​ Introduction to Software Design with Par Robillard, Martin P. 
 + 
 +Il devrait être disponible à la bibliothèque et vous trouverez une version électronique en ligne. ​(voir plus bas dans la catégorie référence). 
 + 
 +**L'​essentiel des éléments qui suivent en sont une simple traduction.** 
 + 
 +</​note>​
  
-Beaucoup des éléments qui suivent en sont une simple traduction. 
  
 ===== Structuration d'une suite de tests ===== ===== Structuration d'une suite de tests =====
Line 25: Line 30:
 ==== Retour sur l'​indépendance des tests ==== ==== Retour sur l'​indépendance des tests ====
  
-En utilisant l'​annotation @Before, nous indiquons à JUnit d'​exécuter la méthode avant l'​exécution de tout test. De même, il est également possible d'​utiliser la notation @After Après une notation annotation pour marquer une méthode qui doit être exécutée après chaque test (par exemple pour libérer certaines ressources). Bien sûr, les objets immuables n'ont pas besoin d'​être réinitialisés,​ ils peuvent donc être stockés comme champs statiques de la classe. ​+En utilisant l'​annotation @BeforeEach, nous indiquons à JUnit d'​exécuter la méthode avant l'​exécution de tout test. De même, il est également possible d'​utiliser la notation @After Après une notation annotation pour marquer une méthode qui doit être exécutée après chaque test (par exemple pour libérer certaines ressources). Bien sûr, les objets immuables n'ont pas besoin d'​être réinitialisés,​ ils peuvent donc être stockés comme champs statiques de la classe. ​ 
 + 
 +=== Test fixture === 
 +Une fixture est un morceau de code qui permet de fixer un environnement logiciel pour exécuter des tests logiciels. Cet environnement constant est toujours le même à chaque exécution des tests. Il permet de répéter les tests indéfiniment et d'​avoir toujours les mêmes résultats. (Wikipedia) 
 + 
  
 <code java> <code java>
Line 36: Line 46:
  
 public class TestFoundationPile { public class TestFoundationPile {
 +                // Test Fisture
  private static final Card ACE_CLUBS = Card.get(Rank.ACE,​ Suit.CLUBS);​  private static final Card ACE_CLUBS = Card.get(Rank.ACE,​ Suit.CLUBS);​
  private static final Card TWO_CLUBS = Card.get(Rank.TWO,​ Suit.CLUBS);​  private static final Card TWO_CLUBS = Card.get(Rank.TWO,​ Suit.CLUBS);​
Line 56: Line 67:
 } }
 </​code>​ </​code>​
-1. Robert C. Martin. Clean Code: A Handbook of Agile Software Craftmanship. Prentice Hall, 2009.+ 
 +===== Couverture des tests ===== 
 + 
 +Il n'est pas toujours physiquement possible de tester de façon exhaustive l'​espace d'​entrée d'un projet. Par exemple, pour tester toutes les comparaisons de deux cartes d'un jeu de 54 cartes, ​ il faudrait faire 1431 tests (ce qui reste faisable), mais pour tester les positions sur un jeu de dames, il y a 443 748 401 247 positions. Même avec un CPU de pointe, tester une méthode ​ pour toutes les positions possibles cela prendrait beaucoup trop de temps et ceci est tout à fait incompatible avec l'​exigence selon laquelle les tests unitaires doivent être exécutés "​rapidement"​. 
 + 
 +De toute évidence, nous devons choisir les tests à réaliser parmi toutes les possibilités (par exemple, sélectionner les configurations du jeu de dame à tester). ​ Le défi fondamental du problème de sélection des cas de test est de tester efficacement,​ c'​est-à-dire de trouver un ensemble minimal de cas de test qui nous fournisse une quantité maximale de "​test"​ pour notre code. Malheureusement,​ bien qu'il soit assez évident de savoir ce qu'est un nombre minimal de cas de test, il n'​existe pas de définition naturelle ou même convenue de ce qu'est une "​quantité de test".  
 + 
 +Une méthode classique pour déterminer ce qu'il faut tester est basée sur le concept de couverture. De manière informelle, une mesure de couverture de test est un nombre (généralement un pourcentage) qui détermine la quantité de code exécutée lorsque nous exécutons nos tests. Les mesures de couverture de test peuvent ensuite être calculées par des outils de couverture de code qui gardent la trace du code qui est exécuté lorsque nous exécutons des tests unitaires. Cela peut paraître simple, mais le problème est qu'il existe différentes définitions de ce que l'on peut entendre par "​code",​ dans le contexte des tests. Chaque définition est une façon différente de calculer "​combien de tests" sont effectués. Certaines organisations de développement de logiciels peuvent avoir des critères d'​adéquation des tests bien définis selon lesquels les suites de tests doivent atteindre certains seuils de couverture, mais dans de nombreux autres cas, les informations fournies par les mesures de couverture sont utilisées plus généralement **pour aider à déterminer où investir les futurs efforts de test**. Voici trois paramètres de couverture bien connus (il y en a beaucoup d'​autres,​ voir Lectures complémentaires). 
 + 
 +==== Couverture d'​instructions ==== 
 + 
 +La couverture des instructions est simplement le nombre d'​instructions exécutées par un test ou une suite de tests, divisé par le nombre d'​instructions dans le code d'​intérêt.  
 +<code java> 
 + public boolean canMove() { 
 + if( isEmpty() ) 
 + return false; 
 + else  
 + return true; 
 +
 +</​code>​ 
 +Le test suivant ne couvre alors que les premières instructions soit 60% du code de la méthode. 
 + 
 +<code java> 
 +@Test 
 + public void testCanMove_Empty() 
 +
 + aPile = new FoundationPile();​ 
 + assertFalse(aPile.canMove());​ 
 +
 +</​code>​ 
 + 
 + 
 + 
 +La logique qui sous-tend la couverture des instructions est simplement que si un défaut est présent dans une instruction qui n'est jamais exécutée, les tests ne vont pas aider à le trouver. Bien que cette logique puisse sembler attrayante, la couverture des relevés est en fait une mauvaise mesure de couverture. Une première raison est que cela dépend fortement de la structure détaillée du code. Nous pourrions réécrire la méthode ''​canMove''​ comme suit, et atteindre une couverture de test de 100% avec exactement les mêmes tests. 
 +<code java> 
 + public boolean canMove() { 
 + return !(isEmpty());​ 
 +
 +</​code>​ 
 +La deuxième raison est que toutes les instructions ne sont pas créées de la même manière, et il peut y avoir beaucoup de choses qui se passent dans une instruction,​ surtout si cette instruction implique une expression booléenne composée (comme c'est le cas de la première instruction dans le dernier exemple). 
 + 
 +==== Couverture des branches ==== 
 + 
 +La couverture des branches est le nombre de "​branches"​ du programme (points de décision) exécutées par le(s) test(s) divisé(s) par le nombre total de branches dans le code qui nous intéresse. Dans ce contexte, une branche est un résultat d'une condition. La couverture des branches est une mesure plus fiable que la couverture des instructions,​ en ce sens que, pour le même résultat de couverture, un plus grand nombre d'​exécutions possibles de programmes auront été testées. Malheureusement,​ le concept de couverture des branches est un peu ambigu dans la littérature,​ en raison des différentes interprétations possibles du terme "​branche"​. Dans le code de canMoveTo ci-après, il n'y a que deux branches si l'on considère l'​instruction "​if"​.  
 +<code java> 
 + public boolean canMoveTo(Card pCard) 
 +
 + if( isEmpty() ) 
 +
 + return pCard.getRank() == Rank.ACE; 
 +
 + else 
 +
 + return pCard.getSuit() == peek().getSuit() &&​ 
 + pCard.getRank().ordinal() == 
 + peek().getRank().ordinal()+1;​ 
 +
 +
 +  
 +</​code>​ 
 +Cependant, les branches vraies et fausses conduisent toutes deux à des instructions qui sont constituées d'​expressions booléennes,​ qui sont elles-mêmes un autre type de branche. Pour être cohérent avec les outils populaires d'​analyse de l'âge de couverture, j'​adopte la définition que les expressions booléennes dans les instructions introduisent également des branches. Bien que plus complexe à déterminer,​ cette définition est également plus utile. Avec cette définition,​ le code original de ''​canMoveTo''​ présente huit branches : le si, le premier relevé de retour, la première comparaison dans le deuxième relevé de retour, et la deuxième comparaison dans le deuxième relevé de retour. Chacune de ces branches a un résultat vrai et faux. Le seul test écrit jusqu'​à présent ne couvre que 3/8 = 37,5 % des branches.  
 + 
 + 
 +D'une manière générale, la couverture des branches est l'un des critères de couverture des tests les plus utiles. Elle est bien étayée par des outils de test et relativement facile à interpréter,​ et englobe également la couverture des instructions,​ ce qui signifie qu'​obtenir une couverture complète des instructions implique toujours une couverture complète des branches. 
 + 
 +==== Eclipse et couverture de tests ==== 
 +[[https://​www.eclemma.org/​|EclEmma]] est un outil gratuit de couverture de code Java pour Eclipse, disponible sous la licence publique Eclipse. Il apporte l'​analyse de couverture de code directement dans l'​atelier Eclipse. 
 +Depuis la version 2.0, EclEmma est basé sur la bibliothèque de codes JaCoCoCo. L'​intégration d'​Eclipse a pour but d'​aider le développeur individuel de manière hautement interactive.  
 + 
 + 
 + 
 +Vous avez différents types de compteurs à disposition (voir https://​www.jacoco.org/​jacoco/​trunk/​doc/​counters.html):​ 
 +{{:​2019_2020:​s3:​methodo:​td:​env1:​capture_d_e_cran_2019-08-25_a_15.28.57.png?​200|}} 
 +{{:​2019_2020:​s3:​methodo:​td:​env1:​capture_d_e_cran_2019-08-25_a_16.05.07.png?​200|}} 
 + 
 +et pour mieux comprendre les codes en couleur : https://​www.eclemma.org/​userdoc/​annotations.html 
 + 
 +==== Sélection des cas de tests ==== 
 + 
 + 
 +Aujourd'​hui il y a deux façons fondamentales d'​aborder la sélection des cas de test : 
 + 
 +    * Les **tests fonctionnels (ou de boîte noire)** tentent de couvrir autant que possible le comportement spécifié d'un programme, en se basant sur certaines spécifications externes de ce que le programme doit faire en particulier au travers de post-condition(Nous verrons également que dans le processus de production d'un programme, on prévoit des tests à réaliser dans les histoires utilisateurs). Les tests de boîte noire présentent de nombreux avantages, notamment le fait qu'il n'est pas nécessaire d'​accéder au code, que les tests peuvent révéler des problèmes dans la spécification. 
 +    * Les **tests structurels (ou de boîte blanche)** tentent de couvrir autant que possible le comportement implémenté d'un programme, sur la base d'une analyse du code source de l'​unité à tester. Le principal avantage du test des boîtes blanches est qu'il peut révéler des problèmes causés par des détails d'​implémentation de bas niveau qui sont invisibles au niveau de la spécification. 
 + 
 + 
 + 
 + 
 +===== Références ===== 
 +1. Robert C. Martin. Clean Code: A Handbook of Agile Software Craftmanship. Prentice Hall, 2009.\\ 
 +2. Le livre de référence :  https://​coderprog.com/​introduction-software-design-java/​\\ 
 +3. Les codes sources associés au livre :  https://​github.com/​prmr/​DesignBook\\ 
2019_2020/s3/methodo/td/env1/tests.1566727531.txt.gz · Last modified: 2019/08/25 12:05 by blay