A estas alturas de la película nadie cuestiona la necesidad de realizar pruebas unitarias en todo desarrollo de software. Y cuando se habla de pruebas unitarias se entiende también que deben ser ser automatizables para poder ejecutarlas de manera desatendida (lo normal es que sea por las noches) tal como dice por ejemplo la metodología de la IntegraciónContinua.
Efectivamente, nadie cuestiona su necesidad, pero sin embargo ¿qué pasa en el día a día del desarrollo? que en muchas ocasiones estos test unitarios no se hacen. En muchas ocasiones, no se hacen porque se tiene la idea de que retrasaría el trabajo al tener que desarrollar más software.
Cada vez hacemos software más complejo, sobre todo con una arquitectura orientada a servicios (SOA). Desde que el usuario le da al botón de “aceptar” en la pantalla hasta que aparecen los datos de respuesta, esta petición viaja por un buen número de servidores y aplicaciones, capas y subcapas de software, módulos, librerías de terceros, bases de datos, etc. etc. ¿que pasa cuando algo falla y no tenemos pruebas automatizadas?:
- No sabremos en que sitio falla
- Tendremos que arrancar toda la aplicación (o varias) para poder reproducir el error. Incluso tendremos que desplegarla en un entorno controlado para poder ver que está fallando.
- Si localizamos el error y lo arreglamos, no podremos hacer pruebas de regresión para asegurarnos de que el nuevo cambio no ha estropeado algo que funcionaba antes.
La creación de test unitarios, no es un capricho ni un añadido superfluo al proyecto, forma parte del propio de desarrollo, de la misma manera que un bucle for en nuestro código.
Un nuevo software que probar
Los sevicios web, ya sean SOAP o REST, está explotando en las empresas y están proliferando por doquier. No podemos dejarlos fuera de nuestras pruebas automatizadas. Debemos tener un mecanismo para poder probar la invocación a estos servicios y saber si están caídos o si no funcionan correctamente.
Para facilitar las pruebas de servicios y ahorrar tiempo y complejidad, he preparado una sencilla utilidad que nos permite invocar un servicio SOAP y buscar en el mensaje de resupuesta una cadena de texto que nos diga si la ejecución ha sido correcta.
Como mejor se ve esto es con un ejemplo, así que usaré para ello un servicio muy sencillo que implementa una suma de dos números. El objetivo sería hacer un test que compruebe que la suma se hace correctamente.
Mensaje de entrada y salida de nuestro servicio de ejemplo obtenido con SOAPUI
Para implementar la prueba unitaria en Java seguiremos los siguientes pasos:
- obtener el WSDL del servicio. El WSDL es el fichero que describe el interfaz del servicio (parámetros de entrada/salida) y la URL o endpoint del servicio.
- Construir un mensaje SOAP suele ser bastante complejo así que usaremos la herramienta gratuita SOAPUIpara construirlo.
- Ejecutamos SOAPUI con el mensaje de entrada e invocamos al servicio. Con esto obtendremos el mensaje de vuelta. En esta respuesta seleccionaremos una parte del mismo que pruebe que el mensaje ha sido correcto
- Crear un Junit y configurar la invocación al servicio con el SOAP de entrada del punto anterior. Poner un assert que compruebe el resultado correcto.
Para invocar al servicio web es necesario enviar una petición HTTP de tipo POST. Para ello utilizaré un método de una clase de utilidad que recibe como parámetros el endpoint del servicio y el mensaje de entrada. Devolverá un objeto con la respuesta: el código HTTP retornado y el mensaje de salida.
La clase de utilidad es la siguiente:
public class HttpUtil {
public InvokePostResponse invokeWS(String endpoint, String message) throws Exception {
URL url;
HttpURLConnection connection = null;
InvokePostResponse ipr = new InvokePostResponse();
try {
// crea el objeto URL con el endpoint del servicio
url = new URL(endpoint);
// abre la conexión
connection = (HttpURLConnection) url.openConnection();
//Selecciona el método POST.
//Es el método que hay que usar para invocar a un servicio web
connection.setRequestMethod(«POST»);
//configuración de la conexión HTTP
connection.setUseCaches(false);
connection.setDoInput(true);
connection.setDoOutput(true);
//según el estándar SOAP hay que enviar un cabecera HTTP con SOAPAction
connection.setRequestProperty(«SOAPAction», «»);
// Enviar la petición mediante un OuputStream
DataOutputStream output = new DataOutputStream(connection.getOutputStream());
output.writeBytes(message);
//vacía el buffer de salida
output.flush();
//cierra el flujo de salida
output.close();
//obtiene el código de respuesta de la petición HTTP
ipr.setHttpCode(connection.getResponseCode());
// Recoge la respuesta del servicio mediante un InputStream
InputStream input;
// si el código es igual o menor de 400 es OK y se coge el input normal
// si es mayor de 400 es que ha habido un error y se coge el input de error
if (connection.getResponseCode() <= 400) {
input = connection.getInputStream();
} else {
input = connection.getErrorStream();
}
//se lee el flujo de entrada línea a línea
BufferedReader rd = new BufferedReader(new InputStreamReader(input));
String line;
StringBuffer response = new StringBuffer();
while ((line = rd.readLine()) != null) {
response.append(line);
response.append(‘\r’);
}
//se cierra elflujo de entrada
rd.close();
//se guarda el mensaje de respuesta en el objeto respuesta
ipr.setMessage(response.toString());
} catch (Exception e) {
throw e;
} finally {
//se cierra la conexión en un bloque finally para asegurarnos de
//se sierra en cualquier caso
if (connection != null) {
connection.disconnect();
}
}
return ipr;
}
}
Clase Test
Por último, sólo nos queda implementar la clase de Test. Usaré JUnit ya que es un framework ampliamente usado e integrado con todas las herramientas de desarrollo. Para implemetar el test necesitaremos:
- El endpoint del servicio. Se obtiene del WSDL.
- El mensaje de entrada. Se obtiene de la herramienta SOAPUI tal como se ve en la captura de pantalla
- El string en el mensaje de vuelta que quiero comprobar y que valida la ejecución del servicio. En este caso como invoco al servicio de suma con los parámetros 2+3, buscaré en la salida el texto <sumaReturn>5.0</sumaReturn>
La complejidad del test se esconde como veis en el método invokeWS al que se le pasa la URL
del endpoint (la URL desde la que responde el servicio), y un string con el mensaje de entrada
(obtenido del SOAPUI)
Obtenemos la respuesta, comprobamos que el código http de respuesta sea 200 y buscamos
un substring en el string de respuesta con la ayuda del método indexOf. En este caso, como
hemos invocado con los valores 2+3 buscaremos «<sumaReturn>5.0</sumaReturn>»
@Test
public void test() {
System.out.println(«invocando…»);
StringBuffer sb = new StringBuffer();
sb.append(«<soapenv:Envelope xmlns:soapenv=\»http://schemas.xmlsoap.org/soap/envelope/\» «);
sb.append(«xmlns:cal=\»http://calculadora.xatacaon\»>»);
sb.append(«<soapenv:Header/>»);
sb.append(«<soapenv:Body>»);
sb.append(«<cal:suma>»);
sb.append(«<cal:a>2</cal:a>»);
sb.append(«<cal:b>3</cal:b>»);
sb.append(«</cal:suma>»);
sb.append(«</soapenv:Body>»);
sb.append(«</soapenv:Envelope>»);
HttpUtil h = new HttpUtil();
InvokePostResponse response = null;
try {
response = h.invokeWS(«http://localhost:8080/CalculadoraWeb/services/Calculadora»,
sb.toString());
} catch (Exception e) {
Assert.fail(e.toString());
}
Assert.assertNotNull(«respuesta nula», response);
Assert.assertNotNull(«mensaje de respuesta nulo», response.getMessage());
System.out.println(response.getMessage());
Assert.assertTrue(«no devuelve el resultado esperado», response.getMessage().indexOf(«<sumaReturn>5.0</sumaReturn>») != -1);
}
En un par de minutos tendremos un caso de test que podremos ejecutar de manera automatizada tantas veces como queramos.
13/10/2011 at 22:10
En este caso no es un test unitario, sino uno de integración. Sin unos mocks/stubs/doubles o algo así dando vueltas no estarás haciendo test unitario.
Si el test falla nunca sabrás si es un problema de la infraestructura (http / red / etc) o del servicio mal implementado o el componente que está por debajo.
Lo único que te interesaría probar en un servicio «Suma», es que se invoca a la operación que haga la suma con los sumandos correctos del componente que resuelve la funcionalidad. Justamente deberías tener un double (http://martinfowler.com/bliki/TestDouble.html) que reemplace a ese objeto y así solo probar la parte de lo que debe hacer el servicio.
13/10/2011 at 22:42
Ok. Se puede ver como que no es unitario, y es de integración. Pero eso es un poco subjetivo, depende de lo que quieras probar ¿No? En este caso quiero probar si el servicio está levantado y respondiendo bien.
Dejémoslo en pruebas automatizadas.