Sostituisci Java System.currentTimeMillis per testare il codice sensibile al tempo


129

Esiste un modo, nel codice o con argomenti JVM, per sovrascrivere l'ora corrente, come presentata tramite System.currentTimeMillis, oltre a modificare manualmente l'orologio di sistema sul computer host?

Un piccolo sfondo:

Abbiamo un sistema che esegue una serie di lavori di contabilità che ruotano gran parte della loro logica intorno alla data corrente (ovvero 1 ° del mese, 1 ° dell'anno, ecc.)

Sfortunatamente, un sacco di codice legacy chiama funzioni come new Date()o Calendar.getInstance(), che alla fine richiamano entrambe System.currentTimeMillis.

A scopo di test, al momento, siamo bloccati con l'aggiornamento manuale dell'orologio di sistema per manipolare l'ora e la data in cui il codice ritiene che il test sia in esecuzione.

Quindi la mia domanda è:

C'è un modo per ignorare ciò che viene restituito System.currentTimeMillis? Ad esempio, dire alla JVM di aggiungere o sottrarre automaticamente alcuni offset prima di tornare da quel metodo?

Grazie in anticipo!


1
Non so più se è rilevante, ma esiste un altro metodo per ottenere questo risultato con AspectJ, vedi la mia risposta su: stackoverflow.com/questions/18239859/…
Nándor Előd Fekete,

@ NándorElődFekete la soluzione nel link è interessante, tuttavia richiede la ricompilazione del codice. Mi chiedo se il poster originale abbia la capacità di ricompilarsi, dato il fatto che sostiene di avere a che fare con il codice legacy.
Cleberz,

1
@cleberz Una delle belle proprietà di AspectJ è che opera direttamente sul bytecode, quindi non ha bisogno del codice sorgente originale per funzionare.
Nándor Előd Fekete,

@ NándorElődFekete grazie mille per il suggerimento. Non ero a conoscenza dell'aspetto della strumentazione a livello di codice (specialmente la strumentazione delle classi JDK). Mi ci è voluto un po ', ma sono stato in grado di capire che dovevo sia una tessitura in fase di compilazione di rt.jar sia una tessitura in fase di caricamento delle classi non jdk per soddisfare le mie esigenze (ignorare System. currentTimeMillis () e System.nanoTime ()).
Cleberz,

@cleberz Ho un'altra risposta per tessere attraverso le classi JRE , puoi anche verificarlo.
Nándor Előd Fekete,

Risposte:


115

Ho fortemente raccomando che invece di fare scherzi con l'orologio di sistema, è stringere i denti e refactoring che il codice legacy per utilizzare un orologio sostituibile. Idealmente, ciò dovrebbe essere fatto con l'iniezione di dipendenza, ma anche se si usasse un singleton sostituibile, si otterrebbe la testabilità.

Questo potrebbe quasi essere automatizzato con la ricerca e la sostituzione per la versione singleton:

  • Sostituisci Calendar.getInstance()con Clock.getInstance().getCalendarInstance().
  • Sostituisci new Date()conClock.getInstance().newDate()
  • Sostituisci System.currentTimeMillis()conClock.getInstance().currentTimeMillis()

(ecc. come richiesto)

Dopo aver fatto il primo passo, puoi sostituire il singleton con DI un po 'alla volta.


50
Disordinare il codice astrattando o racchiudendo tutte le potenziali API per aumentare la testabilità è IMHO non una buona idea. Anche se puoi eseguire il refactoring una volta con una semplice ricerca e sostituzione, il codice diventa molto più difficile da leggere, comprendere e mantenere. Diversi framework simulati dovrebbero essere in grado di modificare il comportamento di System.currentTimeMillis, in caso contrario, utilizzare AOP o la strumentalizzazione fatta da sé è la scelta migliore.
jarnbjo,

21
@jarnbjo: Beh, naturalmente sei il benvenuto alla tua opinione, ma questa è una tecnica IMO piuttosto consolidata, e l'ho sicuramente usata con grande efficacia. Non solo migliora la testabilità, ma rende anche esplicita la tua dipendenza dall'ora del sistema, che può essere utile quando ottieni una panoramica del codice.
Jon Skeet,

4
@ virgo47: penso che dovremo concordare di non essere d'accordo. Non vedo "affermare esplicitamente le tue dipendenze" come inquinare il codice - lo vedo come un modo naturale per rendere il codice più chiaro. Né vedo nascondere inutilmente quelle dipendenze tramite chiamate statiche come "perfettamente accettabili".
Jon Skeet,

26
AGGIORNAMENTO Il nuovo pacchetto java.time incorporato in Java 8 include una java.time.Clockclasse "per consentire orologi alternativi da collegare come e quando richiesto".
Basil Bourque,

7
Questa risposta implica che DI è tutto buono. Personalmente, devo ancora vedere un'applicazione nel mondo reale che ne faccia buon uso. Invece, vediamo una profusione di interfacce separate senza senso, "oggetti" senza stato, "oggetti" solo dati e classi di coesione bassa. IMO, semplicità e design orientato agli oggetti (con oggetti veri e significativi) è una scelta migliore. Per quanto riguarda i staticmetodi, assicurarsi che non siano OO, ma iniettare una dipendenza senza stato la cui istanza non opera su uno stato di istanza non è davvero migliore; è solo un modo stravagante di mascherare ciò che è effettivamente un comportamento "statico".
Rogério,

78

tl; dr

Esiste un modo, nel codice o con argomenti JVM, per sovrascrivere l'ora corrente, come presentata tramite System.currentTimeMillis, oltre a modificare manualmente l'orologio di sistema sul computer host?

Sì.

Instant.now( 
    Clock.fixed( 
        Instant.parse( "2016-01-23T12:34:56Z"), ZoneOffset.UTC
    )
)

Clock In java.time

Abbiamo una nuova soluzione al problema di una sostituzione dell'orologio collegabile per facilitare i test con falsi valori di data e ora. Il pacchetto java.time in Java 8 include una classe astratta java.time.Clock, con uno scopo esplicito:

per consentire il collegamento di orologi alternativi come e quando richiesto

È possibile collegare la propria implementazione di Clock, anche se probabilmente è possibile trovarne uno già realizzato per soddisfare le proprie esigenze. Per comodità, java.time include metodi statici per produrre implementazioni speciali. Queste implementazioni alternative possono essere utili durante i test.

Cadenza alterata

I vari tick…metodi producono orologi che incrementano il momento attuale con una cadenza diversa.

L'impostazione predefinita Clockriporta un tempo aggiornato con la frequenza di millisecondi in Java 8 e Java 9 e nanosecondi (a seconda dell'hardware). Puoi chiedere che il vero momento attuale venga segnalato con una granularità diversa.

