Aserciones más claras

Desde hace tiempo el uso de pruebas unitarias (unit tests) es algo crucial y cada vez más extendido en todos los proyectos que veo, tanto en proyectos de código abierto como en proyectos empresariales cerrados. Esto hace que el código de test cada vez sea más y más revisado y mantenido.

Una cosa importante para conseguir un código mantenible es que sea lo más legible posible. Como todos sabéis una parte imprescindible de las pruebas unitarias es la aserción de lo que espera la prueba. Cuando la complejidad del código aumenta también lo hacen la complejidad de las pruebas y hoy os vamos a presentar una herramienta para intentar hacer menos complejas las pruebas y también más legibles.

Con los frameworks más conocidos de pruebas unitarias ya se incluyen herramientas para facilitar la extensión de las aserciones básicas, principalmente basadas en Harmcrest, que suele ayudar también a tener mensajes de error más explicativos. Pero estas a veces pueden ser un poco complejas tanto de escribir como de entender.

Para conseguir aserciones aún más claras y fluidas (fluent assertions) existe un proyecto llamado AssertJ (heredero de FEST assertions). A continuación vamos a ver un caso de una aserción usando el modelo basado en Harmcrest:

    @Test
    public void test_age() throws Exception {
        assertThat(odersky.age(), greaterThan(50));
        assertThat(odersky.age(), greaterThan(55));
        assertThat(odersky.age(), lessThan(80));
        assertThat(odersky.age(), not(0));
        assertThat(odersky.age(), greaterThan(0));
    }

Como se puede ver estas aserciones son una única por cada acción, desconexas y dan la sensación que estamos verificando cosas desconexas cuando en realidad todas están conectadas entre ellas.

A continuación vemos el mismo código usando AssertJ:

    @Test
    public void test_age() throws Exception {
        assertThat(odersky.age())
                .isGreaterThan(50)
                .isBetween(55, 80)
                .isNotZero()
                .isPositive();
    }

Como se puede ver es mucho más fluido y sencillo de entender lo que se está intentando hacer, además de que podemos ver que toda la aserción está relacionada lo cual ayuda a la regla de verificar una única cosa en cada tests para evitar efectos colaterales.

AssertJ añade cosas interesantes como soporte para Joda time o Guava collections (algunas), a continuación os dejamos un par de ejemplos:

    @Test
    public void test_birthdate() throws Exception {
        assertThat(odersky.getBirthday())
                .isBefore(DateTime.now())
                .isEqualTo("1958-09-05");
    }

    @Test
    public void contribute() throws Exception {
        Project scala = new Project("Scala");
        Developer one = Developer.builder().name("First").build();
        Developer two = Developer.builder().name("Second").build();
        scala.getContributors().put(Boolean.FALSE, one);
        scala.getContributors().put(Boolean.FALSE, two);

        Developer odersky = Developer.builder().name("Martin Ordersky").build();
        scala.getContributors().put(Boolean.TRUE, odersky);

        assertThat(scala.getContributors())
                .containsValues(one, two, odersky)
                .containsKeys(Boolean.FALSE, Boolean.TRUE)
                .contains(entry(Boolean.TRUE, odersky))
                .hasSize(3);
    }

Y por último comentar que también dispone de aserciones para Swing (aunque ellos también ponen en duda la vida que le queda a Swing) o para Neo4j. Además es muy extensible por lo que permite añadir nuevas aserciones pero también generar las aserciones semánticas a través de nuestro código, incluyendo un plugin de maven para facilitar la tarea.

Como siempre podéis ver el código en nuestro repositorio de github.

clientes Json-Rest: con menos código menos errores

Siguiendo el plan iniciado con Lombok de preservar nuestros preciados dedos y como dicen que realmente los desarrolladores brillamos por nuestro interés en evitar al máximo el escribir código, vamos a centrarnos hoy en Retrofit.

Introducción

El objetivo de retrofit es crear clientes REST para Java (primcipalmente para Android). Lo he conocido gracias a este curso de coursera sobre programación de servicios en la nube para dispositivos Android.

Como sabéis REST (y en gran parte JSON) se ha convertido en la lingua franca para la comunicación entre servicios web, cosa que es especialmente interesante en el desarrollo actual y especialmente con el desarrollo de dispositvos móviles. Esto quiere decir que vamos a tener que escribir muchos servicios web en adelante y muchos clientes para estos servicios web. Aquí es donde retrofit (producto open source de Square) nos ayudará haciendo el trabajo pesado de bajo nivel un poco más sencillo.

La idea es que vamos a crear interfaces en Java que representen nuestros servicios web, y retrofit nos va a proporcionar en tiempo de ejecución su implementación, encargandose de cosas como la serialización y deserialización de estructuras en JSON (también se pueden usar otros formatos como XML o protocol buffers).

Lo primero que tenemos que hacer es definir la interfaz que sabemos que tenemos en el servicio web, añadiendo anotaciones para definir el verbo utilizado (@GET, @POST…), o para definir parámetros en la URL (@Path, @Query), parámetros del cuerpo de la petición (@Field, @RequestBody) o incluso cabeceras (@Headers). Además de todas estas opciones existen interceptores para modificar de manera genérica las peticiones.

Después deberemos construir el adaptador, donde podemos configurar muchas cosas incluso el cliente HTTP que queremos usar (esto en Android puede ser muy relevante) o también como queremos hacer la serialización, pudiendo cambiar el serializador por defecto de JSON (usando gson) a XML o similar.

Además de todo esto también dispone de programación reactiva (Rx programming) para lo cual nuestros tipo de retorno pueden ser Observables o Callbacks, cosa muy útil en el caso la programación multihilo de Android. También dispone de gestión de errores.

Ejemplo

Para este ejemplo veremos dos tipo de peticiones con a la API de Yo.

public interface YoApi {

 public static final String BASE_URL = "https://api.justyo.co";

 @FormUrlEncoded
 @POST("/yo")
 Response yo(@Field("api_token") String token, @Field("username") String user);

 @GET("/subscribers_count")
 SubscriptionsResult subscribersCount(@Query("api_token") String token);

}

Aquí hemos definido la interfaz, y como véis tenemos en el primer caso un tipo de retorno Response que se usa en este caso porque el método POST no tiene ningún retorno. En el segundo caso si que hay una respuesta muy simple que sólo contiene el campo result. A continuación podemos ver el bean que se corresponde:

import lombok.Value;

@Value
public class SubscriptionsResult {
    private int result;
}

Finalmente para poder utilizarlo podemos verlo en el código de la clase YoApiTest:

    @BeforeClass
    public static void init() {
        RestAdapter restAdapter = new RestAdapter.Builder()
                .setEndpoint(YoApi.BASE_URL)
                .setLogLevel(RestAdapter.LogLevel.FULL)
                .build();
        client = restAdapter.create(YoApi.class);
    }

    @Test
    public void testSubscribersCount() throws Exception {
        SubscriptionsResult subscriptionsResult = client.subscribersCount(API_KEY);
        Assert.assertEquals(0, subscriptionsResult.getResult());
    }

    @Test
    public void testYo() throws Exception {
        Response yoResponse = client.yo(API_KEY, YO_FRIEND);

        Assert.assertEquals(200, yoResponse.getStatus());
    }

Y con esto tenemos toda la configuración y el código necesario para hacer llamadas a nuestro servicio web. Además hemos incluido un ejemplo del servicio HostIp (ejemplo de petición) donde cambiamos alguna cosa más y hay algo más de código. Si queréis podéis echar un vistazo a la interfaz y al test.

Conclusión

Sin duda es una pieza de código interesante. El código final queda muy límpio y parece ser muy configurable. También he visto que hay alguna alternativa (Volley o incluso OkHttp del propio square aunque este se puede integrar en retrofit.) pero no he tenido tiempo de evaluarla. Parece una cosa muy interesante y sin duda si quisiera hacer servicios web a nivel Android me lo plantearía muy seriamente, no sé si sería mi elección al hacer llamadas de alta concurrencia en algún tipo de servicio de servidor, pero al utilizar RxJava seguramente también sería una buena opción.

En fin, me alegra ver que startups (aunque ya no tan starting :P) se dedican a proporcionar a la comunidad este tipo de proyectos.

Alarga la vida a tus dedos con Lombok

Introduccion

Una de las cosas que más se critica a Java es su verbosidad (el conocido como boilerplate code) que provoca muchas veces dedicar mucho tiempo a escribir código repetitivo para conseguir las mismas cosas que con otros lenguajes se consigue con mucho menos código. Aunque los IDE’s ayudan, es cierto que al final del día dedicar tiempo a este tipo de tareas no es nada productivo (ni tampoco muy bueno para nuestras sufridas manos :P).

 

