Las VAN de Alt.Net hispano
April 16th, 2010
Actualizacion: La hora según las equivalencias horarias seria a las 19:00 en Canarias, 20:00 en Madrid
Alt.Net es un movimiento de la comunidad .Net que surgió ya hace varios años y que trata de unir a personas interesadas por la tecnología .Net, que comparten un espíritu de superación profesional y mejora ...
Filed under Planeta
|
Comment (0)
Matchers y jMock
April 5th, 2010
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.*;
import static org.junit.Assert.*;
import org.hamcrest.Matcher;
import org.jmock.*;
import org.junit.*;
// Omito el resto de imports internos
public class BombServiceWithMatchersTest {
// Omito la declaración de dependencias/mocks y los métodos setUp() y tearDown()
@Test
public void testGetWeatherShouldUseTheSameCountry() throws Exception {
mockery.checking(new Expectations() {
{
Matcher<Country> matcher = hasProperty("name", equalTo("Avalon"));
exactly(1).of(bombSoapClientMock).getWeather(with(matcher));
will(returnValue(1));
}
});
int result = bombService.getWeatherForecast("Avalon");
assertEquals(1, result);
}
// ... other tests
}
Ahora tenemos un test que se asegura que nuestro servicio enviará siempre el mismo parámetro y, si en algún momento lo modificamos por error, el test nos avisará.
Como véis, es muy sencillo definir restricciones a los parámetros que utilizamos. Simplemente hay que tener en cuenta algunos detalles, que dependen del Matcher utilizado. Por ejemplo, el método hasProperty() requiere que la clase que estás evaluando (en mi caso, Country) cumpla las convenciones de Sun sobre getters/setters. Si en la clase Country no existieran los métodos getName() y setName(), el test no habría funcionado (y ¡cuidado!, porque este tipo de errores sólo informan que el método no se ha invocado, y no el porqué).
Para completar la explicación del ejemplo, hay que fijarse que el segundo parámetro del método hasProperty() es, a su vez, un Matcher. Allí podríamos definir cualquier restricción que afectara al valor del campo (en este caso forzamos que sea el valor conocido).
Con los Matchers se pueden conseguir unos tests muy completos, y que nos permitan detectar fácilmente si algún cambio en nuestro código rompe con el comportamiento esperado. Os recomiendo mirar todos los métodos disponibles de la clase org.hamcrest.Matchers, entre los que destacan:
- hasEntry(): muy útil al trabajar con Maps, ya que nos permite definir restricciones sobre claves y valores.
- hasKey(): comprueba si un Map contiene la clave facilitada.
- hasItem(): comprueba si el elemento existe dentro de una List.
- startsWith(): muy interesante al trabajar con Strings.
- ...y muchos más que nos pueden ayudar en cualquier momento...
Por último, para realizar más de una comprobación por parámetro disponemos del método allOf(). Por ejemplo:
allOf(hasKey("key1"), hasKey("key2"));. Sin embargo, como la clase Matcher utiliza Generics de forma intensa, os puede dar muchos problemas de compilación, intentando encajar los tipos de datos. Por ejemplo, el siguiente código no compila, ya que se espera un Matcher<Object> en vez de un Matcher<Country> (dichosas reglas de los Generics, que van buscando el "mínimo común múltiplo"):
Matcher<Country> matcher = allOf(
hasProperty("name", equalTo("Avalon")),
hasProperty("id", equalTo("1")));
exactly(1).of(bombSoapClientMock).getWeather(with(matcher));
La única solución que he encontrado es definir cada Matcher por separado, para que los tipos de cada variable se vayan acomodando por pasos, en vez de intentar inferirlos de golpe:
Matcher<Country> m1 = hasProperty("name", equalTo("Avalon"));
Matcher<Country> m2 = hasProperty("id", equalTo("1"));
Matcher<Country> matcher = allOf(m1, m2);
exactly(1).of(bombSoapClientMock).getWeather(with(matcher));
Espero que los Matchers os sirvan de ayuda para definir tests mucho más específicos. Filed under Planeta | Comment (0)