Ir al contenido principal

Unit Testing & jMock: expectations comunes

Llega un momento en que algunos de nuestros juegos de prueba empiezan a tener muchas expectations y se empieza a complicar la implementación de nuevos tests o el mantenimiento de los existentes.

Una de las consecuencias de aplicar TDD es que cuánto mayor sea la complejidad del test, mayor será la complejidad de nuestro código, y por lo tanto, de su mantenimiento. Así que si empezamos a tener tests con demasiadas expectations, es buen momento para plantearse el refactoring de alguna de las clases, para simplificar o dividir su funcionalidad.

Si aún así seguimos mantiendo unas expectations muy largas, y que además se comparten entre varios tests, es muy recomendable agruparlas. Gracias a la implementación de jMock, podemos declarar varios bloques de expectations en un mismo test, de tal modo que la prueba deberá cumplir las condiciones de todos sus bloques:

@Test
public void testFindHouseShouldReturnOneHouse() throws Exception {
    mockery.checking(new Expectations() {
        {
            // Expectations #1
        }
    });

    mockery.checking(new Expectations() {
        {
            // Expectations #2
        }
    });

    // ... code of the test ...
}

Gracias a esta característica de jMock, podremos refactorizar y extraer el código común en varios métodos private, dentro de nuestra clase de test. Cada uno de estos métodos contendrá uno (o varios) de los bloques de expectations compartidas. Por ejemplo:

@Test
public void testFindHousesShouldReturnHousesThatMatchColor() throws Exception {

    declareCommonExpectations(); // Expectations comunes

    mockery.checking(new Expectations() {
        {
            // Expectations of this test
        }
    });

    // ... code of the test ...
}

@Test
public void testFindHousesShouldReturnHousesThatMatchLocation() throws Exception {

    declareCommonExpectations(); // Expectations comunes

    mockery.checking(new Expectations() {
        {
            // Expectations of this test
        }
    });

    // ... code of the test ...
}

[...]

private void declareCommonExpectations() throws Exception {

    mockery.checking(new Expectations() {
        {
            // Este bloque contiene todas las expectations
            // comunes que compartirán ambos tests.
        }
    });
}

Este tipo de refactor para agrupar código común nos permitirá reducir bastante la longitud de nuestras clases de test, permitiendo que nos centremos en lo realmente importante: cuál es la entrada esperada, y cuál debe ser la salida. Sin duda, resulta especialmente útil cuando nuestra test suite está repleta de métodos findX() o updateY(), cuyo resultado queremos que sea el mismo en varias de las pruebas.

Comentarios

Entradas populares de este blog

Dependency Injection de HttpClient 3.x

Para un fanático de la Dependency Injection como yo, siempre me ha costado trabajar con Apache HttpClient 3.x y la multitud de maneras que tiene la gente de inicializar los clientes. Además, como usuario asiduo de Spring Framework , es imprescindible disponer de una manera de cargar clientes HTTP desde el contexto XML y que sea sencilla de probar. Una de las soluciones más comunes para crear y configurar los clientes es la declaración de un método init() donde se inicializan todos los parámetros, obligando que nuestra clase conozca todos los detalles y parámetros de este componente externo. Pese a que la solución funciona, no deja de ser complicada de testear, y esto es justamente lo que se quiere evitar desde el Test Driven Development (TDD). Para ello, hay que buscar una fórmula que permita "inyectar" los clientes HTTP, ya configurados en el exterior, permitiendo a la clase de destino centrarse en su objetivo. En primer lugar, hay que conseguir la biblioteca HttpClie

Unit Tests (I): "mocking" la interacción

Indispensables. De entre todos los tipos de tests, son los más sencillos de escribir, aunque son los que permiten encontrar (a tiempo) la mayor cantidad de bugs. ¿Qué es un Unit Test? Mejor contar qué hacen y no lo que son: Un Unit Test debe probar un ÚNICO método de una ÚNICA clase . Simple. Sin excepciones (*) . El método a probar SIEMPRE debe tener un estado inicial conocido y un resultado esperado. ¡Siempre! Si cumple ambas premisas, el test podrá ser ejecutado de forma automática y sin depender de nadie (**) . Considero que probar la interacción de una clase con el resto de componentes es uno de los sistemas que mejores resultados ofrece. Estas interacciones se "simulan" en el mismo test, utilizando mock-objects. Antes de empezar con algún ejemplo de Unit Test con Mock Objects, hay algunos consejos que nos pueden facilitar el trabajo: Cada clase debe tener su Interfaz (***) , exceptuando las java.lang.Exception y poco más. Conocer el patrón de Inyección de Depen

Unit Tests (II): clases sin interfaz

Sigamos con los tests unitarios: ¿qué ocurre si alguna de mis dependencias no tiene interfaz? O simplemente, ¿qué ocurre si no me gusta añadir una interfaz a cada una de mis clases? Aunque recomiendo encarecidamente el uso de interfaces (ayudan a definir y delimitar el alcance de una clase), no es del todo extraño que algunas de nuestras dependencias no las tengan. Como ejemplo, veamos una que seguro que a muchos nos ha tocado implementar: un cliente para leer los mensajes de Twitter . Si decidimos usar el proyecto Twitter4J , nos encontramos con que su cliente twitter4j.Twitter no tiene interfaz. Si intentáramos hacer un test usando la guía del post anterior : [...] private TwitterService twitterService = null; private Mockery mockery = new Mockery(); private Twitter twitterMock = null; @Before public void setUp() { twitterService = new TwitterService(); twitterMock = mockery.mock(Twitter.class); twitterService.setTwitter(twitte