Pues aquí es donde aparece el proyecto Lombok. A pesar de la extraña apariencia de la web es un proyecto bastante activo y soportado (en la empresa que trabajo actualmente lo usan). Pues bien lo que hace Lombok es a través de anotaciones evita al programador tener que escribir una y otra vez el mismo código repetitivo y sin valor añadido evitando a su vez errores de tipografía o typos. Básicamente utiliza estas anotaciones para escribir por nosotros el código antes de pasar el compilador.  Los ejemplos clásicos de código repetitivo pueden ser desde los getters/setters pasando por los constructores, los métodos toString/hashCode/equals, étc. Si quieres ver una lista completa de las opciones, puedes visitar su documentación.

Sigue leyendo

Ejecutando JavaScript en Java con Nashorn embebido

(Readme in English)
En un post previo explicaba cómo embeber Rhino en una aplicación. En aquel momento no usé Nashorn porque dependía de Java 8 que aún no estaba disponible. Ahora que hace poco que tenemos la última versión de Java disponible me he decidido a explorar la nueva implementación.

Básicamente he clonado el repositorio del post anterior y lo he refactorizado para usar el nuevo motor JavaScript:

  • he borrado todas las referencias maven porque Nashorn es una biblioteca nativa de Java 8,
  • he hecho algunos cambios estéticos en nombre de paquetes para que sea consistente y
  • he refactorizado y rebautizado la clase RhinoEcmaEvaluator como NashornEcmaEvaluator

Ejecutando scripts con Nashorn

Veamos cómo ejecutaríamos un script con Nashorn:

private static EcmaValue evaluateExpression(String expression,
        List variables) {
    ScriptEngineManager scriptManager = new ScriptEngineManager();
    ScriptEngine nashornEngine = scriptManager.getEngineByName("nashorn");
 
    try {
        putJavaVariablesIntoEngine(nashornEngine, variables);
        Object javaValue = nashornEngine.eval(expression);
        EcmaValue value = EcmaValue.create(javaValue);
        return value;
    } catch (ScriptException e) {
        handleException(e, variables);
        return null;
    }
}

Así pues ejecutar un script con Nashorn consiste en dos pasos:

  • obtener una referencia al motor Nashorn (líneas 3 y 4)
  • y ejecutar el script (línea 8)

Algunas consideraciones:

  • La clase EcmaValue no es parte de Nashorn; es una clase mía que uso a forma de wrapper de los valores que entran y salen del motor JavaScript.
  • La línea 7 (que es opcional) pasa variables del contexto Java al contexto JavaScript

Pasando objetos Java a Nashorn

La manera en la que Nashorn gestiona el binding de variables entre Java y JavaScript es más sencillo que en Rhino. No hay necesidad de crear o manipular contextos JavaScript y crear wrappers encima de objetos, sino que simplemente hay que:

  • instanciar un objeto de Binding, una clase similar a un Map (línea 4),
  • añadir los objetos Java a este mapa con la key que corresponda (línea 20),
  • y finalmente enlazar la instancia de Binding en el scope JavaScript adecuado (línea 10).
private static void putJavaVariablesIntoEngine(ScriptEngine engine,
        List variables) {
 
    Bindings bindings = new SimpleBindings();
 
    for (EcmaVariable variable : variables) {
        putJavaVariableIntoEcmaScope(bindings, variable);
    }
 
    engine.setBindings(bindings, ScriptContext.GLOBAL_SCOPE);
}
 
private static void putJavaVariableIntoEcmaScope(Bindings bindings,
        EcmaVariable variable) {
 
    String variableName = variable.getName();
    EcmaValue ecmaValue = variable.getValue();
    Object javaValue = ecmaValue.getValue();
 
    bindings.put(variableName, javaValue);
}

Gestionando errores JavaScript

La manera en la que Nashorn gestiona las excepciones JavaScript es muy similar a Rhino. El método Script#eval lanza una excepción genérica (una instancia de ScriptException) que debes inspeccionar para determinar el error ECMA que ha producido el problema.

El siguiente snippet muestra cómo hacerlo.

private static final String REFERENCE_ERROR = "ReferenceError";
 
private static void putJavaVariableIntoEcmaScope(Bindings bindings,
        EcmaVariable variable) {
 
    String variableName = variable.getName();
    EcmaValue ecmaValue = variable.getValue();
    Object javaValue = ecmaValue.getValue();
 
    bindings.put(variableName, javaValue);
}
 
private static EcmaValue handleException(ScriptException exception,
        List variables) {
    if (isReferenceError(exception)) {
        throw new IllegalArgumentException("I couldn't resolve some "
            + "variable on expression with vars "
            + Arrays.toString(variables.toArray()), exception);
    }
    throw new RuntimeException(exception);
}
 
private static boolean isReferenceError(ScriptException exception) {
    String message = exception.getMessage();
    return message.startsWith(REFERENCE_ERROR);
}

Conclusiones

  • El JDK8 está en la calle junto con una implementación JavaScript totalmente nueva: Nashorn.
  • Es el nuevo motor estándar de Java (la última release de Rhino data de juno del 2012).
  • Proporcionar una API más concisa con menos código «bolierplate» que Rhino.

Nashorn proporcionar otras muchas características que aún no he podido explorar tales como un rendimiento mucho mayor, integración de consola, sencilla integración Java y JavaScript y funcionalidades que aprovechan las nuevas características funcionales de Java 8.

Ejecutando JavaScript en Java con Mozilla Rhino embebido

(Readme in English)

El reto

En un aplicación Java en el que estoy colaborando, el usuario puede extender las funcionalidades del mismo usando scripts JavaScript en algunos puntos concretos. Algunos de los usos de estos scripts incluyen:

  • definir expresiones booleanas de una complejidad arbitraria para determinar el control de flujo en el contexto de una funcionalidad de edición de workflows o,
  • acceder y manipular a datos generados por la aplicación

Estos puntos de extensión se consideran islas aisladas que no comparten contextos de ejecución entre ellos.

Para construir esta funcionalidad necesitamos:

  1. un intérprete JavaScript para ejecutar los scripts,
  2. la posiblidad de intercambiar objetos entre los ámbitos Java y JavaScript

Ya que construir un intérprete JavaScript es matar moscas a cañonazos, el paso natural es ver qué intérpretes podemos embeber en nuestra aplicación. Algunas de las opciones son las siguientes:

De hecho la decisión fue bastante fácil:

  1. Nashorn requiere necesariamente la versión 8 del JDK que todavía no está en producción.
  2. No tenemos grandes requisitos de rendimiento, la gran ventaja de V8 con respecto a sus competidores, con lo que la mayor dificultad de integración Java / C++ no compensa su uso.
  3. Entre dos versiones de la misma implementación, prefiero utilizar la más estable, así que finalmente escogí Rhino 1.7R4

En el proyecto real, escodí el motor específico detrás de una fachada abstracta para poder cambiarlo cuando fuera necesario. En este post me concentraré en cómo implementarlo con Rhino, eliminando el ruido innecesario.

La solución

Embebiendo Rhino

Para ilustra la solución, lo haremos a través de un ejemplo que hemos publicado en GitHub como un proyecto maven. Exploremos las partes más interesantes del fichero POM; básicamente sólo es necesario especificar la dependencia de Rhino.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.softwarfair.blog</groupId>
	<artifactId>RhinoEmbeddedExample</artifactId>
	<version>1.0-SNAPSHOT</version>
	<packaging>jar</packaging>

	../..

	<dependencies>
		<dependency>
			<groupId>org.mozilla</groupId>
			<artifactId>rhino</artifactId>
			<version>1.7R4</version>
		</dependency>
		../..
	</dependencies>
</project>

Ejecutando JavaScript

La clase RhinoEcmaEvaluator ilustra cómo usar Rhino para ejectuar JavaScript. Contiene un único método (evaluateExpression) que recibe el script a ejecutar y una lista de variables para usar en este script. Veamos cómo funciona.

public class RhinoEcmaEvaluator {
	../..

	public static EcmaValue evaluate(String expression, SymbolTable table) {
		List&lt;EcmaVariable&gt; variables = table.getVariables();
		return evaluateExpression(expression, variables);
	}

	private static EcmaValue evaluateExpression(String expression,
			List&lt;EcmaVariable&gt; variables) {

		try {
			Context context = ContextFactory.getGlobal().enterContext();
			Scriptable scope = getScope(context, variables);
			Object result = context.evaluateString(scope, expression, "", 1, null);
			return EcmaValue.create(result);
		} catch(EcmaError error) {
			return handleException(error, variables);
		} finally {
			Context.exit();
		}
	}

	private static Scriptable getScope(Context context, List&lt;EcmaVariable&gt; variables) {
		Scriptable scope = context.initStandardObjects();
		putJavaVariablesIntoEcmaScope(scope, variables);
		return scope;
	}
	../..
}