Falsi orologi

Alcuni orologi possono mentire, producendo un risultato diverso da quello dell'orologio hardware del sistema operativo host.

  • fixed - Riporta un singolo momento immutabile (non incrementale) come il momento corrente.
  • offset- Riporta il momento attuale ma spostato Durationdall'argomento passato .

Ad esempio, blocca nel primo momento del primo Natale di quest'anno. in altre parole, quando Babbo Natale e le sue renne si fermano per la prima volta . Il primo fuso orario al giorno d'oggi sembra essere Pacific/Kiritimatialle +14:00.

LocalDate ld = LocalDate.now( ZoneId.of( "America/Montreal" ) );
LocalDate xmasThisYear = MonthDay.of( Month.DECEMBER , 25 ).atYear( ld.getYear() );
ZoneId earliestXmasZone = ZoneId.of( "Pacific/Kiritimati" ) ;
ZonedDateTime zdtEarliestXmasThisYear = xmasThisYear.atStartOfDay( earliestXmasZone );
Instant instantEarliestXmasThisYear = zdtEarliestXmasThisYear.toInstant();
Clock clockEarliestXmasThisYear = Clock.fixed( instantEarliestXmasThisYear , earliestXmasZone );

Utilizzare quello speciale orologio fisso per restituire sempre lo stesso momento. Abbiamo il primo momento del giorno di Natale a Kiritimati , con UTC che mostra un orologio da parete di quattordici ore prima, alle 10 di mattina della data precedente del 24 dicembre.

