Anule Java System.currentTimeMillis para probar el código sensible al tiempo

129

¿Hay alguna forma, ya sea en código o con argumentos JVM, para anular la hora actual, tal como se presenta a través System.currentTimeMillis, que no sea cambiar manualmente el reloj del sistema en la máquina host?

Un poco de historia:

Tenemos un sistema que ejecuta una serie de trabajos de contabilidad que giran gran parte de su lógica en torno a la fecha actual (es decir, 1 de mes, 1 de año, etc.)

Desafortunadamente, muchos de los códigos heredados llaman a funciones tales como new Date()o Calendar.getInstance(), las cuales eventualmente llaman a System.currentTimeMillis.

Para fines de prueba, en este momento, estamos atascados con la actualización manual del reloj del sistema para manipular a qué hora y fecha el código cree que la prueba se está ejecutando.

Entonces mi pregunta es:

¿Hay alguna forma de anular lo que devuelve System.currentTimeMillis? Por ejemplo, ¿decirle a la JVM que agregue o reste automáticamente algún desplazamiento antes de regresar de ese método?

¡Gracias por adelantado!

Mike Clark
fuente
1
Ya no sé si es relevante, pero hay otro método para lograr esto con AspectJ, vea mi respuesta en: stackoverflow.com/questions/18239859/…
Nándor Előd Fekete
@ NándorElődFekete la solución en el enlace es interesante, sin embargo, requiere volver a compilar el código. Me pregunto si el póster original tiene la capacidad de volver a compilar, dado el hecho de que afirma estar tratando con código heredado.
cleberz
1
@cleberz Una de las buenas propiedades de AspectJ es que opera en bytecode directamente, por lo que no necesita el código fuente original para funcionar.
Nándor Előd Fekete
@ NándorElődFekete muchas gracias por la pista. No tenía conocimiento de la instrumentación de nivel de bytecode del aspecto J (especialmente la instrumentación de clases JDK). Me tomó un tiempo, pero pude darme cuenta de que tenía que tejer tanto el tiempo de compilación de rt.jar como el tejido de tiempo de carga de las clases que no son jdk para satisfacer mis necesidades (anular el sistema. currentTimeMillis () y System.nanoTime ()).
cleberz
@cleberz Tengo otra respuesta para tejer a través de las clases de JRE , también puedes consultarla.
Nándor Előd Fekete

Respuestas:

115

Recomiendo encarecidamente que, en lugar de jugar con el reloj del sistema, muerda la bala y refactorice ese código heredado para usar un reloj reemplazable. Idealmente, eso debería hacerse con la inyección de dependencia, pero incluso si usara un singleton reemplazable, obtendría capacidad de prueba.

Esto podría casi automatizarse con la búsqueda y reemplazo de la versión singleton:

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

(etc. según sea necesario)

Una vez que haya dado el primer paso, puede reemplazar el singleton con DI un poco a la vez.

Jon Skeet
fuente
50
Acomodar su código abstrayendo o envolviendo todas las API potenciales para aumentar la capacidad de prueba, en mi humilde opinión, no es una muy buena idea. Incluso si puede refactorizar una vez con una simple búsqueda y reemplazo, el código se vuelve mucho más difícil de leer, comprender y mantener. Varios marcos simulados deberían poder modificar el comportamiento de System.currentTimeMillis, si no, el uso de AOP o instrumentalización propia es la mejor opción.
jarnbjo
21
@jarnbjo: Bueno, tu opinión es bienvenida, por supuesto, pero esta es una técnica IMO bastante bien establecida, y ciertamente la he usado con gran efecto. No solo mejora la capacidad de prueba, sino que también hace explícita su dependencia del tiempo del sistema, lo que puede ser útil al obtener una descripción general del código.
Jon Skeet
44
@ virgo47: creo que tendremos que aceptar no estar de acuerdo. No veo "declarar explícitamente sus dependencias" como contaminante del código, lo veo como una forma natural de aclarar el código. Tampoco veo que ocultar esas dependencias mediante llamadas estáticas sea innecesariamente "perfectamente aceptable".
Jon Skeet
26
ACTUALIZACIÓN El nuevo paquete java.time integrado en Java 8 incluye una java.time.Clockclase "para permitir la conexión de relojes alternativos cuando sea necesario".
Basil Bourque
77
Esta respuesta implica que DI es todo bueno. Personalmente, todavía no he visto una aplicación en el mundo real que la haga buen uso. En cambio, vemos una profusión de interfaces separadas sin sentido, "objetos" sin estado, "objetos" de solo datos y clases de baja cohesión. IMO, simplicidad y diseño orientado a objetos (con objetos verdaderos y con estado) es una mejor opción. Con respecto a los staticmétodos, asegúrese de que no sean OO, pero inyectar una dependencia sin estado cuya instancia no opere en ningún estado de instancia no es realmente mejor; de todos modos, es solo una forma elegante de disfrazar lo que efectivamente es un comportamiento "estático".
Rogério
78