Como podéis ver, para ejcutar un script básicamente hay que hacer tres pasos:

  1. obtener el contexto inicial (línea 13)
  2. obtener un scope inicializado con los objetos JavaScript estándar (línea 25)
  3. y finalmente evaluar el script en el contexto utilzando el scope adecuado (línea 15). Esta evaluación devuelve el valor de retorno del script como un objeto Java

Algunas consideraciones:

  1. EcmaVariable y EcmaValue son clases de nuestro proyecto (no parte de Rhino) que simplemente actúan como un wrapper de los objetos Java que pueden «entrar» o «salir» del contexto de ejecución de JavaScript (estaba abstracción permitiría cambiar fácilmente la implementación del motor JavaScript)
  2. En la línea 26, introducimos objetos Java dentro del scope de Rhino. Discutiremos esto más extansamente después.
  3. La ejecución JavaScript debe anidarse en un bloque try/catch para asegurarse que todos los recursos son liberados incluso si se produce algún error imprevisto

Veamos algunos tests para enteder cómo funciona.

@Test
public void testEvaluateSimpleIntExpressionNoVariable() {
	int intValue = 3;
	String expression = "" + intValue;
	SymbolTable table = new SymbolTable();

	EcmaValue actualValue = RhinoEcmaEvaluator.evaluate(expression, table);

	assertEquals(intValue, actualValue.getValue());
}

@Test
public void testEvaluateIntExpressionNoVariable() {
	String expression = "Math.pow(2,3)";
	SymbolTable table = new SymbolTable();

	EcmaValue actualValue = RhinoEcmaEvaluator.evaluate(expression, table);

	double expectedValue = Math.pow(2, 3);
	assertEquals(expectedValue, actualValue.getValue());
}

@Test
public void testEvaluateSimpleStringExpressionNoVariable() {
	String strValue = "Hello world";
	String expression = "\"" + strValue + "\"";
	SymbolTable table = new SymbolTable();

	EcmaValue actualValue = RhinoEcmaEvaluator.evaluate(expression, table);

	assertEquals(strValue, actualValue.getValue());
}

Se trata de ejemplos básicos que ejecutan scripts muy simples:

  1. Si no se pasan variables al contexto JavaScript, entoces la SymbolTable está vacía.
  2. El EcmaValue retornado «wrapea» el valor Java real que se puede obtener mediante el método getValue

Pasando objetos Java al intérprete Rhino

Rhino proporciona una integración entre Java y JavaScript sencilla y elegante. Gracias a su funcionalidad LiveConnect, acceder a Java desde la ejecución JavaScript es casi trivial. Para añadir un objeto Java, la única cosa que tienes que hacer es poner la instancia como una propiedad en el contexto adecuado.

Veámoslo en el código:

private static void putJavaVariablesIntoEcmaScope(Scriptable scope,
		List variables) {

	for (EcmaVariable variable : variables) {
		putJavaVariableIntoEcmaScope(scope, variable);
	}
}

private static void putJavaVariableIntoEcmaScope(Scriptable scope,
		EcmaVariable variable) {

	String variableName = variable.getName();
	EcmaValue ecmaValue = variable.getValue();
	Object javaValue = ecmaValue.getValue();

	Object wrappedValue = Context.javaToJS(javaValue, scope);
	ScriptableObject.putProperty(scope, variableName, wrappedValue);
}

Como vimos, llamábamos al método putJavaVariablesIntoEcmaScope para poner nuestros objetos Java en el scope JavaScript. Veamos cómo funciona:

  1. obtenemos el nombre de la variable y el objeto Java nativo (líneas de la 12 a la 14),
  2. wrapeamos el valor (línea 16)
  3. y finalmente lo añadimos al scope (línea 17)

Veámoslo en acción con algunos tests.

@Test
public void testEvaluateStringExpressionVariable() {
	String expression = "\"how many? \" + i";
	SymbolTable table = new SymbolTable();
	table.putSymbol("i", EcmaValue.create(10));

	EcmaValue actualValue = RhinoEcmaEvaluator.evaluate(expression, table);

	String expectedValue = "how many? 10";
	assertEquals(expectedValue, actualValue.getValue());
}

@Test
public void testEvaluateStringExpressionTwoVariables() {
	String expression = "Math.pow(i,j)";
	SymbolTable table = new SymbolTable();
	table.putSymbol("i", EcmaValue.create(2));
	table.putSymbol("j", EcmaValue.create(3));

	EcmaValue actualValue = RhinoEcmaEvaluator.evaluate(expression, table);

	double expectedValue = Math.pow(2, 3);
	assertEquals(expectedValue, actualValue.getValue());
}

Gestionando los errores

¿Qué sucede si el script intenta acceder a una variabla que no existe en el context JavaScript? En nuestro caso esto podría pasar si, por ejemplo, invocamos el método evaluateExpression con un script que referencia una variable llamada «i» pero no se pasa niguna variable «i» en la SymbolTable.
Como queremos gestionar este escenario en nuestra aplicación, hemos decidido que esta situación debería ser notificada mediante el lanzamiento de una IllegalArgumentException. El siguiente test ilustra esta expectativa:

variable">@Test(expected=IllegalArgumentException.class)
public void testEvaluateVariableNotInSymbolTable() {
	String expression = "i";
	SymbolTable table = new SymbolTable();

	RhinoEcmaEvaluator.evaluate(expression, table);
}

Rhino lanza una EcmaError (que se trata de una RuntimException) siempre que sucede cualquier tipo de problema durante la ejecución JavaScript. Esta clase mimetiza el error análogo en la especificación ECMA. EcmaError no tiene subclases, asíq ue no queda más remedio que explorar el contenido de la instancia para determinar qué ha provocado el problema, en particular a través del atributo errorName. Veamos pues, cómo implementar nuestro requerimiento:

private static final String REFERENCE_ERROR = "ReferenceError";

private static EcmaValue evaluateExpression(String expression,
		List&lt;EcmaVariable&gt; variables) {

	try {
		Context context = ContextFactory.getGlobal().enterContext();
		Scriptable scope = getScope(context, variables);
		Object result = context.evaluateString(scope, expression, "", 1, null);
		return EcmaValue.create(result);
	} catch(EcmaError error) {
		return handleException(error, variables);
	} finally {
		Context.exit();
	}
}

private static EcmaValue handleException(EcmaError error,
		List&lt;EcmaVariable&gt; variables) {

	if (REFERENCE_ERROR.equals(error.getName())) {
		throw new IllegalArgumentException("I couldn't resolve some "
			+ "variable on expression with vars "
			+ Arrays.toString(variables.toArray()), error);
	}
	throw error;
}

Conclusiones

  • Si necesitamos integrar un motor JavaScript en tu aplicación Java, tienes varias opciones (¡no implementes un intérprete propio!)
  • Nashorn será la implementación nativa en el JDK8 y promete nuevas funcionalidades y un gran rendimiento.
  • Mientras tanto, Rhino es una buena opción, muy fácil de integrar y con potentes funcionalides de interoperabilidad

QuickCheck en la JVM

Como muchos otros techies a mi me encanta probar cosas nuevas y ahora mismo hemos acabado uno de los MOOC relacionado con Scala. En estos cursos se suele aprender muchas cosas, y colateralmente estoy aprendiendo una cosa que creo que puede ser de interés son las herramientas basadas en QuickCheck que son de esas cosas que nos pueden servir en un momento dado.

Os pongo en antecedentes:

El problema

Como podréis imaginar por los posts publicados en soft.war.fair creemos firmemente en la calidad del software basándose en los tests. Pero cuando te enfrentas a los tests, por ejemplo usando TDD, suele aparece una duda existencial que es: ¿cuál es el juego de prueba que tengo que usar? Y en especial, ¿Hasta donde tengo que escribir códigos de prueba? Obviamente suele haber un tradeoff a la hora de elegir nuestra batería de tests, se suele empezar por los casos base y después extenderlo con casos más complejos, pero ¿realmente estamos soportando todos los posibles casos? Seguramente no, aunque en muchas ocasiones no es importante en otras si lo es. Además para poder soportar más casos tenemos que escribir más y más código de pruebas, que al final hace que nuestra base de código crezca de manera importante.

La solución

Pues bien, en el entorno del lenguaje funcional Haskell desde hace mucho tiempo (desde el 99!) existe QuickCheck que básicamente lo que sirve para generar propiedades que representan aquello que queremos verificar y la herramienta se encarga de validarlo. Estas propiedades potencialmente pueden testear todos los posibles casos, obviamente esto puede ser infinito en tiempo según el tipo de dato pero al menos buscan diferentes distribuciones generalmente aleatorias para cubrir el mayor el mayor número de posibilidades. Otra cosa importante es que se suele simplificar el código y además se suelen probar casos extremos (esto según la herramienta) de manera que no tenemos que preocuparnos mucho más allá.

El ejemplo