Instant instant = Instant.now( clockEarliestXmasThisYear );
ZonedDateTime zdt = ZonedDateTime.now( clockEarliestXmasThisYear );

instant.toString (): 24-12-2016T10: 00: 00Z

zdt.toString (): 25-12-2016T00: 00 + 14: 00 [Pacifico / Kiritimati]

Vedi codice live su IdeOne.com .

Ora vera, fuso orario diverso

È possibile controllare quale fuso orario è assegnato dall'implementazione Clock. Questo potrebbe essere utile in alcuni test. Ma non lo consiglio nel codice di produzione, dove dovresti sempre specificare esplicitamente l'opzione ZoneIdo gli ZoneOffsetargomenti.

È possibile specificare che UTC sia la zona predefinita.

ZonedDateTime zdtClockSystemUTC = ZonedDateTime.now ( Clock.systemUTC () );

È possibile specificare qualsiasi fuso orario particolare. Specificare un nome proprio fuso orario nel formato continent/region, come ad esempio America/Montreal, Africa/Casablancao Pacific/Auckland. Non usare mai il 3-4 lettera sigla come ad esempio ESTo ISTcome sono non fusi orari veri e propri, non standardizzati, e nemmeno unico (!).

ZonedDateTime zdtClockSystem = ZonedDateTime.now ( Clock.system ( ZoneId.of ( "America/Montreal" ) ) );

È possibile specificare che il fuso orario predefinito corrente della JVM dovrebbe essere il valore predefinito per un Clockoggetto particolare .

ZonedDateTime zdtClockSystemDefaultZone = ZonedDateTime.now ( Clock.systemDefaultZone () );

Esegui questo codice per confrontare. Si noti che riportano tutti lo stesso momento, lo stesso punto nella sequenza temporale. Differiscono solo nel tempo dell'orologio da parete ; in altre parole, tre modi per dire la stessa cosa, tre modi per mostrare lo stesso momento.

System.out.println ( "zdtClockSystemUTC.toString(): " + zdtClockSystemUTC );
System.out.println ( "zdtClockSystem.toString(): " + zdtClockSystem );
System.out.println ( "zdtClockSystemDefaultZone.toString(): " + zdtClockSystemDefaultZone );

America/Los_Angeles era la zona predefinita corrente JVM sul computer che eseguiva questo codice.

zdtClockSystemUTC.toString (): 2016-12-31T20: 52: 39.688Z

zdtClockSystem.toString (): 2016-12-31T15: 52: 39.750-05: 00 [America / Montreal]

zdtClockSystemDefaultZone.toString (): 2016-12-31T12: 52: 39.762-08: 00 [America / Los_Angeles]

La Instantclasse è sempre in UTC per definizione. Quindi questi tre Clockusi relativi alla zona hanno esattamente lo stesso effetto.

Instant instantClockSystemUTC = Instant.now ( Clock.systemUTC () );
Instant instantClockSystem = Instant.now ( Clock.system ( ZoneId.of ( "America/Montreal" ) ) );
Instant instantClockSystemDefaultZone = Instant.now ( Clock.systemDefaultZone () );

instantClockSystemUTC.toString (): 2016-12-31T20: 52: 39.763Z

instantClockSystem.toString (): 2016-12-31T20: 52: 39.763Z

instantClockSystemDefaultZone.toString (): 2016-12-31T20: 52: 39.763Z

Orologio predefinito

L'implementazione utilizzata di default per Instant.nowè quella restituita da Clock.systemUTC(). Questa è l'implementazione utilizzata quando non si specifica a Clock. Guarda tu stesso nel codice sorgente Java 9 pre-release perInstant.now .

public static Instant now() {
    return Clock.systemUTC().instant();
}

L'impostazione predefinita Clockper OffsetDateTime.nowe ZonedDateTime.nowè Clock.systemDefaultZone(). Vedi il codice sorgente .

