174 votes

Remplacer Java System.currentTimeMillis pour tester le code sensible au temps

Est-il possible, dans le code ou avec des arguments JVM, pour remplacer l'heure actuelle, tel que présenté par le Système.currentTimeMillis, autres que de modifier manuellement l'horloge système sur la machine hôte?

Un peu d'histoire:

Nous avons un système qui exécute un certain nombre de comptabilité des emplois qui gravitent une grande partie de leur logique autour de la date actuelle (c'est à dire le 1er du mois, le 1er de l'an, etc)

Malheureusement, beaucoup de l'héritage de code fait appel à des fonctions telles que new Date() ou le Calendrier.getInstance(), qui finalement l'appel vers le Système.currentTimeMillis.

Pour des fins de test, pour l'instant, nous sommes coincés avec manuellement la mise à jour de l'horloge du système pour manipuler quelle heure et de la date à laquelle le code pense que le test est en cours d'exécution.

Donc ma question est:

Est-il un moyen de remplacer ce qui est retourné par le Système.currentTimeMillis? Par exemple, pour dire la JVM automatiquement d'ajouter ou de soustraire certains offset avant de retourner à partir de cette méthode?

Merci à l'avance!

149voto

Jon Skeet Points 692016

J'ai fortement recommander que, au lieu de jouer avec l'horloge système, vous mordre la balle et refactoriser que le code hérité d'utiliser un remplaçable par l'horloge. Idéalement, cela devrait être fait avec l'injection de dépendance, mais même si vous avez utilisé un remplaçable singleton vous permettrait de gagner de testabilité.

Cela pourrait presque être automatisé à l'aide de la recherche et de la remplacer pour le singleton version:

  • Remplacer Calendar.getInstance() avec Clock.getInstance().getCalendarInstance().
  • Remplacer new Date() avec Clock.getInstance().newDate()
  • Remplacer System.currentTimeMillis() avec Clock.getInstance().currentTimeMillis()

(etc, comme l'exige)

Une fois que vous avez pris cette première étape, vous pouvez remplacer le singleton avec DI un peu à la fois.

43voto

Stephen Points 8670

Comme dit par Jon Skeet:

"l'utilisation Joda Time" est presque toujours la meilleure réponse à toute question impliquant "comment puis-je obtenir X avec java.util.Date/Calendrier?"

Donc, ici, va (en supposant que vous avez juste remplacé tous vos new Date() avec new DateTime().toDate())

//Change to specific time
DateTimeUtils.setCurrentMillisFixed(millis);
//or set the clock to be a difference from system time
DateTimeUtils.setCurrentMillisOffset(millis);
//Reset to system time
DateTimeUtils.setCurrentMillisSystem();

Si vous souhaitez importer une bibliothèque qui dispose d'une interface (voir Jon commentaire ci-dessous), vous pouvez simplement utiliser Prevayler de l'Horloge, qui permettra de fournir des implémentations ainsi que l'interface standard. Le pot plein est seulement 96ko, donc il ne devrait pas casser la banque...

16voto

virgo47 Points 916

Lors de l'utilisation de certains DateFactory modèle semble beau, il ne couvre pas les bibliothèques que vous ne pouvez pas contrôler - imaginer la Validation de l'annotation @Passé avec la mise en œuvre en s'appuyant sur le Système.currentTimeMillis (il y a une telle).

C'est pourquoi nous utilisons jmockit de se moquer de l'heure du système directement:

import mockit.Mock;
import mockit.MockClass;
...
@MockClass(realClass = System.class)
public static class SystemMock {
    /**
     * Fake current time millis returns value modified by required offset.
     *
     * @return fake "current" millis
     */
    @Mock
    public static long currentTimeMillis() {
        return INIT_MILLIS + offset + millisSinceClassInit();
    }
}

Mockit.setUpMock(SystemMock.class);

Parce qu'il n'est pas possible de se rendre à l'original unmocked valeur de millis, nous utilisons nano minuterie au lieu de cela - ce n'est pas lié à l'horloge du mur, mais de temps relatif suffit ici:

// runs before the mock is applied
private static final long INIT_MILLIS = System.currentTimeMillis();
private static final long INIT_NANOS = System.nanoTime();

private static long millisSinceClassInit() {
    return (System.nanoTime() - INIT_NANOS) / 1000000;
}

Il est documenté problème, qu'avec HotSpot, le temps sera de retour à la normale après un certain nombre d'appels - voici le rapport: http://code.google.com/p/jmockit/issues/detail?id=43

Pour surmonter cela, nous devons tourner sur un HotSpot optimisation - exécution de la JVM avec cet argument -XX:-Inline.

Même si cela peut ne pas être idéal pour la production, il est tout simplement parfait pour les tests et il est tout à fait transparente pour l'application, en particulier lorsque DataFactory ne rend pas le sens des affaires et est introduit seulement en raison de tests. Il serait agréable d'avoir intégré en option JVM pour s'exécuter dans un temps différent, trop mauvais, il n'est pas possible sans hacks de ce genre.

Histoire complète est dans mon blog ici: http://virgo47.wordpress.com/2012/06/22/changing-system-time-in-java/

Complète, pratique de classe SystemTimeShifter est fourni dans le post. La classe peut être utilisée dans vos tests, ou il peut être utilisé comme la première classe principale avant votre véritable classe principale très facilement afin d'exécuter votre application (ou même l'ensemble du serveur d'applications) dans un autre temps. Bien sûr, cela est destiné à des fins de test surtout, mais pas pour un environnement de production.

ÉDITION juillet 2014: JMockit beaucoup changé ces derniers temps et vous êtes lié à l'utilisation JMockit 1,0 à utiliser correctement (IIRC). Certainement ne pouvez pas mettre à niveau à la version la plus récente où l'interface est complètement différente. Je pensais à l'in-lining, juste le nécessaire, mais comme nous n'avons pas besoin de cette chose dans nos nouveaux projets, je ne suis pas le développement de cette chose.

8voto

ReneS Points 1526

Powermock fonctionne très bien. Je viens de l'utiliser pour se moquer de System.currentTimeMillis() .

6voto

mhaller Points 10002

Utiliser la Programmation Orientée Aspects (AOP, par exemple AspectJ) pour tisser le Système de classe pour retourner une valeur prédéfinie qui vous pourriez mettre dans votre cas de test.

Ou tisser les classes de l'application de rediriger l'appel vers System.currentTimeMillis() ou new Date() d'un autre utilitaire de la classe de votre choix.

Tissage système de classes (java.lang.*) est toutefois un peu plus délicat et vous devrez peut-être exécuter en mode hors connexion pour le tissage rt.jar et utiliser un JDK/rt.jar pour vos tests.

Il est appelé Binaire de tissage et il existe également des outils pour effectuer le tissage du Système de classes et de contourner certains problèmes (par exemple, l'amorçage de la machine virtuelle peut ne pas fonctionner)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X