Sovrascrittura del tempo di sistema per il test in Java

1. Panoramica

In questo breve tutorial, ci concentreremo su diversi modi per sovrascrivere il tempo di sistema per il test .

A volte c'è una logica intorno alla data corrente nel nostro codice. Forse alcune chiamate di funzione come new Date () o Calendar.getInstance () , che alla fine chiameranno System.CurrentTimeMillis .

Per un'introduzione all'uso di Java Clock , fare riferimento a questo articolo qui. Oppure, per l'uso di AspectJ, qui.

2. Utilizzo dell'orologio in java.time

Il pacchetto java.time in Java 8 include una classe astratta java.time.Clock con lo scopo di consentire il collegamento di orologi alternativi come e quando richiesto. Con ciò, possiamo collegare la nostra implementazione o trovarne una già realizzata per soddisfare le nostre esigenze.

Per raggiungere i nostri obiettivi, la libreria di cui sopra include metodi statici per produrre implementazioni speciali . Ne useremo due che restituiscono un'implementazione immutabile, thread-safe e serializzabile.

Il primo è risolto . Da esso, possiamo ottenere un orologio che restituisce sempre lo stesso istante , assicurando che i test non dipendano dall'orologio corrente.

Per usarlo, abbiamo bisogno di un Instant e di uno ZoneOffset :

Instant.now(Clock.fixed( Instant.parse("2018-08-22T10:00:00Z"), ZoneOffset.UTC))

Il secondo metodo statico è offset . In questo, un orologio avvolge un altro orologio che lo rende l'oggetto restituito in grado di ottenere istanti successivi o precedenti della durata specificata.

In altre parole, è possibile simulare l'esecuzione in futuro, in passato o in qualsiasi momento arbitrario :

Clock constantClock = Clock.fixed(ofEpochMilli(0), ZoneId.systemDefault()); // go to the future: Clock clock = Clock.offset(constantClock, Duration.ofSeconds(10)); // rewind back with a negative value: clock = Clock.offset(constantClock, Duration.ofSeconds(-5)); // the 0 duration returns to the same clock: clock = Clock.offset(constClock, Duration.ZERO);

Con la classe Duration , è possibile manipolare da nanosecondi a giorni. Inoltre, possiamo negare una durata, il che significa ottenere una copia di questa durata con la lunghezza negata.

3. Utilizzo della programmazione orientata agli aspetti

Un altro modo per sovrascrivere l'ora di sistema è tramite AOP. Con questo approccio, siamo in grado di tessere la classe System per restituire un valore predefinito che possiamo impostare nei nostri casi di test .

Inoltre, è possibile intrecciare le classi dell'applicazione per reindirizzare la chiamata a System.currentTimeMillis () oa new Date () a un'altra nostra classe di utilità.

Un modo per implementare questo è attraverso l'uso di AspectJ:

public aspect ChangeCallsToCurrentTimeInMillisMethod { long around(): call(public static native long java.lang.System.currentTimeMillis()) && within(user.code.base.pckg.*) { return 0; } } 

Nell'esempio precedente, stiamo intercettando ogni chiamata a System.currentTimeMillis () all'interno di un pacchetto specificato , che in questo caso è user.code.base.pckg. * , E restituiamo zero ogni volta che si verifica questo evento .

È qui che possiamo dichiarare la nostra implementazione per ottenere il tempo desiderato in millisecondi.

Un vantaggio dell'utilizzo di AspectJ è che opera direttamente a livello di bytecode, quindi non ha bisogno del codice sorgente originale per funzionare.

Per questo motivo, non avremmo bisogno di ricompilarlo.

4. Derisione del metodo Instant.now ()

Possiamo usare la classe Instant per rappresentare un punto istantaneo sulla timeline. Normalmente, possiamo usarlo per registrare i timestamp degli eventi nella nostra applicazione. Il metodo now () di questa classe ci consente di ottenere l'istante corrente dall'orologio di sistema nel fuso orario UTC.