tl; dr

¿Hay alguna manera, ya sea en código o con argumentos JVM, para anular la hora actual, tal como se presenta a través de System.currentTimeMillis, que no sea cambiar manualmente el reloj del sistema en la máquina host?

Si.

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

Clock En java.time

Tenemos una nueva solución al problema de un reemplazo de reloj conectable para facilitar la prueba con valores falsos de fecha y hora. El paquete java.time en Java 8 incluye una clase abstracta java.time.Clock, con un propósito explícito:

para permitir que se conecten relojes alternativos cuando sea necesario

Puede conectar su propia implementación de Clock, aunque es probable que pueda encontrar una ya hecha para satisfacer sus necesidades. Para su conveniencia, java.time incluye métodos estáticos para producir implementaciones especiales. Estas implementaciones alternativas pueden ser valiosas durante las pruebas.

Cadencia alterada

Los diversos tick…métodos producen relojes que incrementan el momento actual con una cadencia diferente.

El valor predeterminado Clockinforma un tiempo actualizado con tanta frecuencia como milisegundos en Java 8 y en Java 9 tan fino como nanosegundos (dependiendo de su hardware). Puede solicitar que se informe el momento actual real con una granularidad diferente.

Relojes falsos

Algunos relojes pueden mentir, produciendo un resultado diferente al del reloj de hardware del sistema operativo host.

  • fixed - Informa un momento único que no cambia (sin incremento) como el momento actual.
  • offset- Informa el momento actual, pero cambiado por el Durationargumento pasado .

Por ejemplo, asegure el primer momento de la primera Navidad de este año. en otras palabras, cuando Santa y sus renos hacen su primera parada . La zona horaria más temprana hoy en día parece estar Pacific/Kiritimatien +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 );

Use ese reloj fijo especial para regresar siempre el mismo momento. Tenemos el primer momento del día de Navidad en Kiritimati , con UTC mostrando un reloj de pared de catorce horas antes, las 10 de la mañana de la fecha anterior del 24 de diciembre.

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

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

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

Ver código en vivo en IdeOne.com .

Hora verdadera, zona horaria diferente

Puede controlar qué zona horaria asigna la Clockimplementación. Esto podría ser útil en algunas pruebas. Pero no lo recomiendo en el código de producción, donde siempre debe especificar explícitamente los opcionales ZoneIdo los ZoneOffsetargumentos.

Puede especificar que UTC sea la zona predeterminada.

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

Puede especificar cualquier zona horaria en particular. Especificar un nombre de zona horaria correcta en el formato de continent/region, por ejemplo America/Montreal, Africa/Casablancao Pacific/Auckland. Nunca use el 3-4 abreviatura de letras tales como ESTo IST, ya que son no verdaderas zonas de tiempo, no estandarizados, y ni siquiera único (!).

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

Puede especificar que la zona horaria predeterminada actual de la JVM sea la predeterminada para un Clockobjeto en particular .

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

Ejecute este código para comparar. Tenga en cuenta que todos informan el mismo momento, el mismo punto en la línea de tiempo. Solo difieren en la hora del reloj de pared ; en otras palabras, tres formas de decir lo mismo, tres formas de mostrar el mismo 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 predeterminada actual de JVM en la computadora que ejecutó este código.

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

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

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

La Instantclase siempre está en UTC por definición. Entonces, estos tres Clockusos relacionados con la zona tienen exactamente el mismo efecto.

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

Reloj predeterminado

La implementación utilizada por defecto para Instant.nowes la que devuelve Clock.systemUTC(). Esta es la implementación utilizada cuando no especificas a Clock. Compruébelo usted mismo en el código fuente de Java 9 previo al lanzamiento deInstant.now .

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

El valor predeterminado Clockpara OffsetDateTime.nowy ZonedDateTime.nowes Clock.systemDefaultZone(). Ver código fuente .

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

