Existe-t-il un moyen, que ce soit dans le code ou avec des arguments JVM, de remplacer l'heure actuelle, telle que présentée via System.currentTimeMillis
, autre que de changer manuellement l'horloge système sur la machine hôte?
Un peu de contexte:
Nous avons un système qui exécute un certain nombre de tâches comptables qui tournent une grande partie de leur logique autour de la date actuelle (c'est-à-dire le 1er du mois, le 1er de l'année, etc.)
Malheureusement, une grande partie du code hérité appelle des fonctions telles que new Date()
ou Calendar.getInstance()
, qui finissent par appeler System.currentTimeMillis
.
À des fins de test, pour le moment, nous sommes obligés de mettre à jour manuellement l'horloge système pour manipuler l'heure et la date auxquelles le code pense que le test est en cours d'exécution.
Ma question est donc:
Existe-t-il un moyen de remplacer ce qui est retourné System.currentTimeMillis
? Par exemple, pour dire à la JVM d'ajouter ou de soustraire automatiquement un décalage avant de revenir de cette méthode?
Merci d'avance!
la source
Réponses:
Je recommande fortement qu'au lieu de jouer avec l'horloge système, vous mordiez la balle et refactoriez ce code hérité pour utiliser une horloge remplaçable. Idéalement, cela devrait être fait avec l'injection de dépendances, mais même si vous utilisiez un singleton remplaçable, vous gagneriez en testabilité.
Cela pourrait presque être automatisé avec la recherche et le remplacement de la version singleton:
Calendar.getInstance()
parClock.getInstance().getCalendarInstance()
.new Date()
parClock.getInstance().newDate()
System.currentTimeMillis()
parClock.getInstance().currentTimeMillis()
(etc. au besoin)
Une fois que vous avez franchi cette première étape, vous pouvez remplacer le singleton par DI un peu à la fois.
la source
java.time.Clock
classe «pour permettre à d'autres horloges d'être connectées au besoin».static
méthodes, assurez-vous qu'elles ne sont pas OO, mais injecter une dépendance sans état dont l'instance ne fonctionne sur aucun état d'instance n'est pas vraiment mieux; c'est juste une manière élégante de déguiser ce qui est effectivement un comportement «statique» de toute façon.tl; dr
Oui.
Clock
Dans java.timeNous avons une nouvelle solution au problème d'un remplacement d'horloge enfichable pour faciliter les tests avec de fausses valeurs de date-heure. Le package java.time de Java 8 comprend une classe abstraite
java.time.Clock
, avec un objectif explicite:Vous pouvez brancher votre propre implémentation de
Clock
, bien que vous puissiez probablement en trouver une déjà conçue pour répondre à vos besoins. Pour votre commodité, java.time inclut des méthodes statiques pour produire des implémentations spéciales. Ces implémentations alternatives peuvent être utiles pendant les tests.Cadence modifiée
Les différentes
tick…
méthodes produisent des horloges qui incrémentent le moment actuel avec une cadence différente.La valeur par défaut
Clock
signale une heure mise à jour aussi souvent que des millisecondes en Java 8 et en Java 9 aussi bien que des nanosecondes (selon votre matériel). Vous pouvez demander que le moment réel soit signalé avec une granularité différente.tickSeconds
- Incréments en secondes entièrestickMinutes
- Incréments en minutes entièrestick
- Incrémente de l'Duration
argument passé .Fausses horloges
Certaines horloges peuvent mentir, produisant un résultat différent de celui de l'horloge matérielle du système d'exploitation hôte.
fixed
- Signale un seul moment inchangé (non incrémentiel) comme moment actuel.offset
- Rapporte le moment actuel mais décalé par l'Duration
argument passé .Par exemple, verrouillez le premier moment du premier Noël de cette année. en d'autres termes, lorsque le Père Noël et ses rennes font leur premier arrêt . Le fuseau horaire le plus ancien de nos jours semble être
Pacific/Kiritimati
à+14:00
.Utilisez cette horloge fixe spéciale pour toujours renvoyer le même moment. Nous obtenons le premier moment du jour de Noël à Kiritimati , avec UTC indiquant une horloge murale de quatorze heures plus tôt, 10 heures du matin à la date précédente du 24 décembre.
Voir le code en direct sur IdeOne.com .
Heure vraie, fuseau horaire différent
Vous pouvez contrôler le fuseau horaire attribué par l'
Clock
implémentation. Cela peut être utile dans certains tests. Mais je ne le recommande pas dans le code de production, où vous devez toujours spécifier explicitement l'optionZoneId
ou lesZoneOffset
arguments.Vous pouvez spécifier que UTC soit la zone par défaut.
Vous pouvez spécifier n'importe quel fuseau horaire particulier. Indiquez un nom de fuseau horaire approprié dans le format
continent/region
, tels queAmerica/Montreal
,Africa/Casablanca
ouPacific/Auckland
. N'utilisez jamais l'abréviation de 3 à 4 lettres telle queEST
ouIST
car ce ne sont pas de vrais fuseaux horaires, non standardisés et même pas uniques (!).Vous pouvez spécifier que le fuseau horaire par défaut actuel de la machine virtuelle Java doit être celui par défaut pour un
Clock
objet particulier .Exécutez ce code pour comparer. Notez qu'ils rapportent tous le même moment, le même point sur la chronologie. Ils ne diffèrent que par l' heure de l'horloge murale ; en d'autres termes, trois façons de dire la même chose, trois façons d'afficher le même moment.
America/Los_Angeles
était la zone par défaut actuelle de la JVM sur l'ordinateur qui exécutait ce code.La
Instant
classe est toujours en UTC par définition. Donc, ces troisClock
usages liés à la zone ont exactement le même effet.Horloge par défaut
L'implémentation utilisée par défaut pour
Instant.now
est celle renvoyée parClock.systemUTC()
. Il s'agit de l'implémentation utilisée lorsque vous ne spécifiez pas de fichierClock
. Voyez par vous -Instant.now
même dans le code source de Java 9 pré-version pour .La valeur
Clock
par défaut pourOffsetDateTime.now
etZonedDateTime.now
estClock.systemDefaultZone()
. Voir le code source .Le comportement des implémentations par défaut a changé entre Java 8 et Java 9. Dans Java 8, le moment actuel est capturé avec une résolution en millisecondes seulement malgré la capacité des classes à stocker une résolution de nanosecondes . Java 9 apporte une nouvelle implémentation capable de capturer le moment actuel avec une résolution de nanosecondes - en fonction, bien sûr, de la capacité de l'horloge matérielle de votre ordinateur.
À propos de java.time
Le framework java.time est intégré à Java 8 et versions ultérieures. Ces classes supplantent les anciens gênants hérités des classes date-heure tels que
java.util.Date
,Calendar
, etSimpleDateFormat
.Pour en savoir plus, consultez le didacticiel Oracle . Et recherchez Stack Overflow pour de nombreux exemples et explications. La spécification est JSR 310 .
Le projet Joda-Time , désormais en mode maintenance , conseille la migration vers les classes java.time .
Vous pouvez échanger des objets java.time directement avec votre base de données. Utilisez un pilote JDBC compatible avec JDBC 4.2 ou version ultérieure. Pas besoin de chaînes, pas besoin de
java.sql.*
classes. Hibernate 5 et JPA 2.2 prennent en charge java.time .Où obtenir les classes java.time?
la source
Comme l'a dit Jon Skeet :
Alors voilà (en supposant que vous venez de tout remplacer
new Date()
parnew DateTime().toDate()
)Si vous souhaitez importer une bibliothèque qui a une interface (voir le commentaire de Jon ci-dessous), vous pouvez simplement utiliser Prevayler's Clock , qui fournira des implémentations ainsi que l'interface standard. Le pot plein ne pèse que 96 Ko, il ne devrait donc pas casser la banque ...
la source
Bien que l'utilisation d'un modèle DateFactory semble agréable, cela ne couvre pas les bibliothèques que vous ne pouvez pas contrôler - imaginez l'annotation de validation @Past avec une implémentation reposant sur System.currentTimeMillis (il y en a une).
C'est pourquoi nous utilisons jmockit pour simuler directement l'heure du système:
Comme il n'est pas possible d'obtenir la valeur non simulée d'origine de millis, nous utilisons à la place une nano minuterie - ce n'est pas lié à l'horloge murale, mais le temps relatif suffit ici:
Il y a un problème documenté, avec HotSpot, le temps revient à la normale après un certain nombre d'appels - voici le rapport de problème: http://code.google.com/p/jmockit/issues/detail?id=43
Pour surmonter cela, nous devons activer une optimisation HotSpot spécifique - exécutez JVM avec cet argument
-XX:-Inline
.Bien que cela ne soit peut-être pas parfait pour la production, cela convient parfaitement aux tests et est absolument transparent pour l'application, en particulier lorsque DataFactory n'a pas de sens commercial et est introduit uniquement en raison de tests. Ce serait bien d'avoir une option JVM intégrée pour fonctionner à un moment différent, dommage que ce ne soit pas possible sans des hacks comme celui-ci.
L'histoire complète est dans mon article de blog ici: http://virgo47.wordpress.com/2012/06/22/changing-system-time-in-java/
La classe pratique complète SystemTimeShifter est fournie dans l'article. La classe peut être utilisée dans vos tests, ou elle peut être utilisée comme la première classe principale avant votre vraie classe principale très facilement afin d'exécuter votre application (ou même le serveur d'applications entier) à un moment différent. Bien sûr, cela est principalement destiné à des fins de test, pas à l'environnement de production.
EDIT Juillet 2014: JMockit a beaucoup changé ces derniers temps et vous êtes obligé d'utiliser JMockit 1.0 pour l'utiliser correctement (IIRC). Impossible de passer à la dernière version où l'interface est complètement différente. Je pensais à intégrer uniquement les éléments nécessaires, mais comme nous n'avons pas besoin de ce truc dans nos nouveaux projets, je ne développe pas du tout ce truc.
la source
Powermock fonctionne très bien. Je l'ai juste utilisé pour se moquer
System.currentTimeMillis()
.la source
@PrepareForTest
annotation sur la classe de test).System.currentTimeMillis
n'importe où sur votre chemin de classe (toute bibliothèque), vous devez vérifier chaque classe. C'est ce que je voulais dire, rien d'autre. Le fait est que vous vous moquez de ce comportement du côté de l'appelant ("vous dites spécifiquement"). C'est correct pour les tests simples mais pas pour les tests où vous ne pouvez pas être sûr de ce qui appelle cette méthode d'où (par exemple des tests de composants plus complexes avec des bibliothèques impliquées). Cela ne signifie pas du tout que Powermock a tort, cela signifie simplement que vous ne pouvez pas l'utiliser pour ce type de test.Utilisez la programmation orientée aspect (AOP, par exemple AspectJ) pour tisser la classe System pour renvoyer une valeur prédéfinie que vous pouvez définir dans vos cas de test.
Ou tisser les classes d'application pour rediriger l'appel vers
System.currentTimeMillis()
ou versnew Date()
une autre classe utilitaire de votre choix.Classes de système de tissage (
java.lang.*
) est cependant un peu plus délicat et vous devrez peut-être effectuer un tissage hors ligne pour rt.jar et utiliser un JDK / rt.jar séparé pour vos tests.Cela s'appelle le tissage binaire et il existe également des outils spéciaux pour effectuer le tissage de classes système et contourner certains problèmes avec cela (par exemple, le démarrage de la machine virtuelle peut ne pas fonctionner)
la source
Il n'y a vraiment pas de moyen de le faire directement dans la machine virtuelle, mais vous pouvez tout régler par programmation l'heure système sur la machine de test. La plupart (tous?) OS ont des commandes de ligne de commande pour ce faire.
la source
date
ettime
. Sous Linux, ladate
commande.Un moyen efficace de remplacer l'heure système actuelle à des fins de test JUnit dans une application Web Java 8 avec EasyMock, sans Joda Time et sans PowerMock.
Voici ce que vous devez faire:
Que faut-il faire dans la classe testée
Étape 1
Ajoutez un nouvel
java.time.Clock
attribut à la classe testéeMyService
et assurez-vous que le nouvel attribut sera correctement initialisé aux valeurs par défaut avec un bloc d'instanciation ou un constructeur:Étape 2
Injectez le nouvel attribut
clock
dans la méthode qui appelle une date-heure actuelle. Par exemple, dans mon cas, j'ai dû vérifier si une date stockée dans dataase était antérieureLocalDateTime.now()
, que j'ai remplacéeLocalDateTime.now(clock)
, comme ceci:Que faut-il faire dans la classe de test
Étape 3
Dans la classe de test, créez un objet d'horloge fictif et injectez-le dans l'instance de la classe testée juste avant d'appeler la méthode testée
doExecute()
, puis réinitialisez-le juste après, comme ceci:Vérifiez-le en mode débogage et vous verrez que la date du 3 février 2017 a été correctement injectée dans l'
myService
instance et utilisée dans l'instruction de comparaison, puis a été correctement réinitialisée à la date actuelle avecinitDefaultClock()
.la source
À mon avis, seule une solution non invasive peut fonctionner. Surtout si vous avez des bibliothèques externes et une grande base de code héritée, il n'y a pas de moyen fiable de simuler le temps.
JMockit ... ne fonctionne que pour un nombre limité de fois
PowerMock & Co ... doit se moquer du clients sur System.currentTimeMillis (). Encore une fois une option invasive.
De cela, je ne vois que le javaagent mentionné ou aop approche étant transparente pour l'ensemble du système. Quelqu'un a-t-il fait cela et pourrait suggérer une telle solution?
@jarnbjo: pourriez-vous montrer une partie du code javaagent s'il vous plaît?
la source
Si vous utilisez Linux, vous pouvez utiliser la branche master de libfaketime, ou au moment du test du commit 4ce2835 .
Définissez simplement la variable d'environnement avec l'heure avec laquelle vous souhaitez simuler votre application java et exécutez-la en utilisant ld-preloading:
La deuxième variable d'environnement est primordiale pour les applications Java, qui sinon se figeraient. Il nécessite la branche principale de libfaketime au moment de l'écriture.
Si vous souhaitez modifier l'heure d'un service géré par systemd, ajoutez simplement ce qui suit à vos remplacements de fichier d'unité, par exemple pour elasticsearch, ce serait
/etc/systemd/system/elasticsearch.service.d/override.conf
:N'oubliez pas de recharger systemd en utilisant `systemctl daemon-reload
la source
Si vous voulez vous moquer de la méthode ayant un
System.currentTimeMillis()
argument, vous pouvez passeranyLong()
classe Matchers comme argument.PS Je suis capable d'exécuter mon cas de test avec succès en utilisant l'astuce ci-dessus et juste pour partager plus de détails sur mon test que j'utilise les frameworks PowerMock et Mockito.
la source