Vediamo alcune alternative per modificare il suo comportamento durante il test.

4.1. Sovraccarico ora () con un orologio

Possiamo sovraccaricare il metodo now () con un'istanza Clock fissa . Molte delle classi nel pacchetto java.time hanno un metodo now () che accetta un parametro Clock , il che rende questo il nostro approccio preferito:

@Test public void givenFixedClock_whenNow_thenGetFixedInstant() { String instantExpected = "2014-12-22T10:15:30Z"; Clock clock = Clock.fixed(Instant.parse(instantExpected), ZoneId.of("UTC")); Instant instant = Instant.now(clock); assertThat(instant.toString()).isEqualTo(instantExpected); }

4.2. Utilizzando PowerMock

Inoltre, se dobbiamo modificare il comportamento del metodo now () senza inviare parametri, possiamo utilizzare PowerMock :

@RunWith(PowerMockRunner.class) @PrepareForTest({ Instant.class }) public class InstantUnitTest { @Test public void givenInstantMock_whenNow_thenGetFixedInstant() { String instantExpected = "2014-12-22T10:15:30Z"; Clock clock = Clock.fixed(Instant.parse(instantExpected), ZoneId.of("UTC")); Instant instant = Instant.now(clock); mockStatic(Instant.class); when(Instant.now()).thenReturn(instant); Instant now = Instant.now(); assertThat(now.toString()).isEqualTo(instantExpected); } }

4.3. Utilizzando JMockit

In alternativa, possiamo utilizzare la libreria JMockit .

JMockit ci offre due modi per deridere un metodo statico. Uno sta usando la classe MockUp :

@Test public void givenInstantWithJMock_whenNow_thenGetFixedInstant() { String instantExpected = "2014-12-21T10:15:30Z"; Clock clock = Clock.fixed(Instant.parse(instantExpected), ZoneId.of("UTC")); new MockUp() { @Mock public Instant now() { return Instant.now(clock); } }; Instant now = Instant.now(); assertThat(now.toString()).isEqualTo(instantExpected); }

E l'altro sta usando la classe Expectations :

@Test public void givenInstantWithExpectations_whenNow_thenGetFixedInstant() { Clock clock = Clock.fixed(Instant.parse("2014-12-23T10:15:30.00Z"), ZoneId.of("UTC")); Instant instantExpected = Instant.now(clock); new Expectations(Instant.class) { { Instant.now(); result = instantExpected; } }; Instant now = Instant.now(); assertThat(now).isEqualTo(instantExpected); }

5. Deridere il metodo LocalDateTime.now ()

Un'altra classe utile nel pacchetto java.time è la classe LocalDateTime . Questa classe rappresenta una data-ora senza un fuso orario nel sistema di calendario ISO-8601. Il metodo now () di questa classe ci consente di ottenere la data-ora corrente dall'orologio di sistema nel fuso orario predefinito.

Possiamo usare le stesse alternative per deriderlo come abbiamo visto prima. Ad esempio, sovraccaricando now () con un Clock fisso :

@Test public void givenFixedClock_whenNow_thenGetFixedLocalDateTime() { Clock clock = Clock.fixed(Instant.parse("2014-12-22T10:15:30.00Z"), ZoneId.of("UTC")); String dateTimeExpected = "2014-12-22T10:15:30"; LocalDateTime dateTime = LocalDateTime.now(clock); assertThat(dateTime).isEqualTo(dateTimeExpected); }

6. Conclusione

In questo articolo, abbiamo esplorato diversi modi per sovrascrivere il tempo di sistema per il test. Per prima cosa, abbiamo esaminato il pacchetto nativo java.time e la sua classe Clock . Successivamente, abbiamo visto come applicare un aspetto per tessere la classe System . Infine, abbiamo visto diverse alternative al deridere il metodo now () sulle classi Instant e LocalDateTime .

Come sempre, è possibile trovare esempi di codice su GitHub.