Archivo de la etiqueta: Mock

Mocks… o el TDD y yo

… segunda Parte de TDD is dead? … o yo y el TDD

[Mock y test]Este es el segundo y último artículo de la serie que completa el anterior post TDD is dead? … o yo y el TDD.

Trataré de explicar como defino y codifico mis propios objetos ‘mock‘, y de como los uso tanto para pruebas unitarias, como también en pruebas de integración entre clases.

Y siendo más especifico mostrar como se pueden codificar tres tipos de mock utilizados habitualmente:

  •  Simular la capa de persistencia a base de datos.
  •  El que se utiliza para comprobar que se ha llamado a la interfaz que representa desde el objeto que nos interesa probar.
  • El utilizado simplemente para poder pasar las pruebas y que no nos interesa su comportamiento.

Y como dijo el maestro:

“… hablar es barato, vamos a ver un poco de código…”

Demo

Para ilustrar el ejemplo utilizaré una aplicación web que mediante un formulario html sencillo llamará con ajax a un servicio REST. Consiste en dar de alta códigos de promoción de una web.

[Pantallazo de la Demo Ejemplo Mock]

[Pantallado de la demo]

Los datos que se piden son la promoción, el nombre, el correo electrónico y el código de promoción. Se validará que el correo electrónico no esté ya utilizado y si todo es correcto se enviará el correo a la persona y se mostrarán los datos de los códigos ya dados de alta.

La demo es completamente operativa y se puede usar. Se puede descargar el código aquí:

[Logo GitHub]MoksExample en GitHub

Los detalles de como funciona y como se puede probar están en el archivo README.md

La parte de servicios REST es Java, montada sobre Spring. En el ecosistema de Spring, Spring-Data es el mediador con la capa de persistencia de datos. En el ejemplo se usará como interfaz con la base de datos. Como framework de tests unitarios usaré Junit. Maven será la herramienta de compilación, ejecución de test y construcción del proyecto. Como es una demo para estos fines se usa Hsqldb, base de datos en memoria.

Simular la capa de persistencia de BBDD

En la demo existen dos entidades PROMOTION y PROMOTION_CODE. La primera es el maestro de promociones y en la segunda se almacenarán todos los códigos de promoción relacionados con las primeras. Los objetos de negocio que las definen son los beans: Promotion y PromotionCode

Spring-Data se basa en el patrón repositorio. A grandes rasgos y simplificando, Spring-Data implementa la interfaz de las operaciones CRUD y el resto de consultas que se pueden realizar para cada entidad de la capa de persistencia (usualmente una tabla de una base de datos).

Existen por tanto dos interfaces definidos en el pakage com.logicaalternativa.ejemplomock.repository cuya misión es definir las operaciones CRUD de las entidades

En este caso los dos extienden del interfaz de Spring-Data JpaRepository.

Con Spring-Data sólo tienes que definir la interfaz con las diferentes operaciones de consulta, inserción y modificación que se van a realizar de la entidad y este se encarga de implementar la interfaz. Esto hace que resulte muy fácil codificar tus propios mocks. Después para los test se inyectarán por inversión de control al objeto de negocio.

Para los test he implementado los dos mock de cada uno de los interfaces. Están en el package com.logicaalternativa.ejemplomock.repository.mock .

Las dos clases son muy similares y siguen la misma línea. Sólo se ha implementado las operaciones necesarias para realizar los test. La idea es esa codificar sólo lo estrictamente necesario. Se añadirá o se modificará código cuando toque, si para un futuro test es necesario implementar algún otro método más.

Centrándome por ejemplo en el mock PromotionCodeRepositoryMock, el ‘quid de la cuestión‘ está en que he definido un atributo lista de tipo PromotionCode.

1
public class PromotionCodeRepositoryMock implements PromotionCodeRepository {
2
   private List<PromotionCode> promotionCode;
3
...
4
}

Este es accesible mediante los métodos públicos:

1
public class PromotionCodeRepositoryMock implements PromotionCodeRepository {
2
...
3
   public List<PromotionCode> getPromotionCode()
4
...
5
   public void setPromotionCode(PromotionCode...promotionCode)
6
...
7
}

He codificado los métodos CRUD para que operen con esta lista. Por ejemplo, así he codificado el método que sirve para obtener un código de promoción a través de su clave primaria.

01
public class PromotionCodeRepositoryMock implements PromotionCodeRepository {
02
...
03
  public PromotionCode findOne( final String email ) {		
04
    int index = findIndex( email );		
05
    if ( index != -1 ) {
06
      return clone( getPromotionCode().get( index ) ); 
07
    }	
08
    return null;
09
  }
10
...
11
}