public static ZonedDateTime now() {
    return now(Clock.systemDefaultZone());
}

Il comportamento delle implementazioni predefinite è cambiato tra Java 8 e Java 9. In Java 8, il momento attuale viene catturato con una risoluzione solo in millisecondi nonostante la capacità delle classi di memorizzare una risoluzione di nanosecondi . Java 9 offre una nuova implementazione in grado di catturare il momento attuale con una risoluzione di nanosecondi, a seconda, ovviamente, della capacità dell'orologio hardware del tuo computer.


Informazioni su java.time

Il framework java.time è integrato in Java 8 e versioni successive. Queste classi soppiantare la vecchia fastidiosi legacy classi data-time come java.util.Date, Calendar, e SimpleDateFormat.

Per saperne di più, consulta il tutorial Oracle . E cerca Stack Overflow per molti esempi e spiegazioni. La specifica è JSR 310 .

Il progetto Joda-Time , ora in modalità manutenzione , consiglia la migrazione alle classi java.time .

Puoi scambiare oggetti java.time direttamente con il tuo database. Utilizzare un driver JDBC conforme a JDBC 4.2 o successivo. Non c'è bisogno di stringhe, non c'è bisogno di java.sql.*classi. Hibernate 5 e JPA 2.2 supportano java.time .

Dove ottenere le classi java.time?


questa è la risposta giusta
bharal

42

Come detto da Jon Skeet :

"Usa Joda Time" è quasi sempre la risposta migliore a qualsiasi domanda riguardante "come posso ottenere X con java.util.Date/Calendar?"

Quindi ecco qui (supponendo che tu abbia appena sostituito tutto new Date()con 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();

Se vuoi importare una libreria che ha un'interfaccia (vedi il commento di Jon sotto), puoi semplicemente usare Prevayler's Clock , che fornirà implementazioni e l'interfaccia standard. Il barattolo pieno è solo 96kB, quindi non dovrebbe rompere la banca ...


13
No, non lo consiglierei - non ti spinge verso un orologio veramente sostituibile; ti fa usare la statica dappertutto. L'uso di Joda Time è ovviamente una buona idea, ma vorrei comunque utilizzare un'interfaccia Clock o simile.
Jon Skeet,

@Jon: True - per i test, va bene, ma è meglio avere un'interfaccia.
Stephen

5
@Jon: Questo è un modo semplice e perfetto per IMHO se vuoi testare il codice dipendente dal tempo che utilizza già JodaTime. Cercare di reinventare la ruota introducendo l'ennesimo strato / quadro di astrazione non è la strada da percorrere, se tutto fosse già stato risolto per te con JodaTime.
Stefan Haberl,

2
@JonSkeet: Questo non era quello che Mike stava chiedendo in origine. Voleva uno strumento per testare il codice dipendente dal tempo e non un orologio sostituibile completo nel codice di produzione. Preferisco mantenere la mia architettura complessa quanto basta ma il più semplice possibile. Introdurre un ulteriore livello di astrazione (cioè l'interfaccia) qui non è semplicemente necessario
Stefan Haberl

2
@StefanHaberl: Ci sono molte cose che non sono strettamente "necessarie" - ma quello che sto suggerendo è rendere esplicita una dipendenza già presente e più facilmente verificabile in isolamento. Fingere il tempo di sistema con la statica ha tutti i normali problemi di statica: è lo stato globale senza una buona ragione . Mike stava chiedendo un modo per scrivere un codice testabile che utilizzava la data e l'ora correnti. Credo ancora che il mio suggerimento sia molto più pulito (e chiarisca le dipendenze) rispetto a un effetto statico efficace.
Jon Skeet,

15

Mentre l'utilizzo di un modello DateFactory sembra carino, non copre le librerie che non puoi controllare - immagina l'annotazione di convalida @Past con l'implementazione che si basa su System.currentTimeMillis (ce ne sono).

Ecco perché usiamo jmockit per deridere direttamente l'ora del sistema:

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);

Poiché non è possibile raggiungere il valore originale non modificato di millis, utilizziamo invece nano timer - questo non è correlato all'orologio da parete, ma qui è sufficiente il tempo relativo:

// 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;
}