Para este caso iremos a una cosa muy sencilla, simplemente tenemos un rectángulo y probamos que cualquier rectángulo válido cumple unas propiedades como por ejemplo que se genera bien el área o la hipotenusa. Con este ejemplo podemos ver la potencia directamente, pero pensad que la gracia está en poder probar con muy poco código propiedades más complejas, como la posibilidad de poder trabajar con varios rectángulos.

Este será el código que queremos testear:

public class Rectangle {
    private int height;
    private int width;
	…
    public long area(){
        return height * width;
    }

    public boolean biggerThan(Rectangle that) {
        return area() > that.area();
    }

    public void add(int n) {
        validate(height +n, width +n);
        this.height = height +n;
        this.width = width +n;
    }
}

Y aquí podéis ver como serían algunos test al estilo habitual, probando casos base y después algún caso más genérico:

    @Test
    public void testAreaEmpty1() throws Exception {
        Rectangle cut = new Rectangle(0,0);
        Assert.assertEquals(0l, cut.area());
    }

...

    @Test
    public void testAreaCorrectOneXTwo() throws Exception {
        Rectangle cut = new Rectangle(1,2);
        Assert.assertEquals(2l, cut.area());
    }

Primer candidato: jUnit-quickcheck

En este caso, como su nombre indica, sólo se puede utilizar con el framework jUnit. Se basa en anotaciones y permite ejecutar un test que recibe parámetros muchas veces con dichos parámetros diferentes siguiendo un patrón. Es algo parecido a los parametrized tests, pero bastante más potente y conciso. Los tipos de datos que se pueden usar como parámetros son principalmente tipos primitivos y algunas collections, pero se puede ampliar con tus propios tipos aunque es un poco engorroso.

También puedes configurar los parámetros para que sean con unos ciertos límites que funciona muy bien con los tipos predefinidos.

Veámos un ejemplo

@Theory
    public void testAreaFineGrained(@ForAll @InRange(minInt = 0, maxInt = 300) int x,
                                    @ForAll @InRange(minInt = 0, maxInt = 300) int y) throws Exception {
        Rectangle out = new Rectangle(x,y);
        Assert.assertEquals(x*y, out.area());
    }

    @Theory
    public void testNotIsSquare(@ForAll @InRange(minInt = 0) int x,
                                    @ForAll @InRange(minInt = 0) int y) throws Exception {
        Assume.assumeThat(x, is(not(y)));
        Rectangle out = new Rectangle(x,y);
        Assert.assertFalse(out.isSquare());

    }

En el primer caso estamos generando los parámetros entre 0 y 300, el framework por defecto hará 100 ejecuciones aunque esto se puede configurar.

Mientras en el segundo caso aparece la necesidad de asegurarse que no sean iguales, y la manera de hacerlo es con las asunciones de jUnit, pero con el riesgo de que si en ninguna de las ejecuciones lanzadas se cumple la asunción el test fallará por falta candidatos.

Segundo candidato: QuickCheck

Si, el nombre es muy original :P. En este caso es agnóstico del tipo de test que estemos haciendo y como veréis es más tedioso de construir/configurar pero algo más potente.

En este caso la idea es tener (algunos ya implementados) generadores de objetos, pudiendo componer los generadores para por ejemplo construir objetos más complejos. Aquí podemos ver un generador de Rectángulos:

class RandomRectangleGenerator implements net.java.quickcheck.Generator{
    Generator x = PrimitiveGenerators.positiveIntegers();
    Generator y = PrimitiveGenerators.positiveIntegers();

    @Override public Rectangle next() {
        return new Rectangle(x.next(), y.next());
    }
}

Después para usarlo puede ser tan simple como esto:

  @Test
    public void testArea() throws Exception {
        for (Rectangle rectangle : Iterables.toIterable(new RandomRectangleGenerator())) {
            Assert.assertEquals(rectangle.getHeight() * rectangle.getWidth(), rectangle.area());
        }
    }

Se supone que deben funcionar también las anotaciones pero creo que no funciona del todo bien.

Esta herramienta tiene cosas interesantes, como la generación aleatoria basada en un semilla fija. Y vosotros diréis, lo cualo??? Pues me refiero a que aunque se generen enteros aleatorios para poder cubrir de manera significativa el espectro de los enteros, los enteros que se van a generar serán siempre los mismos, por lo que tendremos cierta predictibilidad útil a veces para, por ejemplo, ejecutar tests en servidores de integración continua. A esto se le llama generación determinista. También tiene generación según estratégias: sólo valores únicos, basado en transformaciones, excluyendo valores, etc.

Esta herramienta es sin duda útil para poder tener nuestros generadores de objetos de manera rápida y sencilla.

Tercer y último (por ahora) candidato: ScalaCheck

En este caso, como ya he comentado antes, se trata de una librería para ser usada en Scala, claro que entonces alguno pensará que no se pueden testear clases java, pero no es así pues todo Scala se transforma a una clase Java y por tanto la interoperabilidad es casi transparente (a excepción de algunos tipos de datos).

En el caso de ScalaCheck también se basa en el concepto de generadores pero algo más avanzado y libre a la vez. Al tratarse de un lenguaje funcional está muy orientado a tener propiedades a verificar y permitir tener un modelo muy cercano al lenguaje natural. Una propiedad puede ser un tipo de verificación con una serie de datos aleatorios, con la gracia que la librería te permite “unir” propiedades por ejemplo diciendo que se tienen que validar todas o al menos una de las propiedades, etc.

Vamos a ver un ejemplo sin utilizar aún un generador nuestro:

 property("area") = forAll { (x:Int, y:Int) =>
    (x >= 0 && y >= 0) ==> {
      val rectangle = new Rectangle(math.abs(x),math.abs(y))
      rectangle.area == x*y
      }
  }

Aquí sucede como en el caso de junit en que estamos filtrando sólo aquellos que nos interesan.

Ahora vamos a ver como se construye y se usa un generador personalizado:

object RectangleGenerator {
  // generator for the Rectangle case class
  val arbRectangleGen:Gen[Rectangle] = for {
    height     width
    rect.area == rect.getHeight * rect.getWidth
  }

  property("hypotenouse of square + int == sum hypotenouses ") = forAll {
    (rect:Rectangle, side:Int) => {
      val square = new Rectangle(side, side).hypotenuse()
      val previousHypotenouse =  rect.hypotenuse()
      rect.add(side)
      square + previousHypotenouse  == rect.hypotenuse()
    }
  }

Como podéis ver el código es realmente sencillo de entender.

Conclusiones & winner

Sobre cual es el mejor, bueno está claro que cada uno tiene sus cosas buenas y sus cosas malas así que daré mis recomendaciones para cada caso:

  • junit-quickcheck: Si lo que se quiere hacer cuadra muy bien con algunos de los parámetros indicados puede ser muy útil, pero además el código puede quedar muy límpio.

  • java quickcheck: Si se necesita un generador determinista o algún tipo de generador más complejo, esta es vuestra solución.

  • ScalaCheck: Si se tienen que combinar varias propiedades o se quiere testear todo un sistema basándose en el paradigma QuickCheck seguramente es la solución más límpia y potente pero con el aliciente de que se debe escribir en Scala.

Mi conclusión es que estas herramientas están bien conocerlas, que pueden ser muy útiles aunque hay que tener cuidado pues pueden añadir una complejidad extra y pueden ocultar que test se están realizando, aunque también es cierto es que usadas sabiamente ahorran mucho código y errores. Eso si, para funcionalidades que tienen que ser probadas exhaustivamente, como interfaces públicas, creo que pueden ser la manera ideal.

Como siempre podéis echar un ojo a nuestros ejemplos en github.

Jar que contiene jars

(Readme in English)
Soy programador y me gusta el lenguaje Java. Sé que hoy día esto puede parecer un poco anacrónico, pero creo que es un lenguaje potente que tiene un gran ecosistema a su alrededor y al que le queda todavía mucha vida por delante (aunque su muerte sea anunciada periódicamente). Dicho lo cual, me siento con derecho a criticar aquello que no me gusta de la plataforma.

Una de las cosas que me resulta más molesta a la hora de distribuir aplicaciones basadas en Java es toda la parafernalia de configuración alrededor de las dependencias y de la configuración del CLASSPATH. Aunque son cosas diferentes está íntimamente relacionados.

Cuando queremos distribuir la aplicación, el procedimiento habitual suele consistir en crear un fichero jar ejecutable, es decir añadiendo al MANIFEST.MF la ruta hacia la clase que contiene el consabido método main que lanzará toda la aplicación. Así pues, la unidad de empaquetado (y distribución) de aplicaciones de escritorio suele ser este fichero jar. El principal problema es que, a diferencia de otras unidades de empaquetado del lado servidor (como wars o ears), no admite la inclusión de dependencias (otros jars) dentro del mismo. Ello implica que cuando tenemos que distribuir nuestra querida aplicación de escritorio que usa otras bibliotecas, hemos de distribuir nuestro código y esas dependencias por separado y después instruir al usuario final para que incluya todos los artefactos en el CLASSPATH. Tedioso para desarrolladores y usuarios. En parte este proceso se suele simplificar con la inclusión de scripts de arranque que modifican el entorno de manera adecuada o especifican los parámetros necesarios a la JVM, aunque, a mi parecer, no deja de ser un pequeño parche.

Solución 1: Ensamblar un nuevo jar que contiene todo el código

Una primera solución a este problema es un procedimiento de fuerza bruta (y poco elegante) que consiste en generar un nuevo fichero jar a partir de todos los jars necesarios para ejecutar la aplicación. Es decir, desempaquetamos nuestro código y el de todas nuestras dependencias y lo re-empaquetamos todo junto en un nuevo y único jar. Obviamente esta solución funciona, pero supongo que se intuye rápidamente que no es la solución que contribuya a tener el ciclo de desarrollo más eficiente y mantenible (overhead de manipulación, colisión de paquetes, pérdida de trazabilidad del origen del código, etc.). De todos modos, para un proyecto pequeño con pocas dependencias puede ser una solución suficiente. Si queremos seguir esta vía, no estamos solos, ya que existe un plugin maven que se encarga de hacer exactamente esto: generar un único jar que es la agregación de nuestro código con todas las dependencias.

He creado un proyecto maven de ejemplo que sigue este procedimiento que podéis encontrar en nuestro GitHub . Veamos las partes destacables del POM:

  • el plugin que nos interesa lo podemos ver de la línea 5 a la 25,
  • en las líneas de la 16 a la 20 especificamos cuál es la clase que contiene el main, información que será incorporada en el manifest del jar para que éste sea ejecutable,
  • y finalmente incluímos algunas dependencias que se ensamblarán en el jar destino.
../..
<build>
	<plugins>
		../..
		<plugin>
			<artifactId>maven-assembly-plugin</artifactId>
			<executions>
				<execution>
					<phase>package</phase>
					<goals>
						<goal>attached</goal>
					</goals>
				</execution>
			</executions>
			<configuration>
				<archive>
					<manifest>
						<mainClass>com.softwarfair.blog.HelloWorldAssembly</mainClass>
					</manifest>
				</archive>
				<descriptorRefs>
					<descriptorRef>jar-with-dependencies</descriptorRef>
				</descriptorRefs>
			</configuration>
		</plugin>
	</plugins>
</build>

<dependencies>
	<dependency>
		<groupId>log4j</groupId>
		<artifactId>log4j</artifactId>
		<version>1.2.17</version>
	</dependency>
</dependencies>
../..

Tras ejecutar mvn install en la carpeta target tendremos el artefacto HelloWorldAssembly-1.0.0-SNAPSHOT-jar-with-dependencies.jar que contiene las clases de nuestro código y de nuestra dependencia. Como siempre, para ejectuar el jar lo hacemos con el comando java -jar <jar-file> y comprobamos que efectivamente funciona sin necesidad de referenciar ningún jar adicional.

$ java -jar HelloWorldAssembly-1.0.0-SNAPSHOT-jar-with-dependencies.jar

0 [main] INFO com.softwarfair.blog.HelloWorldAssembly - Hello world!

Solución 2: one-JAR

Otra solución mucho más elegante sería poder incluir en el propio jar un directorio lib al estilo de los wars donde se pudieran almacenar las dependencias y que el entorno de ejecución (el classloader) entendiera esta situación y cargara de allá los recursos. Esto combinaría en una misma solución una correcta modularización de la aplicación y una mayor facilidad en la distribución y ejecución de la misma.

Esta estrategia es precisamente la que implementa el proyecto open source one-JAR. Parafraseando su página web: one-JAR permite empaquetar una aplicación Java junto con sus dependencias en un único jar ejecutable. La magia consiste en que utiliza un classloader propio que es capaz de cargar clases y recursos directamente desde el interior de un fichero jar en vez de hacerlo desde el sistema de ficheros, y todo ello sin necesidad de escribir una sola línea de código adicional para que funcione.

Para ensamblar este jar único, el proyecto provee de una task Ant y de un plugin maven. En nuestro github, podéis encontrar un repositorio que utiliza el plugin de maven para el mismo código del ejemplo anterior. Las partes importantes del POM son las siguientes:

  • las líneas 5 a la 21 configuran el plugin,
  • la línea 12 indica cuál es la clase que contiene el main de nuestra aplicación
  • la línea 15 especifica cuál es qualifier del artefacto que generará el plugin.
../..
<build>
	<plugins>
		../..
		<plugin>
			<groupId>org.dstovall</groupId>
			<artifactId>onejar-maven-plugin</artifactId>
			<version>1.4.4</version>
			<executions>
				<execution>
					<configuration>
						<mainClass>com.softwarfair.blog.HelloWorldOneJar</mainClass>
						<onejarVersion>0.97</onejarVersion>
						<attachToBuild>true</attachToBuild>
						<classifier>onejar</classifier>
					</configuration>
					<goals>
						<goal>one-jar</goal>
					</goals>
				</execution>
			</executions>
		</plugin>
	</plugins>
</build>
../..

Tras ejecutar mvn install en la carpeta target se habrá generado el artefacto HelloWorldOneJar-1.0-SNAPSHOT.one-jar.jar que contiene nuestra aplicación junto con sus dependencias. Antes de explorar sus interioridades, comprobamos que la ejecución produce el resultado esperado:

$ java -jar HelloWorldOneJar-1.0-SNAPSHOT.one-jar.jar

0 [main] INFO com.softwarfair.blog.HelloWorldAssembly - Hello world!

Como somos seres curiosos, queremos entender cómo funciona one-JAR e investigamos el contenido de nuestro flamante nuevo jar:

  • en la línea 4 identificamos un jar que contiene el código de nuestra aplicación,
  • en la línea 5, dentro de la carpeta lib, vemos que se ha incorporado nuestra dependencia (si tuviéramos más, obviamente aparecerían también en este directorio),
  • en la línea 42 vemos un documento de texto que contiene la licencia open source de la herramienta,
  • finalmente el resto de clases es el código que ejecuta la magia sustituyendo el classloader clásico por el de one-JAR.
$ jar -tf HelloWorldOneJar-1.0-SNAPSHOT.one-jar.jar

META-INF/MANIFEST.MF
main/HelloWorldOneJar-1.0-SNAPSHOT.jar
lib/log4j-1.2.17.jar
com/
com/simontuffs/
com/simontuffs/onejar/
.version
OneJar.class
com/simontuffs/onejar/Boot$1.class
com/simontuffs/onejar/Boot$2.class
com/simontuffs/onejar/Boot$3.class
com/simontuffs/onejar/Boot.class
com/simontuffs/onejar/Handler$1.class
com/simontuffs/onejar/Handler.class
com/simontuffs/onejar/IProperties.class
com/simontuffs/onejar/JarClassLoader$1.class
com/simontuffs/onejar/JarClassLoader$2.class
com/simontuffs/onejar/JarClassLoader$ByteCode.class
com/simontuffs/onejar/JarClassLoader$FileURLFactory$1.class
com/simontuffs/onejar/JarClassLoader$FileURLFactory.class
com/simontuffs/onejar/JarClassLoader$IURLFactory.class
com/simontuffs/onejar/JarClassLoader$OneJarURLFactory.class
com/simontuffs/onejar/JarClassLoader.class
com/simontuffs/onejar/OneJarFile$1.class
com/simontuffs/onejar/OneJarFile$2.class
com/simontuffs/onejar/OneJarFile.class
com/simontuffs/onejar/OneJarURLConnection.class
src/
src/com/
src/com/simontuffs/
src/com/simontuffs/onejar/
src/OneJar.java
src/com/simontuffs/onejar/Boot.java
src/com/simontuffs/onejar/Handler.java
src/com/simontuffs/onejar/IProperties.java
src/com/simontuffs/onejar/JarClassLoader.java
src/com/simontuffs/onejar/OneJarFile.java
src/com/simontuffs/onejar/OneJarURLConnection.java
doc/
doc/one-jar-license.txt

Queda una última duda pendiente. La máquina virtual sabe cuál es la clase principal a ejecutar mediante una entrada en el MANIFEST.MF, así que sospechamos que éste debe contener la ruta a la clase principal del código de one-JAR que se encarga del bootstrap y, efecivamente es así si inspeccionamos el contenido del MANIFEST.MF generado:

Manifest-Version: 1.0

ImplementationVersion: 1.0-SNAPSHOT
Main-Class: com.simontuffs.onejar.Boot
One-Jar-Main-Class: com.softwarfair.blog.HelloWorldOneJar

Conclusión

