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

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...

Matchers y jMock

A medida que la complejidad de nuestro código aumenta, también tiende a aumentar la complejidad de los tests. Al principio nos va bien con asegurarnos que la salida del método es la esperada. Pero llegado el momento en que necesitamos comprobar qué valores contiene cada campo, o qué información se utiliza para llamar a las otras clases, es hora de empezar a utilizar Matchers . Los Matchers nos permiten especificar las restricciones o condiciones que deben cumplir los parámetros de entrada. En anteriores posts, cada vez que escribíamos with(any(String.class)) , estábamos utilizando un Matcher (uno muy genérico, que permite cualquier cadena como parámetro de entrada). Imaginemos que queremos asegurarnos que nuestro método getWeatherForecast() invoca al método getWeather() con una instancia de país, cuyo nombre debe ser el mismo que el recibido. Para ello vamos a utilizar el método hasProperty() de la clase org.hamcrest.Matchers : [...] import static org.hamcrest.Matchers.*; im...

Robolectric (I): Unit Testing en Android

Al intentar aprender Android y cómo desarrollar software sobre su plataforma, lo primero que sorprende es lo complicado (y lento) que resulta hacer TDD. Por suerte, la gente de Pivotal Labs ha creado Robolectric , un framework que permite realizar Unit Tests sin disponer de un emulador Android activo. En primer lugar, debes disponer de un proyecto Android , generado desde Eclipse o desde Línea de Comandos . A partir del proyecto, y usando Maven , la instalación es inmediata. Simplemente hay que crear un fichero pom.xml en la raíz del proyecto, con las siguientes dependencias: [... Cabecera del Proyecto ...] <dependencies> <dependency> <groupId>com.google.android</groupId> <artifactId>android</artifactId> <version>2.3.3</version> <scope>provided</scope> </dependency> <dependency> <groupId>com.pivotallabs</groupId> <artifactId>r...