Que lo que hace es encontrar el índice de la lista de códigos de promoción con el mismo email. Si lo encuentra devuelve una copia de ese objeto. Para entrar más en detalle te invito a revisar los métodos findIndex, clone y el resto de métodos que he implementado de PromotionCodeRepositoryMock

Otro ejemplo es el método saveAndFlush:

01
public class PromotionCodeRepositoryMock implements PromotionCodeRepository {
02
...
03
  public <S extends PromotionCode> S saveAndFlush(S promotionCode ) {
04
    if ( getPromotionCode() == null ) {
05
     this.promotionCode = new ArrayList<PromotionCode>();
06
     getPromotionCode().add( promotionCode );
07
     return promotionCode ;
08
    }
09
    final String email = promotionCode != null ? promotionCode.getEmail() : null;
10
    int index = findIndex( email ) ; 
11
    if ( index == -1 ) {
12
     getPromotionCode().add( promotionCode );
13
    } else {
14
     getPromotionCode().set( index, promotionCode );
15
    }
16
    return promotionCode;
17
  }
18
...
19
}

Que busca en la lista el objeto con el mismo email. Si lo encuentra actualiza el objeto de la lista y sino, lo añade a la lista.

¿Cómo se usa?

El test AddCodeBusinessImpTest prueba el objeto de negocio AddCodeBusinessImp.

La clase de negocio (AddCodeBusinessImp) comprueba que existe el código de promoción en la BBDD y utiliza para ello PromotionRepository. En el método setUp() del test se inicializa un objeto PromotionRepositoryMock con el objeto Promotion deseado y este mock se inserta por inversión de control a la instancia de AddCodeBussinessImp.

01
public class AddCodeBusinessImpTest {
02
  ...
03
  private AddCodeBusinessImp addCodeBusinessImp;
04
  ...
05
  private PromotionRepositoryMock promotionRepositoryMock;
06
  private PromotionCodeRepositoryMock promotionCodeRepositoryMock;	
07
  private Promotion promotion;
08
  ...
09
  @Before
10
  public void setUp() throws Exception {
11
    promotion = new Promotion();
12
    promotion.setIdPromotion( 1 );
13
    ...    
14
    promotionRepositoryMock = new PromotionRepositoryMock();
15
    promotionRepositoryMock.setPromotions( promotion );
16
    ...  
17
    addCodeBusinessImp = new AddCodeBusinessImp();
18
    addCodeBusinessImp.setPromotionRepository( promotionRepositoryMock );
19
    ...
20
  }
21
  ...
22
}

promotion, promotionRepositoryMock, addCodeBusinessImp son atributos privados de AddCodeBusinessImpTest.

Internamente AddCodeBusinessImp llama al método findOne de PromotionRepository con el identificador de la promoción (el del maestro de promociones) del nuevo código que se quiere insertar.

Si queremos testear que ocurre si no encuentra la promoción en la BBDD, simplemente se hace lo que en el método testPromotionNotBbDd de AddCodeBusinessImpTest:

01
public class AddCodeBusinessImpTest {
02
  ...
03
  @Test
04
  public void testPromotionNotBbDd() {
05
    try {
06
      promotionRepositoryMock.setPromotions( ( Promotion[] ) null ); 
07
      addCodeBusinessImp.validateAndAdd( promotionCode );
08
      ...
09
  }
10
  ...
11
}

Si la lista de promociones en PromotionRepositoryMock es nula no se podrá encontrar la promoción (El método findOne devolverá nulo).

En este mismo test también se utiliza PromotionCodeRepositoryMock. Inyectado también por inversión de control a la instancia de AddCodeBusinessImp, en este caso, sirve para comprobar que se ha insertado el código de promoción. Se hace en el test testOk

01
public class AddCodeBusinessImpTest {
02
  ...
03
  @Test
04
  public void testOk() {
05
    try {
06
      addCodeBusinessImp.validateAndAdd( promotionCode );
07
      final List<PromotionCode> promotionCodeBbDCodes = promotionCodeRepositoryMock.getPromotionCode();
08
      boolean result = promotionCodeBbDCodes != null
09
                && promotionCodeBbDCodes.size() == 1
10
                && promotionCode.equals( promotionCodeBbDCodes.get( 0 ) )
11
               ;
12
      ...
13
  }
14
  ...
15
}

Para tener una visión más clara de cual es la idea, lo mejor es revisar cada uno de los test Junit de AddCodeBusinessImpTest Creo que puede ser de gran ayuda para tener una visión global de lo que quiero plantear . He intentado que sean claros y autoexplicativos.

Mock de comprobación de llamada a interfaz y datos correctos.

Lo que interesa en este caso es comprobar que un objeto de negocio hace la llamada a un interfaz con los datos correctos. Lo que se hace es ‘mockear‘ esa interfaz capturando los datos de la llamada para comprobar después de que estos son correctos.

En la demo existe un objeto de negocio, SendMailCodePromotionImp, que a partir de una dirección de correo manda el código de promoción por email. Este objeto de negocio realiza internamente una llamada a la interfaz JavaMailSender que implementa Spring (JavaMailSenderImpl) para enviar el correo electrónico.

JavaMailServerMock, que implementa también el interfaz JavaMailServer, es el mock que va a recoger los datos de la llamada. En este caso sólo me interesaba saber la dirección de correo a la que se envía el correo electrónico. Para ello he implementado el método send:

01
public class JavaMailServerMock implements JavaMailSender {
02
  ...
03
  private String sendTo;
04
  ...
05
  @Override
06
  public void send( MimeMessage mimeMessage ) throws MailException {
07
    String[] to = null;
08
    try {
09
      to = mimeMessage.getHeader("To");
10
    } catch (MessagingException e) {
11
      logger.error( "[send] ".concat( e.getMessage() ), e );  
12
      to = null;      
13
    }
14
    setSendTo( to != null && to.length > 0 ? to[0] : null );
15
  }
16
  ...
17
  public String getSendTo() {
18
    return sendTo;
19
  }
20
  public void setSendTo(String sendTo) {
21
    this.sendTo = sendTo;
22
  }
23
}

Se recoge del argumento de entrada la cabecera “To” para almacenarla en un atributo de la clase.

¿Cómo se usa?

En el test SendMailCodePromotionImpTest se utiliza este Mock. En el método setUp(), se instancia y se inserta por inversión de control al objeto SendMailCodePromotionImp.

01
public class SendMailCodePromotionImpTest {
02
  ...
03
  private SendMailCodePromotionImp sendMailCodePromotionImp;
04
  ...
05
  private JavaMailServerMock javaMailServerMock;
06
  ...
07
  @Before
08
  public void setUp() throws Exception {
09
    ...
10
    javaMailServerMock = new JavaMailServerMock();
11
    ...    
12
    sendMailCodePromotionImp = new SendMailCodePromotionImp();
13
    ...
14
    sendMailCodePromotionImp.setJavaMailSender( javaMailServerMock );
15
    ...
16
  }
17
  ...
18
}

El test testOk de SendMailCodePromotionImpTest sirve como ejemplo para comprobar si se ha realizado correctamente la llamada:

01
public class SendMailCodePromotionImpTest {
02
  ...
03
  public void testOk() {
04
    try {
05
      sendMailCodePromotionImp.sendMailCodePromotion( email, locale );
06
      final String sendTo = javaMailServerMock.getSendTo();
07
      boolean res = sendTo != null
08
              && sendTo.equals( nameUser.toUpperCase() + " <" + email +">"  );
09
    ...
10
  }
11
  ...
12
}

Donde después de hacer la llamada al método sendMailCodePromotion del objeto de negocio se obtiene el atributo de sendTo del mock para comprobar que se envía a la misma dirección de correo electrónico.

En este mock sólo se comprueba la cabecera ‘To‘. También se podría comprobar el texto del mensaje o cualquier otro dato enviado desde el objeto de negocio.

Mock para pasar las pruebas

En la demo he codificado MessageSourceMock que implementa el interfaz MessageSource, utilidad que tiene Spring para la internacionalización. Este mock sólo me interesa para poder probar otras funcionalidades. Ha sido necesario codificado porque el objeto de negocio lo necesitaba, pero, en este caso, el resultado de su llamada era irrelevante. Solamente se ha codificado el método getMessage :

1
public class MessageSourceMock implements MessageSource {
2
  ...
3
  @Test
4
  public String getMessage(String arg0, Object[] arg1, Locale arg2) throws NoSuchMessageException {
5
    return arg0;
6
  }
7
  ...
8
}

Que como puedes comprobar, devuelve directamente la entrada.

¿Cómo se usa?

En el test SendMailCodePromotionImpTest se utiliza este Mock. En el método setUp()Se instancia y se inserta por inversión de control al objeto
SendMailCodePromotionImp.

01
public class SendMailCodePromotionImpTest {
02
  ...
03
  private SendMailCodePromotionImp sendMailCodePromotionImp;
04
  ...
05
  private MessageSourceMock messageSourceMock;
06
  ...
07
    @Before
08
  public void setUp() throws Exception {
09
    ...
10
    messageSourceMock = new MessageSourceMock();
11
    sendMailCodePromotionImp = new SendMailCodePromotionImp();
12
    ...
13
    sendMailCodePromotionImp.setMessageSource( messageSourceMock );
14
  }
15
  ...
16
}

Ya ahora al hacer la llamada getSendTo del objeto de negocio no aparece la tan temida NullPointerException 😉

Conclusiones

Como se puede ver, con un poco de ingenio es relativamente sencillo crearte tus propios objetos mock sin tener que recurrir a Frameworks. La creación de los mock me ayudan a comprender mejor como funciona la aplicación y ver las clases involucradas como verdaderas cajas negras. Esta es la gracia del paradigma de la orientación a objetos.

No necesariamente tiene que llevar demasiado tiempo el codificarlos. Sólo se debería ‘picar’ lo que necesitas cuando lo necesitas y mucho es reaprobechable. Al final del proyecto seguro que tienes un buen juego de objetos mock para pruebas.

Otra ventaja, es que no hay reflexión, no existe bbdd en memoria para los test, ni ha sido necesario levantar un entorno para pruebas… el resultado es que los test van ‘volaos‘.

M.E.

P.D.: Para esta demo, de esto que te vas liando y liando… y poco a poco me ‘currado’ un framework MVC hecho en JavaScript. Cuando lo organice un poco será material para próximos post.

TDD is dead? … o yo y el TDD. Primera Parte

Test-driven development (TDD)”, en la lengua de Cervantes “Desarrollo guiado por pruebas”… Palabras malditas para algunos… Últimamente se dice que está muerto… “TDD is dead?”.


Actualización:
La segunda parte de este artículo es «Mocks… o el TDD y yo»


¿Qué es?

A grandes rasgos, consiste en primero diseñar y realizar los test de una determinada funcionalidad (lo usual es una clase de negocio). En este punto los test no pasan => Test ‘en rojo‘.

[Esquema TDD]A continuación se codifica, centrándonos en que pasen todos los test. Una vez que todos están ‘ok’ (Test en ‘verde‘) se refactoriza el código. Se da un repaso final para que quede todo correcto y pulir los flecos que quedan.

Estos test serán siempre independientes. No debe importar el orden en que se lancen. Se añaden a la herramienta de integración continua para que se pasen en cada compilación/build y asegurarse así que no ha habido ‘efectos colaterales’ en cada modificación de código fuente.

«TDD is dead?«.. el debate.

Si partimos de este enfoque ¿es todo ‘testeable‘? ¿Se ve condicionado tu diseño del software poder hacer este tipo de pruebas? ¿Cuanto tardan tus test? ¿Es asumible este tiempo?

Todo esto y más argumentos se han puesto encima de la mesa. Hay quien opina que que en el TDD puro sólo es para entornos académicos y blogs y que en el mundo real no se puede llevar al extremo. Hay pruebas que no se pueden replicar tan fácilmente y que dependen de un entorno desplegado (terceros sistemas).

Tampoco es recomendable que se condicione el diseño y la arquitectura para poder realizar los test. Debe ser al contrario los test se deben adaptar al diseño.

El TDD se basa en pasar continuamente los test, por lo tanto deben ser lo más rápidos posible. En algunos casos el tiempo de lanzar toda la batería de test no es despreciable. En este punto los Frameworks de ‘mock‘ basados en generación de clases por reflexión están en el punto de mira.

Y a pesar de todo esto, esta forma de desarrollar me ha hecho la vida más sencilla. Lo intento explicar:

Mi Kung-fu, Mi TDD

He llegado a esta metodología tarde. No fue hasta que empezando con un proyecto desde cero, que me puse en serio a seguir este paradigma. Mi conversión ha sido tal, que confieso que ahora ya casi no sé prescindir de los test al enfrentarme a tirar código. Primero me planteo los tests y después implemento. Aunque esto no es siempre así, tengo bastante flexibilidad.

Si tengo claro ‘lo que hay que hacer‘ hago todos del tirón. Repaso todos los casos de uso, las posibles entradas, salidas y los errores y cada caso lo reflejo en un test.  En estos casos lo más normal es que tarde más en diseñar los test que en codificar. El proceso es, de manera iterativa, terminar una parte de la codificación e ir pasando los test, hasta que no quede ninguno con error y la funcionalidad esté terminada.

En los casos que no tengo tan claro lo que tiene que hacer la clase, quizás porque, por ejemplo, tenga que ‘repartir’ la responsabilidad en otra clase, empiezo realizando los test que si tengo claro, validando entradas y resultados parciales. Codifico y logró que se pasen los test. Me replanteo o no la  estructura y vuelvo a codificar los nuevos test y terminar de implementar la parte que queda. Todo también de manera iterativa.

En cualquier caso siempre habrá uno o varios test con las entradas y la salida correctas. Los pongo al principio de la clase de test y me sirve de documentación. De un vistazo se puede saber el comportamiento y el control de errores realizado.

Sobre los ‘mock’

Para poder realizar los test ‘mokeo’ las clases que necesito para probar la funcionalidad. Empecé realizando mis propios objetos Mock porque no quería que mi foco se perdiera en el aprendizaje de un nuevo Framework de ‘mokeo‘.

En la arquitectura en la que suelo trabajar Spring y Spring-Data para acceso a base de datos, en lo que todo es un interfaz, es supersencillo crear el Mock que genere la salida que necesitas. Estos objetos son reaprovechables, ya que en un proyecto normalmente las clases están relacionadas, y utilizas el mismo Mock para varios test.

Esto en realidad no va en contra de ‘la máxima‘ de que los test no puede ser un desarrollo en sí, ya que ‘sólo codifico la parte que necesito‘. Si en un segundo test necesito más funcionalidad completo el comportamiento del Mock.

Resulta que después, leyendo post como este  («When to mock«) en el Blog de Uncle Bob Martin me di cuenta que no iba tan mal encaminado.

Estoy preparando un segundo artículo donde intentaré explicar con ejemplos como hago los ‘mock‘.

Qué he ganado

Sobre todo una nueva visión de enfrentarme al desarrollo, para mi más enriquecedora. Ahora lo primero que pienso son los parámetros que voy a pasar a la clase y que salida quiero obtener. Valoro que entradas podrían causar error y como quiero realizar este control de errores. Esto hace que tenga una idea mucho más clara de lo que va hacer la clase y de que parte de la funcionalidad va a ser responsable.

Para simular la base de datos utilizo mis propios ‘mock‘ implementando los interfaces de Spring-Data y no uso una BBDD en memoria. Esto también me ha ayudado a entender mejor que datos necesito y tener mucho más claro el modelo.

He ganado en seguridad. Casi consigo que el 100% del código sea testeable. Gracias a los test estoy seguro de que se ha pasado por cada uno de los ‘if’ del  código.

Es significativo que ahora uso mucho menos el ‘debug‘ para saber que está fallando y corregir errores. Puede resultar paradójico pero en muchos casos, con los test consigo comportamientos que es difícil de obtener para obtener el mismo resultado en una prueba de integración. Normalmente hago pocos ajustes en el código cuando lo pruebo desplegado en un servidor de aplicaciones, contra una base de datos de verdad, …

Tengo menos bug y más control de la interrelaciones que tiene el código. Si hay alguna modificación de uan parte del código que afecta a otra, los ‘test’ pueden evitar muchos de estos problemas de interrelación. Problemas que suelen surgir, ‘como siempre pasa’ (ley de Murphy), en producción.

Como contrapartida se tarda más en el desarrollo y el proceso de generación de los test no es ‘tan divertido‘ como puede parecer. De todas maneras las ventajas que he obtenido me salen a cuenta y doy el tiempo por bien invertido.

Reflexiones

Realmente en mis proyectos, y a pesar de integrarse con más de un sistema de terceros no he tenido muchos problemas en generar los test. No he sentido que el diseño del software se haya visto condicionado.

Al hilo de esto, estoy acostumbrado a trabajar con Spring y su inversión de control. Hay quienes le ‘chirría’ tener que definir un interfaz aunque sólo haya una implementación.  A mí definirlo me parece más limpio indicando así el api pública de la clase.

Esto para los test viene muy bien. En muchos casos por cada interfaz existirá la implementación de la funcionalidad y otra ‘mock‘ para simular el comportamiento para los test de otra clase.  Insisto no lo veo como un condicionante de diseño para facilitar los test. Hubiera definido el interfaz de todas maneras incluso en el caso de que no hubiera hecho los test.  Sobre esto me parece interesante este post How tdd affect my designs«)

El hecho de implementar mis propios ‘mock‘ y que estos no sean creados utilizando reflexión y sólo haya que instanciarlos, hace que la lentitud no sea un problema.

Así que, en la práctica, no me he encontrado muchos problemas técnico/filosóficos a la hora de utilizar el TDD. Queda pendiente la segunda parte de este artículo de ejemplos de ‘mock’ que estoy utilizado.

Espero que os sirva.

M.E.