  • El artefacto de distribución estándar de aplicaciones de escritorio en el mundo Java es el jar ejectuable.
  • El classloader clásico no permite la inclusión de dependencias (otros jars) dentro de un jar, lo que dificulta la distribución y la ejecución local.
  • Una alternativa es crear un jar único que es el resultado de desempaquetar nuestro código y el de nuestras dependencias y re-empaquetarlo todo de manera conjunta. Este procedimiento puede simplificarse con el uso del plugin de maven assembly.
  • Otra alternativa es utilizar el proyecto one-JAR que permite el empaquetado de jars dentro de jars sustituyendo el classloader clásico por otro más avanzado que sabe inspeccionar el contenido de los ficheros jar.

Mock pequeño => Mockito

Una cosa que nos gusta a todos los técnicos es poder comparar las diferentes alternativas para poder o bien elegir una de ellas o bien saber cual usar en cada caso. Pues bien, el otro día vimos el post explicando el inicio a la programación basada en mocks explicado con el ejemplo de EasyMock. En este post, como ya habréis podido adivinar, hablaré sobre una alternativa llamada Mockito.

Antes de nada vayamos a la historia para poder ser justos en la comparación, mockito nace a partir de EasyMock, de hecho nace creado por Szczepan Faber en 2008 dentro de The Guardian pues los actuales frameworks de mocks le parecían muy engorrosos. Inicialmente se basa en la sintaxis y funcionalidad de EasyMock, pero posteriormente pasa casi a reescribirlo por completo.

Por su parte EasyMock data de 2001 que algo ha llovido, y ya va por su versión 3.2, se trata de un framework con mucha más historia.

Ahora pasamos a ver la comparación directa con el caso de estudio del post anterior y después comentaremos las diferencias

Simulación de comportamiento

public class ReportMakerTest {

   @Test

   public void calculateReport_someUsers() {

       UserDao mock = Mockito.mock(UserDao.class);

       int expectedActiveUsers = 1;

       int expectedInactiveUsers = 3;

       Mockito.when(mock.getUsers()).thenReturn(createUsersToReturnByDao(expectedActiveUsers, expectedInactiveUsers));

       ReportMaker reportMaker = new ReportMaker(mock);

       Report actualReport = reportMaker.calculateReportFromPersistence();

       assertEquals(actualReport.getActiveUsers(), expectedActiveUsers);

       assertEquals(actualReport.getInactiveUsers(), expectedInactiveUsers);

   }

}

En mockito la simulación del comportamiento, que se conoce como stubbing, se hace usando el estilo when(…).thenReturn(…) muy similar al expect(…).andReturn(…) de EasyMock.

Comprobar la colaboración

    @Test
    public void calculateReport_checkCollaborationOnDao() {
        UserDao mock = Mockito.mock(UserDao.class);
        Mockito.when(mock.getUsers()).thenReturn(Collections.<User>emptySet());

        ReportMaker reportMaker = new ReportMaker(mock);
        reportMaker.calculateReportFromPersistence();

        Mockito.verify(mock).getUsers();
    }

Como véis es de manera muy parecida a EasyMock, usando el método verify.

Diferencias

Simulación de comportamiento

En el caso de mockito la inicialización es casi nula, sólo se debe llamar al método mock:

UserDao mock = Mockito.mock(UserDao.class);

y a partir de aquí se puede llamar a los métodos realizando, directamente en la misma llamada la sustitución del comportamiento deseado.

Mockito.when(mock.getUsers()).thenReturn(createUsersToReturnByDao(expectedActiveUsers, expectedInactiveUsers));

En el caso de EasyMock antes de poder empezar la ejecución debemos tener ya listos todos nuestros mocks y haberles dado al “replay”:

EasyMock.expect(userDao.getUsers()).andReturn(usersToReturnByDao);
EasyMock.replay(userDao);

Verificación

Siguiendo lo explicado anteriormente, la verificación también está en un punto diferente, en este caso se configura lo que queremos verificar justo en el momento de verificar:

Mockito.verify(mock).getUsers();

Al igual que con EasyMock se pueden configurar situaciones de ejecuciones N veces o situaciones de error, pero la gran diferencia es el lugar donde se verifican las cosas.

Conclusiones

Como podéis ver en el código fuente de github el código es algo más liviano en mockito. Otra cosa muy importante es que la configuración está en puntos distintos, en el caso de EasyMock es más una especificación a priori de lo que debe pasar, mientras que en el caso de mockito vamos diciendo a cada paso que queremos que pase y que queremos verificar.

Mockito tiene algunas cosas muy interesantes, como el poder espiar o suplantar objetos reales (no mocks). Esto puede oler mal, pero puede tener mucho sentido cuando se trabaja con legacy code. Otra cosa interesante es que no obliga a la verificación siempre en orden, sino que es relajado en este sentido. Y también cosas como verificar con timeout. O incluso tiene cosas como la inyección de dependencias!

En cierto sentido mockito es menos estricto y más flexible y parece ser menos verboso, lo cual se agradece. Obviamente también se puede argumentar que EasyMock deja mucho más claro lo que se está haciendo y tal vez es más formal.

Ah, se me olvidaba, el logo de mockito es muy molón! 😛

Introducción a los mocks

Aquéllos que escribimos pruebas unitarias para todas nuestras clases, más pronto que tarde nos encontramos ante el problema de cómo gestionar los colaboradores de nuestra clase bajo test. Una prueba unitaria, por definición, debe concentrarse sólo en la funcionalidad de la clase que quiere comprobar. Por otro lado, para que un sistema haga algo interesante, necesariamente diversas clases han de colaborar entre sí y por tanto siempre, por muy bien diseñado que esté dicho sistema, habrá un pequeño acoplamiento.

Así pues tenemos dos fuerzas enfrentadas que compiten a la hora de hacer software de calidad: pruebas unitarias y colaboración entre componentes. Este dilema se afronta siguiendo una estrategia en dos pasos:

  1. asumimos que el colaborador funciona (obviamente, porque al desarrollarlo también hemos proporcionado sus tests, ¿verdad?),

  2. sustituimos el colaborador por un “imitador” que nos abstrae de su complejidad y simula fácilmente los comportamientos que necesitemos para el test en cuestión.

Afortunadamente este un problema que está resuelto y que generalmente consiste en inyectar estos “imitadores” durante la ejecución del test. Dichos imitadores se llaman técnicamente mocks (o doubles, o fakes, o stubs según la bibliografía y con pequeños matices) y generalmente se generan mediante bibliotecas (como, por ejemplo, EasyMock o Mockito en el mundo Java) que facilitan mucho su construcción y aportan funcionalidades interesantes.

Simular los colaboradores y sus comportamientos

Por ejemplo, imaginemos que tenemos un componente de lógica que escribe un informe agregando información de usuarios que en el sistema real se recuperan desde un sistema de persistencia. Un esqueleto del código sería el siguiente (os podéis descargar todo el proyecto maven desde nuestro GitHub).

public class ReportMaker {
    private final UserDao userDao;

    public ReportMaker(UserDao userDao) {
        this.userDao = userDao;
    }

    public Report calculateReportFromPersistence() {
        Set usersAtPersistence = getUsers();
        return calculateReport(usersAtPersistence);
    }

    private Report calculateReport(Set users) {
        int activeUsersCount = 0;
        int inactiveUsersCount = 0;

        for (User currentUser : users) {
            if (currentUser.isActive()) {
                activeUsersCount++;
            } else {
                inactiveUsersCount++;
            }
        }

        return new Report(activeUsersCount, inactiveUsersCount);
    }

    private Set getUsers() {
        return userDao.getUsers();
    }
}

public interface UserDao {
    Set getUsers();
}

Como ahora estamos interesados en testear la clase ReportMaker, no queremos ni comprobar que la clase UserDao funciona bien (¡de hecho es sólo una interfaz sin implementación!), ni tener que setear una base de datos en el contexto de la ejecución del test. Así pues, tenemos que nuestra clase bajo prueba es ReportMaker y está acoplada con el colaborador UserDao. Veamos cómo “mockeamos” este colaborador.

En mi ejemplo voy a utilizar TestNG como framework de testing unitario y EasyMock como biblioteca de mocks, pero podría haber utilizado otras combinaciones (como por ejemplo JUnit y Mockito), ya que las técnicas, e incluso los nombres de métodos, son muy similares.

Sin más dilación, aquí va el código de test.

public class ReportMakerTest {
    @Test
    public void calculateReport_someUsers() {
        int expectedActiveUsers = 1;
        int expectedInactiveUsers = 3;
        UserDao userDao = createMockUserDao(expectedActiveUsers,
                expectedInactiveUsers);

        ReportMaker reportMaker = new ReportMaker(userDao);
        Report actualReport = reportMaker.calculateReportFromPersistence();

        assertEquals(actualReport.getActiveUsers(), expectedActiveUsers);
        assertEquals(actualReport.getInactiveUsers(), expectedInactiveUsers);
    }

