Avez vous confiance en vos tests?

le 27/03/2008 par Olivier Mallassi
Tags: Product & Design

Dans un précédent post, j'avais déjà évoqué l'insuffisance de l'indicateur "couverture de test" à lui seul pour garantir la détection de régressions. Pour résumer, un autre indicateur permettant de répondre aux questions "mes tests me protègent-ils contre d'éventuelles régressions?" ou formuler autrement "Quel est le niveau de confiance que j'ai dans mes tests?" doit être trouvé.

Deux idées existent. La méthode plus simple consiste à compter le nombre d'assertions réalisées: efficace et simple à mettre en oeuvre puisque cette solution repose sur un simple grep (comme si grep était simple...).
L'autre méthode repose sur le principe suivant:

Le code de test "valide" le code métier. le code métier peut valider le code de test...

Alors concrètement comment faire?! comment l'implémenter? Comme j'avais envie de coder, voilà "l'histoire" d'un POC visant à valider les principes précédemment énoncés.

Le principe clé du POC est le suivant: modifions aléatoirement le code métier existant et assurons nous que les tests associés échouent. Formulé autrement, si pour un code métier donné le test est vert, alors en modifiant ce même code métier - et donc en introduisant des régressions - le test doit être rouge...
Ainsi, il suffit d'exécuter les tests unitaires existants N fois (N étant paramétrable) et pour chaque exécution:

  • n'introduire qu'une seule et unique modification dans l'ensemble code métier exécuté par ce test. La modification effectuée est choisie au hasard parmi un ensemble d'opérations de modification disponible. La portion de code métier qui sera modifiée est également choisie au hasard.
  • vérifier que le test est bien "rouge"

On peut estimer que plus N (le nombre d'exécution des tests) est grand, plus l'éventail de régressions introduit sera riche et nous permettra de calculer un indice traduisant notre niveau de confiance dans les tests.

Concernant les principes techniques:

  • le code métier et le code de test existe déjà
  • le code métier sera "weavé" - ou tisser en bon français - avec AspectJ et interceptera toutes les exécutions de méthodes. Par exemple:

pointcut allBusinessCode() : execution( * *.*(..)) && !execution(*.(..))
&& within(com.mycompany.apb..*)
&& !within(com.mycompany.apb..*Test*);

  • le code de test sera lui aussi "weavé" avec AspectJ

public pointcut allTest(): within(*..*Test*)

 && execution(\* \*..test\*(..)) 

 && !within(com.agitator..\*); 
  • et - notamment - interceptera les éventuelles (et normalement elles existent...) remontées d'erreur

after() throwing(junit.framework.AssertionFailedError afe):allTest()
{ ...

Alors quelles modifications introduire dans le code? A ce jour et à titre d'exemple les modifications type sont les suivantes:

  • modification des paramètres d'entrée ou de sortie des méthodes. AOP est parfait pour cela
  • lancement d'une exception Java en lieu et place de l'exécution nominale
  • passage de certains paramètres à null
  • Sur les types Java classiques et à titre d'exemple, les quelques opérations suivantes sont réalisées:
    • sur les entiers, l'algorihme multiplie l'entier par un nombre aléatoire
    • sur les objets de type java.lang.String, l'algotihme inverse simplement le premier et le dernier caractère
  • et ainsi de suite...

Reste enfin à automatiser ces comportements: et là c'est Maven 2 et son mécanisme de plugin qui rentre en jeu..

Pour pouvoir réaliser les étapes citées précédemment, il est nécessaire:

  • d'offrir à l'utilisateur la possibilité de définir le code métier qu'il souhaite tisser. Cela est possible au niveau de la configuration du plugin grâce à la variable et en utilisant un template d'aspect que le plugin instancie avec la définition du pointcut sppar la configuration.
  • de compiler les sources et les tests dans des répertoires différents de target/classes et target/test-classes afin notamment de ne pas perturber l'exécution des tests unitaires non modifiés. Ainsi, il a été nécessaire de "forker" le cycle de vie standard (grâce au fichier lifecycle.xml) pour que la phase de test réalise les éléments suivant:
    • instancier le template d'aspect avec la configuration de l'utilisateur tel que présentée précédemment
    • compiler et tisser le code métier et le code de test avec les aspects définis dans des répertoires autres que les répertoires standards Maven (typiquement target/agitator/classes et target/agitator/test-classes)
    • exécuter N fois l'ensemble des tests avec le nouveau classpath (et donc les répertoires créés précédemment)

le snippet suivant donne un exemple de la configuration du plugin

     org.codehaus.mojo  
    maven-agitator-plugin  
    1.0-SNAPSHOT  
    testTheTests  
        test  
        instrument              
        < !\[CDATA\[within(com.mycompany.apb..\*) && !within(com.mycompany.apb..\*Test\*) && !within(com.mycompany.fwk.persistence.PersistentObject+)\]\]>                 
          3  
          aspectj  
              aspectjrt  
              1.5.3 

Ainsi au cours de l'exécution mvn site, l'ensemble des opérations de modification effectuées ainsi que leur impact sur le test sont stockés dans un fichier xml qui servira par la suite à la création du rapport Maven:

Cette première version du rapport indique un indicateur de confiance qui est le taux de tests dont le code a été modifié et qui sont en erreur (vous me suivez là? ). A ce jour et pour faire simple, si tous les tests ont échoués - c'est à dire que toutes les régressions introduites ont été modifiées -, cet indicateur est de 100 et est donc au vert. Le rapport indique ensuite pour chacun des tests modifiés et pour chacune des exécutions, la méthode métier modifiée ainsi qu'une description de la modification apportée.

Reste néanmoins qu'il s'agit là d'un POC et du travail reste nécessaire pour faire de ce plugin "quelque chose" de plus industriel et utilisable sereinement "dans la vraie vie" - avec notamment:

  • la nécessité d'aller plus loin dans les algorithmes de modification -. J'imagine qu'il serait possible d'analyser syntaxiquement le code afin d'être capable de modifier non plus et uniquement des paramètres d'entrée ou de sortie de méthodes mais des boucles for, while...bref être plus fin et plus subtil dans les modifications effectuées.
  • l'ajout d'un plugin eclipse qui offrirait les mêmes mécanismes aux développeurs le tout mieux intégré dans leur environnement de travail.

Reste également qu'aujourd'hui, à ma connaissance, aucun outil ne réalise vraiment ce genre de modification...