16mar. 2010
Tests unitaires pour UIMA avec UUTUC
15:45 - Par Fabien Poulard - Sciences & Recherche
La qualité du code développé dans le cadre des activités de recherche scientifique n'est pas toujours aussi bon qu'on pourrait l'espérer. Outre la nécessité (évidente à mes yeux) d'ouvrir le codes des activités scientifiques financées par l'État et les collectivités territoriales, il est également nécessaire de suivre de bonnes pratiques de programmation. L'écriture de tests unitaires et leur exécution régulière est une de ces bonnes pratiques.
Je présente dans ce billet un cas d'utilisation de la bibliothèque UUTUC, présentée lors du Workshop sur l'Ingénierie Logiciel, les Tests et l'Assurance Qualité pour le Traitement des Langues Naturelles (SETQA-NLP 2009), pour tester l'implémentation d'une bibliothèque développée et utilisée dans le cadre de ma thèse (tddts-uima-shingling).
Principe de UUTUC
UUTUC est une bibliothèque offrant un certain nombre de méthodes facilitant le processus de test des composants UIMA. On y trouve notamment un certain nombre de classes de type Factory qui facilitent la mise en place de chaînes de traitement simples pour expérimenter les composants.
À l'aide de ces classes, l'exécution d'un AE sur un simple fichier texte se résume à ces quelques lignes :
AnalysisEngine engine = AnalysisEngineFactory.createAnalysisEngineFromPath("descriptors/tutorial/ex1/RoomNumberAnnotator.xml"); JCas jCas = AnalysisEngineFactory.process(engine, "data/WatsonConferenceRooms.txt");
Le couplage de UUTUC avec JUnit permet de mettre en place un banc de tests unitaires :
- Afin de tester la conformité de l'implémentation avec les spécifications attendues ;
- Prévenir les problèmes de régression lors de l'évolution des composants
Écriture de tests unitaires
J'utilise le framework JUnit 4 pour les tests unitaires. Il suffit de faire précéder les méthodes considérées comme des tests par @Test pour qu'elles soient reconnues comme telles par JUnit. Exemple :
import org.junit.Test; import static org.junit.Assert.*; ... /** * This class defines the tests for the main methods of the Shingle class. */ public class ShingleTest { ... /** * This method just checks that the isComplete method works * @throws InvalidShingleException * @throws OverloadShingleException */ @Test public void completeness() throws InvalidShingleException, OverloadShingleException { Shingle s1 = new Shingle(2); assertFalse( s1.isComplete() ); // before any adding s1.add( theShingleItems[0] ); assertFalse( s1.isComplete() ); // after a first adding s1.add( theShingleItems[0] ); assertTrue( s1.isComplete() ); // should be complete by now } ... }
Combiné à UUTUC, il permet de mettre en place un environnement UIMA assez simplement. Ainsi dans l'exemple ci-dessous, nous définissons une méthode à exécuter avant chaque test (@Before) qui crée un JCas et y ajoute quelques annotations à l'aide des Factory de UUTUC :
/** Static data for testing */ private static String CAS_CONTENT = "Suisse : inauguration d'une nouvelle synagogue, une première depuis 50 ans"; private static Integer[][] CAS_OFFSETS = { {0,6}, {9,21}, {22,24}, {24,27}, {28,36}, {37,46}, {48,51}, {52,60}, {61,67}, {68,74} }; ... /** * This method is used to set up the testing environment, creating the * data necessary for the different tests methods. */ @Before public void setUp() throws UIMAException, IOException, ShinglingTestingException { // Set up a CAS with a couple of shingle items in TypeSystemDescription tsd = TypeSystemDescriptionFactory .createTypeSystemDescription("shingling-ts"); theTestingCas = JCasFactory.createJCas(tsd); theTestingCas.setDocumentText(CAS_CONTENT); for(Integer[] idx: CAS_OFFSETS) { AnnotationFactory.createAnnotation(theTestingCas, idx[0], idx[1], ShingleItem.class) ); } }
Malheureusement il y a assez peu de documentation concernant UUTUC. Il est ainsi régulièrement nécessaire d'aller jeter un œil au code source qui heureusement est très bien écrit.
Intégration avec Maven
Maven modélisant toutes les étapes du cycle de développement, il intègre une étape test entre le compile et le package. La gestion des tests unitaires se faisant quant à eux au travers du plugin maven-surefire-plugin.
Il faut tout d'abord rajouter dans le pom.xml les informations de dépendance sur UUTUC et JUnit :
<repository> <snapshots> <enabled>false</enabled> </snapshots> <id>uutuc-googlecode</id> <name>uutuc Google Code repository</name> <url>http://uutuc.googlecode.com/svn/repo/</url> </repository> ... <!-- UUTUC for testing --> <dependency> <groupId>org.uutuc</groupId> <artifactId>uutuc</artifactId> <version>0.9.10</version> <optional>false</optional> <scope>test</scope> </dependency> <!-- JUnit 4 for testing --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.3.1</version> <scope>test</scope> </dependency>
Il suffit ensuite de faire appel au plugin maven-surefire-plugin qui prend en charge tout ce qui concerne les tests, sous réserve que ces derniers soient bien présents dans src/test/java :
<!-- Testing --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <reportFormat>brief</reportFormat> <useFile>false</useFile> </configuration> </plugin>
Il est alors possible de lancer l'exécution des tests avec Maven :
$ mvn test ... ------------------------------------------------------- T E S T S ------------------------------------------------------- Running tddts.uima.shingling.ShingleTest Tests run: 16, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 3.992 sec Results : Tests run: 16, Failures: 0, Errors: 0, Skipped: 0 ...
Plus d'excuse pour ne pas tester votre code maintenant ! L'excuse de faire du prototypage pour la recherche n'en est pas une bonne dès que les résultats que vous publiez dépendent de la qualité dudit code. C'est votre intégrité et honnêteté scientifique qui est en jeux ;)