    private UserDao createMockUserDao(int activeUsersCount,
            int inactiveUsersCount) {
        Set usersToReturnByDao = createUsersToReturnByDao(
                activeUsersCount, inactiveUsersCount);

        UserDao userDao = EasyMock.createMock(UserDao.class);
        EasyMock.expect(userDao.getUsers()).andReturn(usersToReturnByDao);
        EasyMock.replay(userDao);
        return userDao;
    }
}

Como vemos, el método createMockUserDao() nos crea un mock a partir de la interfaz UserDao mediante la llamada createMock(). En este momento el mock no implementa ningún comportamiento, esto quiere decir que si llamáramos a alguno de sus métodos se lanzaría una excepción. Así pues, pasamos a definir el comportamiento que queremos tener cuando alguien invoque su método getUsers(); en EasyMock a este hecho se le denomina programar expectativas y se hace mediante la llamada expect(). En nuestro ejemplo queremos devolver una Set<User> concreto cuando alguien llame a dicho método: expect(UserDao.getUsers()).andReturn(usersToReturnByDao).

Finalmente la llamada replay() marca el fin de la fase de programación del mock: a partir de este momento la llamadas que reciba deberán comportarse tal y como se ha programado.

Una vez preparado el mock, el resto del test lo programamos sin ninguna consideración adicional más.

Recapitulando, hemos conseguido testear unitariamente una clase sin que sus colaboradores nos molesten y con la posibilidad de programar el comportamiento esperado para dichos colaboradores.

En este ejemplo hemos programado un comportamiento muy sencillo: “cuando te llamen a este método, devuelve este valor”. Esto es sólo un aperitivo de los comportamientos que se pueden programar. Para que os hagáis una idea, se pueden programar comportamientos como:

  • si se invoca este método con el valor -1 entonces lanza una excepción”,

  • “a este método lo podéis llamar todas las veces que queráis y en todos los casos devuelve 5”,

  • “este método sólo puede llamar entre 2 y 3 veces”,

  • “todas las expectativas programadas deben suceder el orden que ha sido determinado” (o lo contrario)

  • “siempre que el parámetro pertenezca a la clase X, independientemente de su valor, devuelve null”

  • etc

Para más detalles echadle un ojo al tutorial de de EasyMock o dejad vuestras preguntas como comentarios en este post.

Comprobar que se ha producido una colaboración

Otro aspecto que podemos querer testear es que la clase bajo test ha llamado efectivamente a un método de alguno de sus colaboradores. Es un caso diferente al anterior.

En el caso anterior, si nuestra clase ReportMaker hubiera obtenido los usuarios desde algún otro sitio diferente al UserDao, el test habría seguido en verde. Lo que hemos programado es el hecho que al llamar al método getUsers() se devuelva un Set<User> concreto, pero en ningún sitio estamos efectivamente forzando que se produzca dicha invocación.

Para hacer fallar el test en caso de no que se cumplan todas las expectativas del mock tenemos el método verify(). Veamos cómo se utiliza con otro ejemplo.

@Test
    public void calculateReport_checkCollaborationOnDao() {
        UserDao userDao = createMockUserDao(0, 0);

        ReportMaker reportMaker = new ReportMaker(userDao);
        reportMaker.calculateReportFromPersistence();

        EasyMock.verify(userDao);
    }

Conclusiones

  • Es importante tener testeado de manera unitaria todos los componentes porque para que un sistema complejo funcione bien, valga la redundancia, sus partes han de funcionar bien.

  • Para que un sistema haga algo interesante debe haber colaboración entre componentes lo que introduce acoplamiento. Así pues hemos de intentar reducir el acoplamiento al máximo pero cierto nivel es inevitable.

  • Los mocks nos permiten abstraer los colaboradores para centrarnos en la clase bajo test.

  • No hay necesidad de crear los mocks desde cero pues existen diferentes frameworks que se encargan del trabajo duro y nos permiten centrarnos en nuestro problema.

Barrera de entrada de proyectos demolida gracias al software

Como @ ivan_parraga señaló en uno de nuestras excursiones al blat i cafe, el esfuerzo y el dinero que es necesario para montar un proyecto tecnológico se ha reducido a 0 o casi. Con este post voy a analizar esta situación y enseñar algunas herramientas que sirven para luchar (war) contra las barreras del coste del desarrollo y pueden ser muy interesante para el día a día.

HISTORIA

La idea de que hacer software es gratis o casi gratis es algo que la mayoría de la gente ha pensado desde principios de los 90, pero en realidad nunca ha sido así.  El hecho es que ahora se está empezando a acercar a ese punto gracias en parte a los modelos de desarrollo   y producción FreeTier. Anteriormente había algunas herramientas importantes o facilitadora que no se podía conseguir sin una inversión generalmente grande y difícil de justificar por las empresas medianas o pequeñas que iba desde un buen equipo de desarrollo, entornos de pre-producción, herramientas de análisis, servidores de integración continua, etc. Ahora podemos encontrar estas herramientas en la red para casi todo. La mayoría de ellas viene con la filosofía de facilitar la vida a las iniciativas pequeñas (poco volúmen) y conseguir ingresos importantes de los peces grandes compensando al resto. Así que, finalmente, si tienes una idea lo único que necesitas es unas manos y un cerebro entrenados, el resto puede ser gratis al principio y si tienes éxito entonces ya no te importará invertir más.

En el pasado, cuando se iniciaba una nueva startup, era bastante normal dejar a un lado lo que el manager entendía como daños colaterales: QA, integración continua, control de la producción, etc, pero la cosa es que esas técnicas se han vuelto cada vez más importantes, no sólo por su ayuda intrínseca al software desarrollo, sino también para que los técnicos seamos más felices (y estos último somos bastante escasos 🙂 ).

Hay grandes grandes players del mundo de internet (por no decir todos) cuya estrategia de captación de nuevos usuarios es el FreeTier desde los conocidos IaaS (Amazon, Microsoft, etc) pasando por los nuevos actores de PaaS (heroku, GAE, OpenShift) para acabar con el SaaS con soluciones de usuario final (con cientos de ejemplos: Salesforce.com, gmail, Trello, wordpress.com, HootSuite, etc). Todos ellos tratan de obtener el usuario gratuito entusiasmado con buenas funcionalidades para captarlo como usuario premium. Pero el punto aquí es que empiezan a aparecer más y más herramientas para ayudar a los desarrolladores a conseguir convertir sus ideas en realidad.

Factores facilitadores

Hay algunos factores que han hecho que esta situación llegue a ser una realidad, algunos de ellos son:

  • La computación en nube. La cual ha pasado de ser una tendencia a ser una necesidad para casi cada empresa de tecnología (todavía hay detractores y algunos casos donde no puede encajar por restricciones de seguridad o de la ley). Esto ha ayudado a muchos negocios a conseguir la infraestructura necesaria, a ser eficientes y a poder crecer virtualmente sin límite. Colateralmente se han construidos muchos nuevos negocios encima de las nubes () actuales, es decir, negocios como Dropbox que se basan en otra nube pero añadiendo valor.

  • Aplicaciones Tienda online como iTunes, Android Play Store o Chrome WebStore. Esto ha ayudado a la gente a implementar fácilmente nuevas ideas sin necesidad de canales de distribución ni estar realmente conectados con el cliente, en gran parte gracias a las nuevas soluciones móviles. Pero también llegan al mundo de los plugins para el navegador, mundo de escritorio o incluso otros mundos como los IDE o a tiendas de aplicaciones para herramientas como puede ser el mercado de aplicaciones de Jira.

  • Software en todas partes. Esto tiene que ver con el móvil, obviamente, pero no sólo. Software es hoy en día en todos los espacios de nuestra vida (probablemente esto se aplica sólo en el primer mundo 😥 ). Podemos ver el software en todas las empresas que va desde el datáfono hasta los grandes ERPs o de los proveedores de la nube antes citados. Esto también se aplica a nuestra vida personal: temas sociales (Facebook, Twitter, LinkedIn, etc), el deporte, la cocina, quedadas, pareja, etc Así que el hecho es que la sociedad está pidiendo más programas en la tarea cotidiana. Este factor es el que tira la primera ficha del dominó, cuanto más software se necesita provoca que se necesite realizar más desarrollos y como cualquier sector el volumen trae consigo mejoras para hacer las cosas con mayor calidad (por suerte), más baratas y más rápidas.

