Estaba viendo que me había llegado el boletín mensual de Infojobs y en el decía que han crecido un 7% sus ofertas de empleo relacionadas con la calidad del software. Esto me ha hecho plantearme por qué. ¿Por qué se solicita ahora más ayuda para asegurar la calidad de los desarrollos?
a) Porque ahora hay que reducir costes y los errores son costosos (muy costosos) b) Porque el sector ha madurado y se ha dado cuenta de que así no se puede seguir y se necesitan expertos en asegurar la calidad de los desarrollos. c) Quizás es que se hayan dejado de contratar desarrolladores porque ya no hay desarrollos y, proporcionalmente, parezca que han subido las necesidades de otros puestos.
Obviamente descarto la opción b) por irreal y la c) porque me parece difícil de que sea así (es una sensación). En cambio, la opción a) me parece plausible. Es posible que, por fin, esté calando el mensaje de los 70 de que el coste de los errores de desarrollo se multiplica en varios órdenes de magnitud cuando éstos llegan a producción y en España estemos empezando a salir de la crisis del software.
Todo el código fuente lo tenéis disponible en GoogleCode.
Pruebas unitarias
En Eclipse y con m2eclipse he creado el proyecto raíz con dos módulos: web (de tipo war) y web-integration-test (de tipo pom). El proyecto web lo he creado con el arquetipo “org.apache.wicket:wicket-archetype-quickstart:1.4-rc1″ y luego lo he tocado un poco.
< ?xml version="1.0" encoding="UTF-8"?>
shopaasshopaas0.0.1-SNAPSHOT4.0.0shopaaswebwarWeb Application0.0.1-SNAPSHOTUI wicket para Shop As A Serviceshopaas-websrc/main/resourcessrc/main/java****/*.javasrc/test/java****/*.javamaven-compiler-plugintrue1.51.5truetrueorg.mortbay.jettymaven-jetty-plugin10808060000maven-eclipse-plugintrueorg.apache.wicketwicket${wicket.version}org.slf4jslf4j-log4j121.4.2log4jlog4j1.2.14junitjunit4.5testorg.mortbay.jettyjetty${jetty.version}providedorg.mortbay.jettyjetty-util${jetty.version}providedorg.mortbay.jettyjetty-management${jetty.version}provided6.1.141.4-rc1
No quiero entrar en detalles sobre cómo es una aplicación Wicket, pero sí explicaré los dos detalles más importantes:
Si quiero desplegar la aplicación en Jetty no tengo más que ejecutar Maven con el “goal” jetty:run, pero esto sólo nos sirve para probar manualmente nuestra aplicación. (Por cierto, el puerto 8080 es el puerto por defecto, pero me gusta explicitar esta configuración).
Wicket proporciona la clase WicketTester, que permite probar la aplicación fuera del contenedor, mediante una simulación del mismo. De esta manera podemos hacer pruebas unitarias de todos los componentes de nuestra GUI sin necesidad de empaquetarla y desplegarla. Más adelante, en futuros artículos, tengo previsto entrar en detalle en cómo desarrollar la GUI con Wicket haciendo TDD.
package shopaas;
import junit.framework.TestCase;import org.apache.wicket.util.tester.WicketTester;
/** * Simple test using the WicketTester */public class TestHomePage extends TestCase{ private WicketTester tester;
@Override public void setUp() { tester = new WicketTester(new WicketApplication()); }
public void testRenderMyPage() { //start and render the test page tester.startPage(HomePage.class);
//assert rendered page class tester.assertRenderedPage(HomePage.class);
//assert rendered label component tester.assertLabel("message", "If you see this message wicket is properly configured and running"); }}
Esta prueba es muy simple, pero lo suficiente como para ver “por dónde van los tiros”. Como podéis comprobar, llegados a este punto podemos tener las pruebas unitarias de nuestra aplicación web completamente automatizadas y, por tanto, incluidas en nuestro sistema de integración continua sin mayor problema.
Pruebas funcionales
Pruebas funcionales o de integración son términos muy usados pero no tengo claro que todo el mundo entienda lo mismo cuando los usa. Lo que yo estoy llamando aquí pruebas funcionales o de integración son aquellas en las que pruebo el sistema desplegado (hasta donde se puede razonablemente). Pues bien, lo que voy a enseñar ahora es cómo desplegar de manera automática el war de nuestra aplicación y lanzar (automáticamente también) las pruebas.
Para esto he usado Cargo y he modificado el ciclo de vida de varios plugins (compiler, surefire y el propio cargo) para que se ejecuten en el orden adecuado. Además, he usado Selenium Webdriver para escribir y ejecutar las pruebas.
Al ejecutar (desde el proyecto raiz) Maven con el goal “integration-test”, el propio Maven estudia las dependencias entre artefactos y empaqueta el war (y ejecuta sus pruebas) antes de pasar a desplegar ese war en un Jetty 6 que se arranca y detiene solamente para ejecutar las pruebas de integración.
El único test que he incluido como prueba de concepto contiene dos tipos de prueba (una con Webdriver y otra sin él, la anotada con @Ignore).
package shopaas.web.integration.test;
import static org.junit.Assert.assertEquals;
import java.net.HttpURLConnection;import java.net.URL;
import org.junit.Before;import org.junit.Ignore;import org.junit.Test;import org.openqa.selenium.WebDriver;import org.openqa.selenium.htmlunit.HtmlUnitDriver;
public class WebappTest {
@Ignore@Test public void testCallIndexPage() throws Exception { URL url = new URL("http://localhost:8080/"); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.connect(); assertEquals(200, connection.getResponseCode()); }
private WebDriver driver;
@Before public void setUp() { driver = new HtmlUnitDriver(); }
@Test public void testHomepage() { driver.get("http://localhost:8080/"); assertEquals("Wicket Quickstart Archetype Homepage", driver.getTitle()); }
}
He utilizado el HtmlUnitDriver que, aunque también es una emulación de una navegador, para mi es suficiente, no me complica mi entorno y además es más rápido. Pero si quisiera utilizar Firefox, Safari o Internet Explorer, no tendría más que cambiar el driver correspondiente. Lógicamente, podemos tener la misma prueba con un driver diferente y estaremos asegurando que nuestra aplicación funciona para varios navegadores. Mmmmm, veo que he captado vuestra atención…
Problemas encontrados
Al poner todo esto en pie he estado bastante tiempo atascado porque cada vez que ejecutaba las pruebas de integración obtenía un mensaje de error en Jetty diciendo que no era capaz de arrancar la aplicación porque “No suitable Log constructor”. No entiendo de dónde sale el dichoso commons-logging, pero el caso es que poniendo el fichero commons-logging.properties en el classpath de la aplicación web (para que se empaquete en el war), todo pasó a funcionar perfectamente.
El otro inconveniente que tuve es más un detalle al que prestar atención que otra cosa. Se trata del valor del contexto que hay que poner en la configuración de Cargo, que debe coincidir con el valor usado en el URL de la aplicación en nuestras pruebas y en el fichero web.xml de la aplicación web.
El año pasado he comenzado el desarrollo de un plug-in para Concordion sin pruebas unitarias. “¡Vaya tío chungo!”, estaréis pensando ahora mismo. Bueno, en cierto modo tenéis razón. Pero tengo una buena excusa.
El caso es que he copipegado código del ejemplo de editor multipáginas que viene con Eclipse y también del WicketBench (un plugin para trabajar con Wicket del que pretendo plagiar más de una idea). Y resulta que este código “heredado” hace un uso bastante prolijo de métodos estáticos para obtener información de contexto y, sobre todo, de la plataforma. Y me viene al pelo el reciente artículo de Misko Hevery donde explica por qué los métodos estáticos son tan malos para las pruebas y delatan diseños deficientes.
En resumen: no tengo pruebas unitarias de mi código porque no puedo sustituir las llamadas a métodos estáticos por ningún tipo de doble. Cualquier colaborador puedo sustituirlo más o menos fácilmente con un doble (p.ej. un mock), pero un método estático está pegado “a fuego” a una clase, y no puedo sustituir una clase por otra…
Claro está que estoy cambiando el código para encapsular las llamadas a métodos estáticos en clases de colaboración que pueda sustituir en mis pruebas. Así acoto el problema, tal y como Eric Evans aconseja al explicar el “anticorruption layer” (capa anticorrupción), y mejoro la facilidad para escribir pruebas para mi diseño y, por tanto, mejoro también mi diseño.