El comportamiento de las implementaciones predeterminadas cambió entre Java 8 y Java 9. En Java 8, el momento actual se captura con una resolución solo en milisegundos a pesar de la capacidad de las clases para almacenar una resolución de nanosegundos . Java 9 trae una nueva implementación capaz de capturar el momento actual con una resolución de nanosegundos, dependiendo, por supuesto, de la capacidad del reloj de hardware de su computadora.


Sobre java.time

El marco java.time está integrado en Java 8 y versiones posteriores. Estas clases suplantar la vieja problemáticos heredados clases de fecha y hora como java.util.Date, Calendar, y SimpleDateFormat.

Para obtener más información, consulte el Tutorial de Oracle . Y busque Stack Overflow para obtener muchos ejemplos y explicaciones. La especificación es JSR 310 .

El proyecto Joda-Time , ahora en modo de mantenimiento , aconseja la migración a las clases java.time .

Puede intercambiar objetos java.time directamente con su base de datos. Utilice un controlador JDBC compatible con JDBC 4.2 o posterior. No hay necesidad de cadenas, no hay necesidad de java.sql.*clases. Hibernate 5 y JPA 2.2 admiten java.time .

¿Dónde obtener las clases java.time?

Albahaca Bourque
fuente
esta es la respuesta correcta
bharal
42

Como dijo Jon Skeet :

"use Joda Time" es casi siempre la mejor respuesta a cualquier pregunta que involucre "¿cómo puedo lograr X con java.util.Date/Calendar?"

