En todos los proyectos aparecen métodos que necesitan realizar comprobaciones de fechas u horas en la lógica de negocio, ya sea consultando la hora actual del sistema, ya sea comparando rangos horarios.
Si antes de implementar el método le damos un pensada a cómo probarlo, aparece una duda importante: ¿cómo puedo suplantar la fecha o la hora para que mis tests no dependan del día o la hora actuales?
Pongamos un ejemplo sencillo: tenemos un método que si lo ejecutamos en un día par, tiene que llamar a un servicio externo. Una primera implementación, sin pensar cómo probarlo, nos daría un código parecido a:
Un código sencillo... ¡que resulta imposible de probar correctamente! Como se puede ver a continuación, si el test se ejecuta en un día par, funcionará. Pero si el test se ejecuta un día impar (situación corriente en un sistema de integración continua), fallará:
Llegados a este punto, a uno se le pueden ocurrir soluciones del estilo: "cambiemos la fecha del sistema"... pero no son muy recomendables. Yo prefiero aprovechar las bondades de la inyección de dependencias, que nos facilitará enormemente la tarea de probar el código.
Una posible solución es crear una clase TimeResolver (y opcionalmente su interfaz ITimeResolver) y utilizarla desde nuestros servicios para consultar la fecha actual:
Gracias a esta ayuda, ya podemos crear un test que simule lo que sucede en cualquier combinación de fechas y horas:
Este par de casos de test servirán para validar que el nuevo código se comporta como esperamos:
¡Conseguido! Ya podemos asegurar el correcto comportamiento de todos los casos que queramos. Sin duda, esta pequeña indirección me ha sido muy útil siempre que he necesitado trabajar con fechas o calendarios. Espero que os sea de utilidad.
Si antes de implementar el método le damos un pensada a cómo probarlo, aparece una duda importante: ¿cómo puedo suplantar la fecha o la hora para que mis tests no dependan del día o la hora actuales?
Pongamos un ejemplo sencillo: tenemos un método que si lo ejecutamos en un día par, tiene que llamar a un servicio externo. Una primera implementación, sin pensar cómo probarlo, nos daría un código parecido a:
[...] public class TimeService { private IExternalService externalService = null; public void timeMethod() { Calendar cal = Calendar.getInstance(); if (cal.get(Calendar.DAY_OF_MONTH) % 2 == 0) { externalService.externalMethod(); } } [...] }
Un código sencillo... ¡que resulta imposible de probar correctamente! Como se puede ver a continuación, si el test se ejecuta en un día par, funcionará. Pero si el test se ejecuta un día impar (situación corriente en un sistema de integración continua), fallará:
[...] public class TimeServiceTest { [...] // ¡¡Este test sólo funciona los días pares!! @Test public void testTimeServiceShouldCallExternalServiceIfDayIsEven() { mockery.checking(new Expectations() { { exactly(1).of(externalServiceMock).externalMethod(); } }); timeService.timeMethod(); } [...] }
Llegados a este punto, a uno se le pueden ocurrir soluciones del estilo: "cambiemos la fecha del sistema"... pero no son muy recomendables. Yo prefiero aprovechar las bondades de la inyección de dependencias, que nos facilitará enormemente la tarea de probar el código.
Una posible solución es crear una clase TimeResolver (y opcionalmente su interfaz ITimeResolver) y utilizarla desde nuestros servicios para consultar la fecha actual:
package utils; import java.util.Calendar; import java.util.Date; public class TimeResolver implements ITimeResolver { @Override public Calendar getCurrentCalendar() { return Calendar.getInstance(); } @Override public Date getCurrentDate() { return new Date(); } }
Gracias a esta ayuda, ya podemos crear un test que simule lo que sucede en cualquier combinación de fechas y horas:
[...] public class TimeServiceTest { private TimeService timeService = null; private Mockery mockery = new Mockery(); private IExternalService externalServiceMock = null; private ITimeResolver timeResolverMock = null; @Before public void setUp() throws Exception { timeService = new TimeService(); externalServiceMock = mockery.mock(IExternalService.class); timeResolverMock = mockery.mock(ITimeResolver.class); timeService.setExternalService(externalServiceMock); timeService.setTimeResolver(timeResolverMock); } @After public void tearDown() throws Exception { mockery.assertIsSatisfied(); } @Test public void testTimeServiceShouldCallExternalServiceIfDayIsEven() { mockery.checking(new Expectations() { { Calendar cal = Calendar.getInstance(); cal.set(Calendar.DAY_OF_MONTH, 8); // Even Day exactly(1).of(timeResolverMock).getCurrentCalendar(); will(returnValue(cal)); exactly(1).of(externalServiceMock).externalMethod(); } }); timeService.timeMethod(); } @Test public void testTimeServiceShouldNotCallExternalServiceIfDayIsOdd() { mockery.checking(new Expectations() { { Calendar cal = Calendar.getInstance(); cal.set(Calendar.DAY_OF_MONTH, 7); // Odd Day exactly(1).of(timeResolverMock).getCurrentCalendar(); will(returnValue(cal)); // External Method will not be called exactly(0).of(externalServiceMock).externalMethod(); } }); timeService.timeMethod(); } }
Este par de casos de test servirán para validar que el nuevo código se comporta como esperamos:
[...] public class TimeService { private IExternalService externalService; private ITimeResolver timeResolver; public void timeMethod() { Calendar cal = timeResolver.getCurrentCalendar(); if (cal.get(Calendar.DAY_OF_MONTH) % 2 == 0) { externalService.externalMethod(); } } [...] }
¡Conseguido! Ya podemos asegurar el correcto comportamiento de todos los casos que queramos. Sin duda, esta pequeña indirección me ha sido muy útil siempre que he necesitado trabajar con fechas o calendarios. Espero que os sea de utilidad.
Comentarios
Publicar un comentario