C'è un problema documentato, che con HotSpot il tempo torna alla normalità dopo una serie di chiamate - ecco il rapporto sul problema: http://code.google.com/p/jmockit/issues/detail?id=43

Per ovviare a questo, dobbiamo attivare un'ottimizzazione HotSpot specifica: eseguire JVM con questo argomento -XX:-Inline.

Anche se questo potrebbe non essere perfetto per la produzione, va bene per i test ed è assolutamente trasparente per l'applicazione, specialmente quando DataFactory non ha senso per gli affari e viene introdotto solo a causa dei test. Sarebbe bello avere l'opzione JVM integrata per funzionare in tempi diversi, peccato che non sia possibile senza hack come questo.

La storia completa è nel mio post sul blog qui: http://virgo47.wordpress.com/2012/06/22/changing-system-time-in-java/

La classe completa e pratica SystemTimeShifter è fornita nel post. La classe può essere utilizzata nei test o può essere utilizzata come prima classe principale prima della classe reale reale in modo molto semplice per eseguire l'applicazione (o anche l'intero appserver) in un altro momento. Naturalmente, ciò è inteso principalmente a scopo di test, non per l'ambiente di produzione.

EDIT luglio 2014: JMockit è cambiato molto di recente e sei obbligato a utilizzare JMockit 1.0 per utilizzarlo correttamente (IIRC). Sicuramente non è possibile eseguire l'aggiornamento alla versione più recente in cui l'interfaccia è completamente diversa. Stavo pensando di inserire solo le cose necessarie, ma poiché non abbiamo bisogno di questa cosa nei nostri nuovi progetti, non sto sviluppando affatto questa cosa.


1
Il fatto che stai cambiando il tempo per ogni classe va in entrambi i modi ... per esempio se deridi nanoTime invece di currentTimeMillis dovresti stare molto attento a non interrompere java.util.concurrent. Come regola generale, se una libreria di terze parti è difficile da usare nei test, non dovresti deridere gli input della libreria: dovresti deridere la libreria stessa.
Dan Berindei,

1
Usiamo questa tecnica per i test in cui non prendiamo in giro nient'altro. È un test di integrazione e testiamo l'intero stack se fa quello che dovrebbe quando una certa attività si verifica la prima volta l'anno, ecc. Aspetto positivo di nanoTime, per fortuna non abbiamo bisogno di deriderlo perché non ha un orologio da parete significato a tutti. Mi piacerebbe vedere Java con funzionalità di time shift in quanto ne avevo bisogno più di una volta e fare casini con l'ora del sistema a causa di ciò è molto sfortunato.
Virgo47,

7

Powermock funziona alla grande. L'ho usato solo per deridere System.currentTimeMillis().


Ho provato a usare Powermock, e se lo capissi correttamente, non deriderebbe davvero il lato della chiamata. Invece trova tutti i chiamanti e quindi applica la simulazione. Ciò potrebbe richiedere molto tempo, poiché è necessario lasciarlo controllare OGNI classe nel proprio percorso di classe se si desidera essere veramente sicuri che la propria applicazione funzioni in tempo spostato. Correggimi se sbaglio sul modo di deridere Powermock.
virgo47,

1
@ virgo47 No, l'hai capito male. PowerMock non "controllerà mai ogni classe nel tuo percorso di classe"; controllerà solo le classi che gli dirai specificamente di controllare (attraverso l' @PrepareForTestannotazione sulla classe test).
Rogério,

1
@ Rogério - è esattamente così che lo capisco - ho detto "devi lasciarlo ...". Cioè se ti aspetti di chiamare System.currentTimeMillisovunque sul tuo percorso di classe (qualsiasi lib) devi controllare ogni classe. Questo è ciò che intendevo, nient'altro. Il punto è che deridi quel comportamento dal lato del chiamante ("lo dici specificamente"). Questo è OK per test semplici ma non per test in cui non si può essere sicuri di ciò che chiama quel metodo da dove (ad esempio test di componenti più complessi con le librerie coinvolte). Ciò non significa che Powermock sia affatto sbagliato, significa solo che non è possibile utilizzarlo per questo tipo di test.
Virgo47