Así que aquí va (suponiendo que hayas reemplazado todo 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();

Si desea importar una biblioteca que tenga una interfaz (vea el comentario de Jon a continuación), puede usar Prevayler's Clock , que proporcionará implementaciones y la interfaz estándar. El frasco lleno tiene solo 96kB, por lo que no debería romper el banco ...

Stephen
fuente
13
No, no recomendaría esto, no te mueve hacia un reloj genuinamente reemplazable; te mantiene usando estática en todo momento. Usar Joda Time es, por supuesto, una buena idea, pero todavía me gustaría usar una interfaz de reloj o similar.
Jon Skeet
@ Jon: Cierto: para probar, está bien, pero es mejor tener una interfaz.
Stephen
55
@ Jon: Esta es una manera fácil y perfecta en mi humilde opinión si quieres probar el código dependiente del tiempo que ya usa JodaTime. Intentar reinventar la rueda introduciendo otra capa / marco de abstracción no es el camino a seguir, si todo ya se resolvió con JodaTime.
Stefan Haberl
2
@ JonSkeet: Esto no es lo que Mike estaba pidiendo originalmente. Quería una herramienta para probar el código dependiente del tiempo y no un reloj reemplazable completo en el código de producción. Prefiero mantener mi arquitectura tan compleja como sea necesaria pero lo más simple posible. Introducir una capa adicional de abstracción (es decir, interfaz) aquí simplemente no es necesario
Stefan Haberl
2
@StefanHaberl: Hay muchas cosas que no son estrictamente "necesarias", pero realmente lo que estoy sugiriendo es que una dependencia ya presente sea explícita y más fácilmente comprobable de forma aislada. Fingir el tiempo del sistema con estática tiene todos los problemas normales de la estática: es un estado global sin una buena razón . Mike estaba pidiendo una forma de escribir código comprobable que utilizara la fecha y hora actuales. Todavía creo que mi sugerencia es mucho más limpia (y hace que las dependencias sean más claras) que evitar una estática.
Jon Skeet
15

Si bien usar algún patrón de DateFactory parece agradable, no cubre las bibliotecas que no puede controlar; imagine la anotación de validación @Past con una implementación que se basa en System.currentTimeMillis (existe).

Es por eso que usamos jmockit para burlarse del tiempo del sistema directamente:

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

Debido a que no es posible llegar al valor original no desbloqueado de millis, en su lugar utilizamos un temporizador nano; esto no está relacionado con el reloj de pared, pero el tiempo relativo es suficiente aquí:

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

Hay un problema documentado, que con HotSpot el tiempo vuelve a la normalidad después de varias llamadas: aquí está el informe del problema: http://code.google.com/p/jmockit/issues/detail?id=43

Para superar esto, tenemos que activar una optimización HotSpot específica: ejecutar JVM con este argumento -XX:-Inline.

Si bien esto puede no ser perfecto para la producción, está bien para las pruebas y es absolutamente transparente para la aplicación, especialmente cuando DataFactory no tiene sentido comercial y se introduce solo por las pruebas. Sería bueno tener la opción de JVM incorporada para ejecutarse en un tiempo diferente, lástima que no sea posible sin hacks como este.

La historia completa está en mi blog aquí: http://virgo47.wordpress.com/2012/06/22/changing-system-time-in-java/

Completa clase práctica SystemTimeShifter se proporciona en la publicación. La clase se puede usar en sus pruebas, o se puede usar como la primera clase principal antes de su clase principal real muy fácilmente para ejecutar su aplicación (o incluso el servidor de aplicaciones completo) en un momento diferente. Por supuesto, esto está destinado principalmente a fines de prueba, no para entornos de producción.

EDITAR Julio de 2014: JMockit ha cambiado mucho últimamente y seguramente usará JMockit 1.0 para usarlo correctamente (IIRC). Definitivamente no puedo actualizar a la última versión donde la interfaz es completamente diferente. Estaba pensando en incluir solo las cosas necesarias, pero como no necesitamos esto en nuestros nuevos proyectos, no estoy desarrollando esto en absoluto.

virgo47
fuente
1
El hecho de que esté cambiando el tiempo para cada clase va en ambos sentidos ... por ejemplo, si se burlara de nanoTime en lugar de currentTimeMillis, tendría que tener mucho cuidado de no romper java.util.concurrent. Como regla general, si una biblioteca de terceros es difícil de usar en las pruebas, no debe burlarse de las entradas de la biblioteca: debe burlarse de la biblioteca en sí.
Dan Berindei
1
Utilizamos esta técnica para pruebas en las que no nos burlamos de nada más. Es una prueba de integración y probamos toda la pila si hace lo que debería cuando ocurre alguna actividad la primera vez en el año, etc. Buen punto sobre nanoTime, afortunadamente no necesitamos burlarnos de eso ya que no tiene reloj de pared significado en absoluto. Me encantaría ver Java con funciones de cambio de tiempo, ya que las necesitaba más de una vez y jugar con el tiempo del sistema debido a esto es muy desafortunado.
virgo47
7

Powermock funciona muy bien. Solo lo usé para burlarme System.currentTimeMillis().

ReneS
fuente
Traté de usar Powermock, y si lo entendiera correctamente, realmente no se burlaría del lado del que llama. En su lugar, encuentra todas las personas que llaman y luego aplica el simulacro. Esto puede llevar mucho tiempo, ya que debe dejar que verifique CADA clase en su classpath si desea estar realmente seguro de que su aplicación funciona en un horario diferente. Corrígeme si me equivoco acerca de la forma de burlarse de Powermock.
virgo47
1
@ virgo47 No, lo entendiste mal. PowerMock nunca "verificará cada clase en su classpath"; solo verificará las clases que usted le dice específicamente que verifique (a través de la @PrepareForTestanotación en la clase de prueba).
Rogério
1
@ Rogério, así es exactamente como lo entiendo, dije "tienes que dejarlo ...". Es decir, si espera llamar a System.currentTimeMilliscualquier parte de su classpath (cualquier lib), debe verificar cada clase. A eso me refería, nada más. El punto es que te burlas de ese comportamiento del lado de la persona que llama ("específicamente lo dices"). Esto está bien para pruebas simples, pero no para pruebas en las que no puede estar seguro de qué llama a ese método desde dónde (por ejemplo, pruebas de componentes más complejas con bibliotecas involucradas). Eso no significa que Powermock esté mal en absoluto, solo significa que no puede usarlo para este tipo de prueba.
virgo47
1
@ virgo47 Sí, entiendo lo que quieres decir. Pensé que querías decir que PowerMock tendría que examinar automáticamente cada clase en el classpath, lo que obviamente sería absurdo. Sin embargo, en la práctica, para las pruebas unitarias generalmente sabemos qué clase (s) lee la hora, por lo que no es un problema especificarlo; para las pruebas de integración, de hecho, el requisito en PowerMock de tener todas las clases de uso especificadas puede ser un problema.
Rogério
6

Utilice la Programación orientada a aspectos (AOP, por ejemplo, AspectJ) para tejer la clase del Sistema para devolver un valor predefinido que podría establecer dentro de sus casos de prueba.

O tejer las clases de la aplicación para redirigir la llamada a System.currentTimeMillis()o new Date()a otra clase de utilidad de su propio.

java.lang.*Sin embargo, las clases del sistema de tejido ( ) son un poco más complicadas y es posible que deba realizar un tejido fuera de línea para rt.jar y usar un JDK / rt.jar separado para sus pruebas.

Se llama tejido binario y también hay herramientas especiales para realizar el tejido de las clases del sistema y evitar algunos problemas con eso (por ejemplo, el arranque de la VM puede no funcionar)

mhaller
fuente
Esto funciona, por supuesto, pero es mucho más fácil hacerlo con una herramienta de burla que con una herramienta AOP; El último tipo de herramienta es demasiado genérico para el propósito en cuestión.
Rogério
3

Realmente no hay una manera de hacer esto directamente en la máquina virtual, pero podría hacer algo para programar mediante programación la hora del sistema en la máquina de prueba. La mayoría de los sistemas operativos (¿todos?) Tienen comandos de línea de comandos para hacer esto.

Jeremy Raymond
fuente
Por ejemplo, en windows los comandos datey time. En Linux el datecomando.
Jeremy Raymond el
¿Por qué no debería ser posible hacer esto con AOP o instrumentalización?
jarnbjo
3

Una forma de trabajar para anular el tiempo actual del sistema para propósitos de prueba JUnit en una aplicación web Java 8 con EasyMock, sin Joda Time y sin PowerMock.

Esto es lo que debes hacer:

Lo que hay que hacer en la clase probada

Paso 1

Agregue un nuevo java.time.Clockatributo a la clase probada MyServicey asegúrese de que el nuevo atributo se inicializará correctamente en los valores predeterminados con un bloque de instanciación o un constructor:

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
  }
  // (...)
}

