Contrôler le temps pour faciliter les tests
Bien souvent, il est difficile de tester du code dans lequel l’écoulement du temps a une grande importance :
- dans la téléphonie (avec des durées de sonnerie, de conversation) avec des statistiques en bout de chaine
- pour des tests de statistiques liées à des évènements de manière générale
- pour des purges de fichiers qui s’appuient sur une date de validité
- passage heure d’été heure d’hiver
- applications avec des fuseaux horaires différents
- tests de calcul de temps
- … et des tas d’autres exemples que vous trouverez aisément dans vos bases de code
Ne serait-il pas pratique de pouvoir disposer du temps tel un magicien et faire que par exemple tous les tests d’une classe se déroulent le 10 janvier 2010 à 10h00 parce que cette date m’arrange et que je suis capable d’écrire des assertions qui seront toujours valables ?
Nous avons donc développé une librairie de temps simulé que nous appelons ici BarreVerteTime
.
Cette librairie est très facile d’utilisation. Il suffit de se déclarer en mode temps simulé et de donner la date que l’on désire pour le test. L’inconvénient de notre librairie est qu’elle a un impact dans le code de production : nous ne pouvons plus faire de new Date()
ou jouer avec les Calendar
(et c’est tant mieux pour cette partie là). A la place, on utilise la librairie pour obtenir la date courante. Ainsi, dans votre code de production, vous aurez des lignes de ce genre :
Date maDateNonTestable = new Date(); // :-( à ne plus faire
Date maDateTestable = BarreVerteTime.now(); // :-) la nouvelle façon
C’est un prix à payer mais nous trouvons que cela vaut le coup car nous avons pu améliorer considérablement la testabilité de notre code.
Dans vos tests, il suffit de passer en mode temps simulé et de lui donner la date désirée dans le setup
puis de revenir en mode normal dans le teardown
.
A la longue, tous les tests se ressemblent dans les parties setUp
et tearDown
, et une grande duplication entre les tests utilisant BarreVerteTime apparaît. Imaginez donc un instant que le code ci-dessus apparait dans tous vos tests qui utilisent BarreVerteTime ! Dans notre base de code, ce nombre est vraiment important.
Bien sûr, ceci est intolérable : mais il existe un moyen simple pour factoriser ce genre de code. En effet, depuis jUnit 4.7, il est possible de créer des règles (appelées Rules). Une fois la règle créée, le code pour passer un test en temps simulé tient en une ligne :
public class MonTest {
@Rule public BarreVerteTimeRule timeRule = new BarreVerteTimeRule("2010-01-10 10:00:00");
@Test public void premierTest { /*...*/ }
@Test public void deuxiemeTest { /*...*/ }
}
Comme vous pouvez le voir, la déclaration est très simple : l’annotation @Rule
précède la déclaration de l’objet en visibilité public
.
Une règle jUnit est très facile à coder. Nous utilisons ici une ExtrernalResource
. Il nous suffit d’implémenter les méthodes before
et after
qui seront appelées respectivement avant et après chaque test de la classe outillée (comme si vous aviez ajouté des annotations @Before
et @After
à des méthodes de votre test) :
package fr.barreverte.time;
import java.util.Date;
import org.junit.rules.ExternalResource;
static import fr.barreverte.BarreVerteDateUtils.*;
public class BarreVerteTimeRule extends ExternalResource {
public final String dateDeMaintenant;
/** @param dateDeMaintenant yyyy-MM-dd HH:mm:ss */
public BarreVerteTimeRule(String dateDeMaintenant) {
this.dateDeMaintenant = dateDeMaintenant;
}
@Override protected void before() throws Throwable {
BarreVerteTime.setMockTime(true);
BarreVerteTime.getInstance().setMockDate(
formatDate(dateDeMaintenant));
}
@Override protected void after() {
BarreVerteTime.setMockTime(false);
}
public Date now() {
return BarreVerteTime.getInstance().now();
}
}
Et voilà ! Désormais, vous avez le contrôle total sur le temps dans votre logiciel. Comme vous avez pu le constater, rien de magique là-dedans finalement. En outre, vous avez appris à utiliser une des dernières fonctionnalités proposées par jUnit et cela vous a permis d’éviter la duplication dans votre code. Moi, j’appelle cela un bon plan, pas vous ?
NB : nous voudrions ouvrir le code de cette librairie, mais il faut que nous voyions cela auprès de notre employeur. Si nous y parvenons, le code sera disponible sur github.
-
Philippe Blayo le 2011-01-13 :
Même si les @Rule sont apparus avec junit 4.7, mieux vaux les utiliser en version 4.8 où elles s’exécutent avant
@Before
et après@After
. En junit 4.7, le champs annoté par@Rule
n’est pas disponible danssetUp()
ettearDown()
ce qui surprend la plupart des gens. - Jean-Philippe Caruana le 2011-01-14 : Oui merci pour cette précision. J’utilise actuellement la version 4.8.2.
Réactions