1
@ virgo47 Sì, capisco cosa intendi. Pensavo intendessi dire che PowerMock avrebbe dovuto esaminare automaticamente ogni classe nel percorso di classe, il che ovviamente sarebbe assurdo. In pratica, tuttavia, per i test unitari di solito sappiamo quali classi leggono l'ora, quindi non è un problema specificarlo; per i test di integrazione, infatti, il requisito in PowerMock di avere tutte le classi di utilizzo specificate può essere un problema.
Rogério,

6

Utilizzare la programmazione orientata agli aspetti (AOP, ad esempio AspectJ) per tessere la classe System per restituire un valore predefinito che è possibile impostare nei casi di test.

O tessere le classi di applicazione per reindirizzare la chiamata a System.currentTimeMillis()o new Date()ad un'altra classe di utilità di tua scelta.

Le classi di sistema di tessitura ( java.lang.*) sono tuttavia un po 'più complicate e potrebbe essere necessario eseguire la tessitura offline per rt.jar e utilizzare un JDK / rt.jar separato per i test.

Si chiama tessitura binaria e ci sono anche strumenti speciali per eseguire la tessitura di classi di sistema e aggirare alcuni problemi con ciò (ad esempio l'avvio della VM potrebbe non funzionare)


Funziona, ovviamente, ma è molto più facile da fare con uno strumento beffardo che con uno strumento AOP; quest'ultimo tipo di strumento è troppo generico per lo scopo in questione.
Rogério,

3

Non esiste davvero un modo per farlo direttamente nella VM, ma si potrebbe fare qualcosa per impostare programmaticamente l'ora di sistema sulla macchina di prova. La maggior parte (tutti?) Del sistema operativo dispone di comandi da riga di comando per eseguire questa operazione.


Ad esempio, su Windows i comandi datee time. Su Linux il datecomando.
Jeremy Raymond,

Perché non dovrebbe essere possibile farlo con AOP o strumentalizzazione (ci sono già stato)?
jarnbjo,

3

Un modo di lavorare per sovrascrivere l'ora corrente del sistema ai fini del test JUnit in un'applicazione Web Java 8 con EasyMock, senza Joda Time e senza PowerMock.

Ecco cosa devi fare:

Cosa deve essere fatto nella classe testata

Passo 1

Aggiungi un nuovo java.time.Clockattributo alla classe testata MyServicee assicurati che il nuovo attributo sia inizializzato correttamente ai valori predefiniti con un blocco di istanza o un costruttore:

import java.time.Clock;
import java.time.LocalDateTime;

public class MyService {
  // (...)
  private Clock clock;
  public Clock getClock() { return clock; }
  public void setClock(Clock newClock) { clock = newClock; }

  public void initDefaultClock() {
    setClock(
      Clock.system(
        Clock.systemDefaultZone().getZone() 
        // You can just as well use
        // java.util.TimeZone.getDefault().toZoneId() instead
      )
    );
  }
  { 
    initDefaultClock(); // initialisation in an instantiation block, but 
                        // it can be done in a constructor just as well
  }
  // (...)
}

Passo 2

Iniettare il nuovo attributo clocknel metodo che richiede una data-ora corrente. Ad esempio, nel mio caso ho dovuto eseguire un controllo per verificare se una data memorizzata nella base di dati è avvenuta prima LocalDateTime.now(), con la quale ho sostituito LocalDateTime.now(clock), in questo modo:

import java.time.Clock;
import java.time.LocalDateTime;

public class MyService {
  // (...)
  protected void doExecute() {
    LocalDateTime dateToBeCompared = someLogic.whichReturns().aDate().fromDB();
    while (dateToBeCompared.isBefore(LocalDateTime.now(clock))) {
      someOtherLogic();
    }
  }
  // (...) 
}

Cosa deve essere fatto nella classe di test

Passaggio 3

Nella classe test, crea un oggetto orologio simulato e inseriscilo nell'istanza della classe testata prima di chiamare il metodo testato doExecute(), quindi ripristinalo subito dopo, in questo modo:

import java.time.Clock;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import org.junit.Test;

public class MyServiceTest {
  // (...)
  private int year = 2017;
  private int month = 2;
  private int day = 3;

  @Test
  public void doExecuteTest() throws Exception {
    // (...) EasyMock stuff like mock(..), expect(..), replay(..) and whatnot

    MyService myService = new MyService();
    Clock mockClock =
      Clock.fixed(
        LocalDateTime.of(year, month, day, 0, 0).toInstant(OffsetDateTime.now().getOffset()),
        Clock.systemDefaultZone().getZone() // or java.util.TimeZone.getDefault().toZoneId()
      );
    myService.setClock(mockClock); // set it before calling the tested method

    myService.doExecute(); // calling tested method 

    myService.initDefaultClock(); // reset the clock to default right afterwards with our own previously created method

    // (...) remaining EasyMock stuff: verify(..) and assertEquals(..)
    }
  }

Controllalo in modalità debug e vedrai che la data del 3 febbraio 2017 è stata correttamente iniettata myServicenell'istanza e utilizzata nelle istruzioni di confronto, quindi è stata correttamente ripristinata alla data corrente con initDefaultClock().


2

Secondo me solo una soluzione non invasiva può funzionare. Soprattutto se hai librerie esterne e una grande base di codice legacy non c'è modo affidabile per deridere il tempo.

JMockit ... funziona solo per un numero limitato di volte

PowerMock & Co ... deve deridere i client su System.currentTimeMillis (). Ancora una volta un'opzione invasiva.

Da ciò vedo solo l' approccio javaagent o aop menzionato come trasparente per l'intero sistema. Qualcuno l'ha fatto e potrebbe indicare una soluzione del genere?

@jarnbjo: potresti mostrare un po 'del codice javaagent per favore?


3
soluzione jmockit funzionante per un numero illimitato di volte con un piccolo -XX: -Inline hack (su HotSpot di Sun): virgo47.wordpress.com/2012/06/22/changing-system-time-in-java Viene fornita una classe completa e pratica SystemTimeShifter nella posta. La classe può essere utilizzata nei test o può essere utilizzata come prima classe principale prima della classe reale reale in modo molto semplice per eseguire l'applicazione (o anche l'intero appserver) in un altro momento. Naturalmente, ciò è inteso principalmente a scopo di test, non per l'ambiente di produzione.
virgo47,