Paso 2

Inyecte el nuevo atributo clocken el método que requiere una fecha y hora actual. Por ejemplo, en mi caso tuve que realizar una verificación de si una fecha almacenada en la base de datos sucedió antes LocalDateTime.now(), con lo que remplacé LocalDateTime.now(clock), así:

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();
    }
  }
  // (...) 
}

Lo que hay que hacer en la clase de prueba.

Paso 3

En la clase de prueba, cree un objeto de reloj simulado e inyéctelo en la instancia de la clase probada justo antes de llamar al método probado doExecute(), luego reinícielo inmediatamente después, de esta manera:

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(..)
    }
  }

Verifíquelo en modo de depuración y verá que la fecha del 3 de febrero de 2017 se ha inyectado correctamente en la myServiceinstancia y se ha utilizado en las instrucciones de comparación, y luego se ha restablecido correctamente a la fecha actual con initDefaultClock().

KiriSakow
fuente
2

En mi opinión, solo una solución no invasiva puede funcionar. Especialmente si tiene bibliotecas externas y una gran base de código heredado, no hay una forma confiable de burlarse del tiempo.

JMockit ... funciona solo por un número restringido de veces

PowerMock & Co ... necesita burlarse de los clientes a System.currentTimeMillis (). De nuevo una opción invasiva.

A partir de esto, solo veo que el enfoque javaagent o aop mencionado es transparente para todo el sistema. ¿Alguien ha hecho eso y podría apuntar a tal solución?

@jarnbjo: ¿podría mostrar algo del código de Java por favor?

usuario1477398
fuente
3
La solución jmockit funciona un número ilimitado de veces con un pequeño -XX: -Hack en línea (en el HotSpot de Sun): virgo47.wordpress.com/2012/06/22/changing-system-time-in-java Se proporciona la clase práctica completa SystemTimeShifter en el post. La clase se puede usar en sus pruebas, o se puede usar como la primera clase principal antes de su clase principal real muy fácilmente para ejecutar su aplicación (o incluso el servidor de aplicaciones completo) en un momento diferente. Por supuesto, esto está destinado principalmente a fines de prueba, no para entornos de producción.
virgo47
2

Si está ejecutando Linux, puede usar la rama maestra de libfaketime, o al momento de probar commit 4ce2835 .

Simplemente configure la variable de entorno con el tiempo con el que desea simular su aplicación Java y ejecútela 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 segunda variable de entorno es primordial para las aplicaciones java, que de lo contrario se congelarían. Requiere la rama maestra de libfaketime en el momento de la escritura.

Si desea cambiar la hora de un servicio administrado systemd, simplemente agregue lo siguiente a las anulaciones de archivos de su unidad, por ejemplo, para elasticsearch esto sería /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"

No olvides recargar systemd usando `systemctl daemon-reload

faxmodem
fuente
-1

Si desea burlarse del método que tiene System.currentTimeMillis()argumento, puede pasar anyLong()de la clase Matchers como argumento.

PD: Puedo ejecutar mi caso de prueba con éxito usando el truco anterior y solo para compartir más detalles sobre mi prueba de que estoy usando los marcos de PowerMock y Mockito.

Paresh Mayani
fuente