vendredi 1 novembre 2013

Tester ses plugins Maven

Lorsqu'il y a développement, il y a généralement test.
Mais tester un plugin (Mojo) Maven n'est pas évident. Il existe plusieurs solutions, plus ou moins pratique.
1 - Tester sans framework dédié.
Pour tester un mojo Maven (une classe Java associée à un goal), le plus simple consiste a déclarer tous les attributs exposés à Maven en protected ou en package private.
Par exemple, un Mojo affichant une chaine de caractère.
public class ShowMessageMojo extends AbstractMojo {
 /**
  * @parameter
  * @required
  */
 private String message ;
    
    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        getLog().info(message) ;
    }
}

Pour tester ce mojo, il est nécessaire de changer private String message ; en protected String message ;. Ainsi, il est possible de créer un classe de test qui hérite de ce mojo et qui ajoutera un getter/setter.
Il est aussi tout a fait possible, dans le mojo du départ de mettre des getter/setter. Ces getter/setter seront en plus utilisé par Maven pour modifier l'attribut.

Ensuite, une simple classe JUnit permet de tester.
Le gros inconvénient viendra lorsque le mojo voudra récupérer le projet Maven, les repositories...
2 - Tester avec maven-plugin-testing-harness
Pour pouvoir exécuter un mojo dans un contexte Maven, il existe le plugin maven-plugin-testing-harness.
Dans l'exemple suivant, voici la dépendance utilisée :
<dependency>
  <groupId>org.apache.maven.plugin-testing</groupId>
  <artifactId>maven-plugin-testing-harness</artifactId>
  <version>1.3</version>
  <scope>test</scope>
</dependency>

Reprise de l'exemple précédent, mais avec l'ajout du projet maven :
/**
 * @goal showMessage
 * @requiresProject true
 * 
 * @author Emeric MARTINEAU
 */
public class ShowMessageMojo extends AbstractMojo {
 /**
  * Le POM.
  * 
  * @parameter expression="${project}"
  * @required
  */
 protected MavenProject project;
 
 /**
  * @parameter
  * @required
  */
 private String message ;
    
    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        getLog().info(project.getBasedir()) ;
    }
}

Comment alors créer un projet maven dans le cadre d'un test ?
Pour cela, une classe de test dérivant de AbstractMojoTestCase doit être créée.

Cette classe met a disposition un ensemble de méthode qui facilite le test.

Il faut au préalable, créer un fichier pom.xml qui appel le plugin, exemple :
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                      http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>group-id-bidon</groupId>
    <artifactId>artifact-id-bidon</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>Test plugin</name>

    <build>
        <plugins>
            <plugin>
                <groupId>org.mycompany.plugin</groupId>
                <artifactId>maven-toto-plugin</artifactId>
                <version>1.0.0-SNAPSHOT</version>
                <configuration>
                    <message>SRVF_TestMaven</message>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Ce fichier de pom permet de récupérer une instance du plugin/mojo.
Voici comment instancier le mojo et l'exécuter :
// Par convention, le classe de test porte le nom de la
// classe du mojo, suffixé avec "Test".
public class ShowMessageMojoTest extends AbstractMojoTestCase {
    /**
     * @throws Exception if any
     */
    public void testMojo() throws Exception
    {
        // Lecture du fichier pom pour initiliser le context Plexus
        File pom = getTestFile( "src/test/resources/pom.xml" );
        assertNotNull( pom );
        assertTrue( pom.exists() ) ;
        
        // Recuperation du mojo par le nom du goal
        ShowMessageMojo myMojo = (ShowMessageMojo) lookupMojo( "showMessage", pom );
        assertNotNull( myMojo );
                
        // Execution du mojo
        myMojo.execute();
    }
}

Le résultat ?
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.469 sec <<< FAILURE!
testMojo(ShowMessageMojoTest)  Time elapsed: 0.283 sec  <<< ERROR!
java.lang.NullPointerException: null
 at ShowMessageMojo.execute(ShowMessageMojo.java:31)
 at ShowMessageMojoTest.testMojo(ShowMessageMojoTest.java:30)

NullPointerException à la ligne getLog().info(project.getBaseDir()) ;. Pourquoi ?
Parce que expression="${project}" n'est pas interprété.

Il faudra donc, manuellement affecter une valeur, juste avant l'exécution du mojo, avec :
public class ShowMessageMojoTest extends AbstractMojoTestCase {
    /**
     * @throws Exception if any
     */
    public void testMojo() throws Exception
    {
        // Lecture du fichier pom pour initiliser le context Plexus
        File pom = getTestFile( "src/test/resources/pom.xml" );
        assertNotNull( pom );
        assertTrue( pom.exists() ) ;

        // Recuperation du mojo par le nom du goal
        ShowMessageMojo myMojo = (ShowMessageMojo) lookupMojo( "showMessage", pom );
        assertNotNull( myMojo );

        // Creation du projet maven
        final MavenProject mvnProject = new MavenProject() ;
        mvnProject.setBasedir(new File("/")) ;
        setVariableValueToObject(myMojo, "project", mvnProject) ;
  
        // Execution du mojo
        myMojo.execute();
    }
}

Il en sera ainsi pour toute les proriétés.

Evidement, cela reste encore un peu manuel. Mais d'autre méthode existe pour configurer le mojo.

Il est possible d'utiliser la ligne configureMojo(myMojo, "maven-toto-plugin", pom) ;.
public class ShowMessageMojoTest extends AbstractMojoTestCase {
    /**
     * @throws Exception if any
     */
    public void testMojo() throws Exception
    {
        // Lecture du fichier pom pour initiliser le context Plexus
        File pom = getTestFile( "src/test/resources/pom.xml" );
        assertNotNull( pom );
        assertTrue( pom.exists() ) ;

        // Recuperation du mojo par le nom du goal
        ShowMessageMojo myMojo = (ShowMessageMojo) lookupMojo( "showMessage", pom );
        assertNotNull( myMojo );

        configureMojo(myMojo, "maven-toto-plugin", pom) ;
  
        // Execution du mojo
        myMojo.execute();
    }
}

Le seul inconvénient de ce système, c'est qu'il ne gère pas la possiblilité d'avoir dans le même fichier de configuration, plusieurs goal avec la baslise <execution />.
Il faut donc pour chaque goal créer un pom.

1 commentaire:

  1. A défaut d'un nouvel article, voilà une très bonne nouvelle !
    Il va enfin être possible de retester ses mojo Maven avec le Maven Plugin Testing avec Mave 3.0 et 3.1.

    https://jira.codehaus.org/browse/MPLUGINTESTING-30

    Il faudra juste attendre la release 3 du plugin :-(

    RépondreSupprimer