2

Se stai usando Linux, puoi usare il ramo master di libfaketime o al momento del test esegui il commit 4ce2835 .

Basta impostare la variabile d'ambiente con il tempo con cui si desidera deridere l'applicazione java ed eseguirla usando ld-preloading:

# bash
export FAKETIME="1985-10-26 01:21:00"
export DONT_FAKE_MONOTONIC=1
LD_PRELOAD=/usr/local/lib/faketime/libfaketimeMT.so.1 java -jar myapp.jar

La seconda variabile d'ambiente è fondamentale per le applicazioni Java, che altrimenti si congelerebbero. Richiede il ramo master di libfaketime al momento della scrittura.

Se desideri modificare il tempo di un servizio gestito da systemd, aggiungi quanto segue alle sostituzioni dei file dell'unità, ad esempio per elasticsearch /etc/systemd/system/elasticsearch.service.d/override.conf:

[Service]
Environment="FAKETIME=2017-10-31 23:00:00"
Environment="DONT_FAKE_MONOTONIC=1"
Environment="LD_PRELOAD=/usr/local/lib/faketime/libfaketimeMT.so.1"

Non dimenticare di ricaricare systemd usando `systemctl daemon-reload


-1

Se vuoi deridere il metodo con System.currentTimeMillis()argomento, puoi passare la anyLong()classe Matchers come argomento.

PS Sono in grado di eseguire correttamente il mio caso di test usando il trucco di cui sopra e solo per condividere maggiori dettagli sul mio test che sto usando i framework PowerMock e Mockito.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.