  • Open source creciendo exponencialmente y cada vez más importante para el desarrollador. Éste también es un factor del tipo primera ficha del dominó: cuanto más gente hay haciendo Open source, mejores herramientas aparecen y más interés sobre estas herramientas y sobre la gente que realiza este desarrollo. Esto finalmente revierte en mejorar el Open source y colateralmente en mejorar el sector por completo.

Todo no es libre

Como he dicho antes lo único que necesitas son tus manos y tu cerebro. Estas son cosas valiosas que obviamente no son gratis y seguramente tampoco tienen un modelo Free Tier . Estas son las cosas que se convertirán en más y más deseadas porque, como vas a ver, el resto de “cosas” pueden ser mucho más sencillas y baratas.

También es importante comparar la opción de Free Tier con el resto de opciones pues las gratuitas suelen tener limitaciones y puede que pagar un poco más consigues un gran salto (como Jira por 10$). Por tanto debemos ser muy conscientes de las opciones que hay en el mercado antes de escoger una de estas opciones.

Por último hay que tener claro que algunas de estas herramientas están pensadas sólo para proyectos open source y por tanto para usarlas necesitas que tu proyecto sea también de open source

Enseñame las herramientas!

Bien, repasemos qué herramientas usamos durante el desarrollo

  • Un ordenador para desarrollar, implementar, documentar, etc

  • Herramientas de comunicación

  • Bug trackers o herramientas de gestión de proyectos

  • Un SCM

  • Herramientas de automatización (como selenium)

  • Servidores de integración continua

  • Entornos de pre-producción o stagging, etc

  • Entorno de producción

    • de base de datos

    • servidor de aplicaciones

    • Monitorización

    • Backups

  • Un canal de distribución

Como se puede ver durante el desarrollo que utilizamos unas cuantas herramientas y para cada una de ellas puedes llegar a encontrar alguna que te funcione. Tu trabajo será ponerlas todas juntas. Vamos a repasar algunos ejemplos:

Documentación

Seguramente todos conocéis google docs y sus imitadores, el cual yo he estado usando durante años y no puedo estar más contento. Pero la documentación no se acaba aquí y gdocs tiene algunas limitaciones que se pueden superar con:

  • Draw.io. Un herramienta para realizar modelos

  • Google sites para tener un site público, indexable y de más. Esto se puede conseguir con muchas otras herramientas como wordpress, github o confluence.

  • Wikis. Con herramientas como wikispaces o wiki.zoho.com. Aquí podemos ver algunas más.

  • Herramientas para mockups. Son herramientas para trabajar en las ideas antes de implementarlas y hay algunas de estas online.

Herramientas de comunicación

Hay muchísimas soluciones desde hace ya mucho tiempo, desde las herramientas de google hasta otras tipo Skype & cia. Es importante es ver que se adecuan a tus necesidades de agilidad, seguridad o ubicuidad.

Hostings de sistemas de control de versiones (SCM)

Hay un gran movimiento alrededor de GitHub.com pero no es ni el primero ni el úlitmo, hay bastantes más, por ejemplo:

  • Google Code (iniciado en 2005). Que permite tener repositorios open source en GIT, SVN o Mercurial pero también tiene wiki’s, gestión de tareas y hasta herramientas de revisión de código integradas! (existía la posibilidad de permitir descargar cualquier artefacto, pero esta funcionalidad ha sido deprecada)

  • Launchpad. Uno de los más usados por la comunidad open source. Ofrece desde gestión de código a code reviews pasando por la creación de paquetes ubuntu y también la gestión de traducciones que es muy demandada en el mundo open source.

  • SourceForge. Otra herramienta muy popular para proyectos open source, que últimamente está haciendo cosas no muy bien (mirad los comentarios de Sergio).

La mayoría requieren acceso libre a tu código cosa que puede suponer un problema en algún caso aunque dependerá del tipo de proyectos, en algunos casos puedes pagar por acceso privado (como GitHub). Aunque hay alternativas que no tienen ese requerimiento, como gitlab.com.

A parte hay que tener en cuenta que los repositorios públicos pasan a formar parte de los currículums de los desarrolladores en conjunción con herramientas como MasterBranch, aunque este tipo de cosas mejor las dejamos para otro post.

También hay otras herramientas para revisión de código online, pero la mayoría de ellas requieren instalación en servidores propios (Code Collaborator es seguramente una de las mejores junto con Crucible de la suite de Atlassian).

Herramienta de gestión de tareas

La mayoría de hostings de SCM ya tienen un sistema de gestión de tareas integrado. Pero en este apartado vamos a ver algún candidato más, pues la necesidad de la gestión de tareas ni mucho menos es único al desarrollo de software.

  • Trello.com. Probablemente la más sencilla y bonita de usar, focalizada en pizarras kanban con gran soporte multidispositivo.

  • Jira. Sin duda una herramienta de lo mejor y con mucha experiencia. Gratuita para proyectos open source y sólo 10$ (que van a caridad) para el uso de 1 a 10 usuarios.

  • Redmine. Otra herramienta open source con una gran interfaz y buenos plugins.

IDE (Entorno de desarrollo integrado)

Obviamente la mayoría conocéis las herramientas tipo Eclipse, Netbeans o IntelliJ IDEA que o son totalmente libres o tienen versión para la comunidad (como el caso de IDEA). Pero hay otras herramientas que intentan cerrar el ciclo de desarrollo permitiendo desarrollar sin una máquina de desarrollo, tan sólo con una conexión a internet y un browser.

Yo empecé conociendo Cloud9, que es un entorno completo de desarrollo permitiendo incluso deployar directamente en servicios como Heroku. En otro nivel está el proyecto Orion, que es un proyecto Eclipse para permitir desplegar tus aplicaciones pesadas, basadas en Eclipse RCP, directamente en la web y seguramente el primer ejemplo será un IDE, como sucede en el caso de Eclipse que es un ejemplo de aplicación RCP.

Últimamente he conocido un nuevo actor en este mundillo: Code Envy. Parece similar a Cloud9 pero con una filosofía que parece dar más poder al desarrollador, permitiendo llegar a hacer cosas como crear test unitarios o bien debugar tu aplicación.

Hay muchos más pero la mayoría son simples editores online con syntax highlighting y algunos con conexión a SCM’s.

Servidores de integración continua y herramientas de calidad de código

Esto es algo que dentro del mundo open source es bastante necesario debido a la distribución temporal y espacial de los componentes de equipos que empuja un poco más hacia la calidad automatizada.

  • Cloud Bees: Es una solución para todo el proceso de desarrollo, es decir, una solución de continuous delivery usando Jenkins como orquestador del proceso.

  • Travis-ci.org. Para proyectos open source con una fuerte dependencia de GitHub.com y permitiendo incluso ejecutar test de navegador vía Selenium.

  • Coveralls. Junto a este último hay otros similares que permiten integración dentro de los repositorios de GitHub como coveralls que sirve para ver la cobertura de testing que tiene el código.

Servidores [de producción]

Hay un montón de proveedores que ofrecen diferentes soluciones en términos de servidores, algunos dan el servidor en modelo FreeTier (AWS, OpenShift) y otros ofrecen el servicio que puedas necesitar (Heroku, Jelastic) ya sea base de datos, web server, etc. A partir de aquí puedes usar los servidores para lo que quieras, incluido producción, y si necesitas escalar seguramente será muy sencillo aunque en ese punto te tocará aflojar la cartera.

En los modelos que ofrecen el servicio puedes elegir el tipo de servicio y configurarlo pero no sueles poder acceder directamente a la máquina, o al menos no suele ser necesario.

En las soluciones intermedias como OpenShift, el software deseado (Servidor aplicaciones, Base de Datos, etc) está preinstalado y después tú puedes acceder para configurarlo.

Por último las soluciones de sólo servidores en cloud (AWS) dependen de imágenes de sistemas operativos base que puede tener o no el software que se busca ya configurado, pero conceptualmente es sólo el sistema operativo.

Backups

Para conseguir tener los datos a salvo se pueden usar los propios sistemas de almacenamiento en la nube como S3 de AWS o cloudfiles de Rackspace (incluso puede interesar tenerlo en varios proveedores). Pero también hay opciones menos orientadas al mundo profesional pero con un coste muy interesante tipo Dropbox o cualquiera de sus competidores que llegan a ofrecer hasta 1 TB sin coste.

Monitoreo y alertas

Una de esas cosas que no eres consciente hasta que vas realmente a producción es que te tienes que preocupar por saber cuando tus sistema no están online pues obviamente pierdes dinero, para ello existen herramientas no sólo de alertas en caso de error sino también de monitoreo y diágnosis como New Relic o Monitis.

Conclusión

En este post hemos hecho una vista de pájaro sobre algunas soluciones disponibles en las diferentes fases de la vida del software que te pueden permitir empezar a trabajar sin inversión de dinero. En algunas ocasiones te ves obligado a ser una organización sin ánimo de lucro pero otras veces simplemente existen los modelos FreeTier y cuando realmente estos se queden cortos puedes pasar a una versión premium sin dificultad.

Para la gente del mundo del software es importante conocer las alternativas que hay ahí fuera y espero que este post ayude a tener una primera visión de lo que hay y si estáis interesados en más detalles o nuevas herramientas estad atentos al blog!

A pesar de ser un post bastante largo, si alguno está pensando iniciar una nueva andadura espero que este post os pueda ayudar.