30.10.06

Combinando el modo grabar/reproducir con el modo API de código de Selenium

Saludos.

Selenium (http://www.openqa.org/) es una herramienta open-source estupenda para realizar pruebas de sistema / aceptación de aplicaciones web. Selenium incluye una herramienta que se integra con Firefox y que permite grabar todo lo que hagamos con Firefox, guardarlo en un archivo, y reproducirlo después. Además, Selenium también incluye la posibilidad de escribir nuestras pruebas directamente en Java, C#, Python y Ruby.

Para un proyecto real, estábamos muy interesados en utilizar Selenium y su herramienta para grabar / reproducir pruebas. Sin embargo, queríamos hacer dos cosas que no se pueden hacer con la filosofía de grabar / reproducir: la primera es añadir código adicional que compruebe el estado de la aplicación y el estado de la base de datos y la segunda ejecutar todas las pruebas grabadas automáticamente cada cierto tiempo.

Por suerte el código de una prueba cuando se graba es muy similar al código que habría que escribir en un lenguaje, por ejemplo en Java o CSharp. A continuación incluyo el código de una sencilla prueba

Código 1. Prueba grabada con Firefox.


class NewTest
def test_foo
open "/links_jsp/Default.jsp"
assertTitle "Links"
clickAndWait "//a[contains(@href, 'LinkNew.jsp')]"
assertTitle "Links"
type "name", "Prueba"
type "link_url", "Prueba"
type "description", "URL de prueba"
clickAndWait "//input[@value='Insert']"
assertTitle "Links"
end
end


Código 2. Prueba escrita en Java


import com.thoughtworks.selenium.*;
import junit.framework.*;


public class Selenium_InsertarEnlace extends TestCase {

private Selenium sel;

public void setUp() {
sel = new DefaultSelenium("localhost",
4444, "*firefox", "http:/localhost:8080");
sel.start();
}

public void testInsertarEnlace() {
sel.open("/links_jsp/Default.jsp");
assertEquals("Links", sel.getTitle());
sel.click("//a[contains(@href, 'LinkNew.jsp')]");
sel.waitForPageToLoad("5000");
sel.type("name", "Prueba");
sel.type("link_url", "Prueba");
sel.type("description", "URL de prueba");
sel.click("//input[@value='Insert']");
sel.waitForPageToLoad("5000");
assertEquals("Links", sel.getTitle());
}

public void tearDown() {
sel.stop();
}

public static void main(String[] args) {
junit.textui.TestRunner.run(new TestSuite(Selenium_InsertarEnlace.class));
}
}


Código 3. Prueba escrita en CSharp

using Selenium;
using NUnit.Framework;
namespace MyTests {
[TestFixture]
public class Selenium_InsertarEnlace {
private ISelenium sel;

[SetUp]
public void SetUp() {
sel = new DefaultSelenium("localhost",
4444, "*firefox", "http:/localhost:8080");
sel.Start();
}

[Test]
public void testGoogle() {
sel.Open("/links_jsp/Default.jsp");
Assert.AreEqual("Links", sel.getTitle());
sel.Click("//a[contains(@href, 'LinkNew.jsp')]");
sel.WaitForPageToLoad("5000");
sel.Type("name", "Prueba");
sel.Type("link_url", "Prueba");
sel.Type("description", "URL de prueba");
sel.Click("//input[@value='Insert']");
sel.WaitForPageToLoad("5000");
Assert.AreEqual("Links", sel.getTitle());
}

[TearDown]
public void TearDown() {
sel.stop();
}
}
}



Para ejecutar la prueba en Java, es necesario tener en ejecución el servidor de Selenium ("java -jar selenium-server.jar") y tener en el PATH la ruta a Firefox. Al ejecutar la prueba en Java (necesitamos "selenium-java-client-driver.jar") se abrirá una nueva instancia del Firefox y, en la salida estándar veremos el resultado de la prueba. La prueba en CSharp no la hemos ejecutado, pero debería ser igual de sencillo.

Ahora es posible añadir a la prueba todas las comprobaciones adicionales que queramos como, por ejemplo, conectar con la BBDD para asegurarnos que el enlace está ahí.

Estamos escribiendo un sencillo script para traducir automáticamente el código de una prueba capturada a código Java. Si alguien está interesado no tiene más que escribirme.

25.10.06

Aplicaciones autoadministradas y autoreparadas

Después de asistir a la conferencia de un gurú (o eso dijeron) de la arquitectura de software en un congreso, me llevo la idea de que el desarrollo de aplicaciones que sean capaces de administrase ellas solitas (y de paso repararse) es un buen nicho de mercado.

Esto no es, ni mucho menos, ciencia-ficción. Si no me equivoco la plataforma Java ya incorpora desde hace tiempo interfaces de monitorización de componentes bajo el nombre de JMX.

Para poder hacer esto, dado que la tecnología existe, lo que hay que hacer es plantearlo desde el comienzo del desarrollo de la aplicación e incluir en nuestra aplicación, módulos que monitoricen el funcionamiento de la misma y que tengan la capacidad de tomar las medidas correctivas necesarias. Esto puede venderse muy bien, ya que, como nos comentó el gurú, por cada dólar gastado en adquirir una aplicación (esto incluye el desarrollo a medida), se gastan nueve dólares en su administración y mantenimiento (en euros será menos, seguro). Si podemos desarrollar aplicaciones autoadministradas, podemos reducir el gasto durante toda la vida útil de la aplicación de nuestros clientes 9 veces, lo cuál es bastante interesante.

Además una aplicación que sea capaz de arreglarse a sí misma, es una aplicación que no tiene por qué detenerse nunca, lo cuál es muy beneficioso en ciertos nichos de mercado.

19.10.06

Presentaciones "De casos de uso a casos de prueba" en mi web

Saludos.

He puesto en mi web el 95% de las presentaciones que voy a usar en el seminario "De casos de uso a casos de prueba" que impartiré el martes 24 en el evento SoloRequisitos. La dirección es:

http://www.lsi.us.es/~javierj/cursos.htm

Después del evento publicaré el 100% de las transparencias, aunque el 5% que falta son las menos importantes.

Feliz otoño

18.10.06

Eclipse. Caballo ganador en la investigación

Saludos.

A menudo nos encontramos comparativas entre Eclipse y NetBeans y muchas veces nos da la impresión (al menos a mí) de que ambas plataformas tienen una lucha feroz por imponerse en el mundo de la empresa.
sin embargo en el mundo de la investigación y las universidades no es así. Por lo que he podido ver en un reciente congreso, todo el mundo usa Eclipse y desarrolla sus ideas a partir de Eclipse. En ese sentido, el EMF es una herramienta de uso casi inevitable. Netbeans no aparece por ningún sitio ni se le espera.

¡Dios mío, yo uso Netbeans!. ¿De dónde me puedo descargar Eclipse?.

16.10.06

Evitando el lado oscuro de las pruebas (y II)

Como ya comentamos hace tiempo en este blog, las pruebas de código tipo JUnit tienen un lado oscuro. Si el código cambia, las pruebas pueden quedar inservibles, lo cuál supone un desperdicio de tiempo y recursos.

La solución que propuse fue la de intentar automatizar lo máximo posible la generación de pruebas. Así, si el código cambiaba, simpleente se volvía a ejecutar el programa que generaba un nuevo conjunto de pruebas.

Otra posible solución que se ha comentado en el taller de pruebas es la verificacón estática del código. Es decir, revisar el código para buscar errores antes de ejecutarlo. Según comentaron, las revisiones estáticas de código pueden resolver cerca de un 80% de los errores básicos. Un artículo muy bueno al respecto puede encontrase en (http://in2test.lsi.uniovi.es/pris2006/).

Feliz otoño.

15.9.06

Reglas para escribir buenos casos de uso

Saludos a todos.

Desde principios de verano estoy colaborando con la empresa Icinetic (www.icinetic.com) aplicando algunas de mis ideas sobre pruebas para mejorar su proceso de fabricación de software y también desarrollando nuevos servicios.

Uno de los primeros pasos para poner en marcha un proceso de prueba es tener buenos casos de uso. Cuanto mejor sean los casos de uso, más fácil será fabricar el software y elaborar buenas pruebas. En Icinetic tienen su propio conjunto de reglas para desarrollar buenos casos de uso y, además, me han dado permiso para difundirlas. Estas reglas son:

1. El caso de uso se inicia con la acción de un actor.

2. El caso de uso tiene una precondición y una postcondición

3. Mostrar todas las excepciones posibles, así como los flujos alternativos. Al menos “datos incorrectos” y “error al guardar los datos”

4. Mantener el mismo nivel de abstracción.

5. Rastreabilidad hacia el requisito/s de información y el objetivo/s por el que se ha incluido el caso de uso.

6. Comprobar que las relaciones entre casos de uso (include y extends) sean consecuentes con diagramas de casos de uso.

7. Comprobar que los actores que inician los casos de uso sean consecuentes con los diagramas de caso de uso

8. En las eliminaciones comprobar revisando los requisitos de información si se debería eliminar más elementos en cascada. En tal caso detallarlo en la postcondición.

En general, este conjunto de reglas me parece muy bueno. A continuación incluyo los comentarios que les hice a ellos para que el conjunto de reglas fueran aún mejor.

Completamente de acuerdo con las reglas 1, 4, 6 y 8.
No estoy muy convencido de que un caso de uso deba tener siempre una precondición y una postcondición. Si no identificamos claramente una pre o postcondición vale más la pena no ponerla que forzar la situación.
Por regla general, una precondición expresa el estado que debe tener el sistema para poder ejecutar un caso de uso y una postcondición el estado en el que queda el sistema después de ejecutar un caso de uso. Por ejemplo, no se me ocurre ninguna postcondición para un caso de uso de realizar una búsqueda ya que no cambia nada en el sistema. Poner por poner es para nada.
La regla 5 puede hacerse más genérica. Es buena idea que cualquier requisito sea rastreable, y no solo los requisitos de almacenamiento de información.
La regla 7 me parece un poco ambigua. Cuando, en un diagrama de casos de uso hay más de un actor que participa en un caso de uso, no hay, que yo conozca, ninguna notación para saber cuál es el actor que comienza el caso de uso. Yo les propuse escribir esta regla de una forma más genérica: “comprobar que los actores participantes en un caso de uso son consecuentes con los diagramas de casos de uso”.

Además, les propuse añadir las siguientes reglas, la mayoría sacadas sobre todo del libro “Writting Effective Use Cases” de Cockburn.

9. Si el caso de uso se inicia por la acción de un actor, al final de dicho caso de uso el actor debe obtener un resultado (salvo que el caso de uso sea una inclusión o extensión de otro caso de uso. Entonces, probablemente, no se inicie con a acción de un actor.)
10. Comprobar que el caso de uso sea como un partido de tenis (El actor hace… El sistema hace…) y como un partido de fútbol (se sabe en todo momento qué pasa y quién lo hace)

11. Un caso de uso de 3 pasos o menos o de más de 10 pasos probablemente no sea un buen caso de uso (salvo que el caso de uso sea una inclusión o extensión de otro caso de uso).

12. Todas las acciones similares deben describirse con las mismas frases. Por ejemplo, enunciar todas las inserciones de datos con la frase “El actor X introduce los datos….”. No permitir variantes como “inserta datos”, “añade datos”, “incluye datos”, ni siquiera “introducir datos”, etc. Siempre igual, por muy aburrido que resulte.

Por cierto, la regla 12 es muy útil cuando varias personas distintas escriben casos de uso y también a la hora de utilizar herramientas software de análisis de casos de uso y de generación de pruebas.

Como resumen de todo lo visto: un buen requisito y caso de uso debe tener estas características: completo, correcto, no ambiguo, validado, verificable y rastreable.

a. Un requisito completo es aquel que lo cuenta todo, si hay un limite dice cuál es, si hay algo que hacer dice el qué, etc.

b. Un requisito correcto es aquel que está bien escrito, si faltas de ortografía, ni errores sintácticos y que, además, sigue correctamente las reglas para definir requisitos que se estén usando.

c. Un requisito validado es un requisito que ha sido visto y comprendido por el cliente (y ha dado su visto bueno).

d. Un requisito no ambiguo es aquel requisito que tiene el mismo significado para todo el que lo lea.

e. Un requisito verificable es un requisito que expresa cosas que se pueden probar. Un ejemplo de requisitos no verificables son los que dependen de que una persona haga algo determinado (llevar un paquete, comprobar que los códigos estén correctos, etc.).

f. Un requisito rastreable es un requisito que se sabe de dónde viene y se sabe a dónde va.


P.D.
Autonota. Ahora que retomo la actividad, sería buena idea terminar el curso de Cacuts, a ser posible, utilizando la herramienta Cactus.

6.9.06

Publicaciones en mi web.

Saludos.

He publicado en mi web una página con referencias a todas las propuestas para probar casos de uso que conozco. El enlace es http://www.lsi.us.es/~javierj/approaches.htm

También he publicado un texto dónde se describe brevemente propuestas para especificar pruebas y para implementarlas, tanto para aplicaciones de escritorio como para aplicaciones web. El enlace es http://www.lsi.us.es/~javierj/investigacion_ficheros/EICP.pdf.


Feliz septiembre.
PD: aún está pendiente terminar la serie de pruebas con Cactus.

18.8.06

La desidia del verano

Saludos a todos.

Se supone que el verano, con las vacaciones, sería una época estupendapara escribir un montón de entradas interesantes.

Pues no. La verdad es que, aunque tengo tiempo libre, en verano se apetece escribir nada. Espero volver en septiembre.

Feiz vacaciones a todos.

10.7.06

Un ejemplo de diseño de pruebas con Cactus (II).

Un millón de disculpas por la tardanza, continuamos:

Necesitaremos definir los siguientes elementos para cada caso de prueba: acciones a realizar, valores de prueba, resultado esperado. Por comodidad, vuelvo a poner el comportamiento que el servlet debe tener:


1. El servlet recibe una petición de login con un nombre de usuario y una clave.
1.1. Si el usuario ya se ha registrado, el servlet informa de ello y termina.
1.2. Si el usuario ya lo intentó 3 veces, el servlet informa de ello y termina.
2. En caso contrario, el servlet comprueba el nombre y la clave recibida.
2.1. Si el nombre es válido y la clave coincide, el servlet registra al usuario, informa de ello y termina.
2.2. Si el nombre no es válido o la clave no coincide, el servlet incrementa el número de intentos e informa de ello.


Comencemos.

Las acciones a realizar serán todas las combinaciones posibles, tal y como se muestra a continuación.


c.a) 1 -> 1.1
c.b) 1 -> 1.2
c.c) 1 -> 2 -> 2.1
c.d) 1 -> 2 -> 2.2


La combinación c.a) representa el escenario en el que un usuario solicita el acceso pero ya se había validado. La combinación c.c) representa el escenario en que un usuario solicita el acceso e introduce un nombre y clave correctos, etc.

Además, estás combinaciones pueden repetirse varias veces, tal y como se muestra a continuación:


a) 1 -> 1.1 -> 1 -> 1.1 -> ... (hasta que la sesión expire)
b) 1 -> 1.2 -> 1 -> 1.2 -> ... (hasta que la sesión expire)
c) 1 -> 2 -> 2.2 -> 1 -> 2 -> 2.1
d) 1 -> 2 -> 2.2 -> 1 -> 2 -> 2.2 -> 1 -> 2 -> 2.1
e) 1 -> 2 -> 2.2 -> 1 -> 2 -> 2.2 -> 1 -> 2 -> 2.2 -> 1 -> 1.2 -> 1 -> 1.2 -> ... (hasta que la sesión expire)
f) 1 -> 2 -> 2.2 -> 1 -> 2 -> 2.2 -> 1 -> 2 -> 2.1 -> 1 -> 1.1 -> 1 -> 1.1 -> ... (hasta que la sesión expire)


Las repeticiones de combinaciones, en principio, no las tendremos en cuenta, aunque sería interesante probar algunas, por ejemplo la c, la d, la e y la f.

Pasemos ahora a los valores de prueba. En primer lugar identificamos los valores o variables y su dominio:


Nombre de usuario (Cadena de texto)
Clave (Cadena de texto)
Número de intentos (Entero)
Está validado (Booleano)


A continuación dividimos el dominio en distintas categorías. Podemos definir, de manera informal, una categoría como un subconjunto de los valores del dominio para los cuales el sistema siempre presenta el mismo comportamiento. Algunos ejemplos de categorías se muestran a continuación:


Nombre de usuario Correcto / Incorrecto
Clave Correcto / Incorrecto
Número de intentos Menor de 3 / igual a 3 *
Registrado Cierto / Falso

*- Debemos investigar si es posible que el número de intentos sea mayor que 3 o menor que 0.


No todas las categorías pueden tomarse siempre. En general, las categorías posibles estarán restringidas por el camino que estemos probando. Por ejemplo para la secuencia c) (la secuencia de acceso correcto), el número de intentos debe ser menos que tres, el usuario no debe estar registrado y el nombre y la clave deben ser correctos. A continuación se expresan las restricciones de cada una de las combinaciones.


c.a) Registrado = Cierto.
c.b) Registrado = Falso && Numero de intentos = 3
c.c) Registrado = Falso && Numero de intentos < 3 && Nombre = Correcto && Clave = Correcto
c.d) Registrado = Falso && Numero de intentos < 3 && ( Nombre = Incorrecto || Clave = Incorrecto )


A partir de estas restricciones vamos a construir los escenarios de prueba. Un escenario de prueba es una combinación concreta, con un conjunto de valores de prueba concretos que cumplen las restricciones. Cada escenario de prueba es una prueba candidata, es decir, puede convertirse en un caso de prueba. En total, para las cuatro combinaciones del principio hemos identificado 10 escenarios de prueba:


Escenario 1:
Camino a)
Registrado = Cierto.

Escenario 2:
Camino b)
Registrado = Falso
Numero de intentos = 3

Escenario 3:
Camino c)
Registrado = Falso
Numero de intentos = 0

Escenario 4:
Camino c)
Registrado = Falso
Numero de intentos = 2
Nombre = correcto
Clave = correcto

Escenario 5:
Camino d)
Registrado = Falso
Numero de intentos = 0
Nombre = correcto
Clave = incorrecto

Escenario 6:
Camino d)
Registrado = Falso
Numero de intentos = 0
Nombre = incorrecto
Clave = correcto

Escenario 7:
Camino d)
Registrado = Falso
Numero de intentos = 0
Nombre = incorrecto
Clave = incorrecto

Escenario 8:
Camino d)
Registrado = Falso
Numero de intentos = 2
Nombre = correcto
Clave = incorrecto

Escenario 9:
Camino d)
Registrado = Falso
Numero de intentos = 2
Nombre = incorrecto
Clave = correcto

Escenario 10:
Camino d)
Registrado = Falso
Numero de intentos = 2
Nombre = incorrecto
Clave = incorrecto


Aún nos falta decidir cuáles de estos escenarios se implementarán como pruebas con Cactus y definir el resultado esperado de cada escenario. Eso lo veremos en la siguiente entrada, que espero que no tarde tanto como esta.

Feliz verano.

12.6.06

Un ejemplo de diseño de pruebas con Cactus (I).

Saludos y disculpas por la tardanza.

Vamos a ver un sencillo ejemplo donde vamos a diseñar un conjunto de pruebas para un servlet que controla el acceso mediante nombre y clave (el login de toda la vida) y las vamos a implementar con la herramienta Cactus de Apache. Por supuesto, las ideas que veamos pueden aplicarse a otras plataformas de desarrollo web y otras herramientas.
Cactus es una herramienta que nos permite escribir pruebas para código que se ejecute en un servidor, como servlets, JSP, filtros, EJB, etc. Básicamente, una prueba escrita en Cactus se divide en dos partes: la parte cliente y la parte servidor. La parte cliente se ejecuta en nuestra máquina mientras que la parte servidor se ejcuta en el servidor. Más adelante veremos como usarla, ahora vamos a describir el problema y diseñar un conjunto de pruebas.

Vamos a probar el servlet de acceso. Lo primero que necesitamos es el funcionamiento esperado del servlet. Dicho funcionamiento se describe a continuación:

1. El servlet recibe una petición de login con un nombre de usuario y una clave.
1.1. Si el usuario ya se ha registrado, el servlet informa de ello y termina.
1.2. Si el usuario ya lo intentó 3 veces, el servlet informa de ello y termina.
2. En caso contrario, el servlet comprueba el nombre y la clave recibida.
2.1. Si el nombre es válido y la clave coincide, el servlet registra al usuario, informa de ello y termina.
2.2. Si el nombre no es válido o la clave no coincide, el servlet incrementa el número de intentos e informa de ello.

El código del servlet que vamos a probar no lo necesitamos ni para diseñar las pruebas ni para codificarlas. De todas amneras dicho código se muestra a continuación:


import javax.servlet.*;
import javax.servlet.http.*;

public class AccessServlet extends HttpServlet {

private String user ="test";
private String passwd = "test";

protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, java.io.IOException
{
HttpSession session;
ServletOutputStream out;
Boolean validUser;
Integer numTries;
String pUser, pPass;

out = resp.getOutputStream();

// 1. Obtengo la sesion.
session = req.getSession();

// 2. Miro si el usuario ya está validado
validUser = (Boolean) session.getAttribute("validUser");
if (validUser != null) {
out.println("

"+"Ya eres un usuario válid");
out.flush();
return;
}

// 3. Comprobamos el número máximo de intentos
numTries = (Integer) session.getAttribute("numTries");
if (numTries == null) {
numTries = new Integer(0);
}

if (numTries.intValue() >= 3) {
out.println("

"+"Demasiados intentos");
out.flush();
return;
}

// 4. Recuperamos los parámetros.
pUser = req.getParameter("f_user");
pPass = req.getParameter("f_password");

// 5. Comprobamos si el nombre y la clave son válidos
if ( pUser.equals(user) && pPass.equals(passwd) ) {
session.setAttribute("validUser", new Boolean(true) );
out.println("

"+"Eres un usuario válido");
out.flush();
return;
}

// 6. Si no, incrementamos en 1 el número de intentos y lo guardamos en la sesión
numTries = new Integer(numTries.intValue()+1);
session.setAttribute("numTries", numTries );
out.println("

"+"Los datos de acceso no son válidos.");
out.flush();

}
}



Por simplicidad, se ha incluido el nombre y la clave válidos en el propio servlet. Si consultáramos con una base de datos, las pruebas que vamos a diseñar a continuación serán exactamente iguales. Sin embargo la arquitectura de pruebas (por ejemplo el setUp() de un caso de prueba) será un poco más complejo.


En la próxima entrada diseñaremos pruebas para probar este servlet y las codificaremos con Cactus.

PD: en el código de ejemplo faltan los println para las etiquetas HTML y BODI.

18.5.06

Extrayendo fragmentos de frases con expresiones regulares

Saludos.

Recientemente, desarrollando unas herramientas de prueba, me he encontrado la necesidad de extraer fragmentos de frases compuestas. Lo que buscaba era poder hacer algo así. Si tengo, por ejemplo, estas dos frases.

1: El niño cogió la pelota con las manos.
2: El niño de azul cogió la pelota grande y morada con las dos manos.

Y quiero aplicar el siguiente patrón.

El $1 cogió $2 con $3

El resultado que quiero es:

1: $1 = "niño", $2 = "la pelota", $3 = "las manos"
2: $1 = "niño de azul", $2 = "la pelota grande y morada", $3 = "las dos manos"


Por suerte encontré una manera rápida y fácil de hacer esto en Java con expresiones regulares y Jakarta RegExp (http://jakarta.apache.org/regexp/index.html). Con esta herramienta escribo el patrón utilizando una expresión regular y encerrando entre paréntesis las expresiones que luego quiero recuperar. Así, el patrón anterior se expresaría de la siguiente forma:

El (.+) cogió (.+) con (.+)

El código para aplicar las frases anteriores al patrón se muestra a continuación.



import org.apache.regexp.*;

public class DemoBlog {

public static void main(String[] args) {

String f1 = "El niño cogió la pelota con las manos.";
String f2 = "El niño de azul cogió la pelota grande y morada con las dos manos.";
String re00 = "El (.+) cogió (.+) con (.+)";

RE re = new RE(re00);
System.out.println("Match: " + re.match(f1) );
System.out.println("Match: " + re.match(f2) );
}
}




El código anterior nos muestra por consola que ambas frases casan con la expresión regular.

Ahora, para extraer la parte de la frase que casa con cada una de las expresiones entre paréntesis del patrón, solo tenemos que llamar a getParen(índice), con índice = 1 para obtener el primer paréntesis e índice = 3 para el último. Por ejemplo:


System.out.println("$1 = " + re.getParen(1) );
System.out.println("$2 = " + re.getParen(2) );
System.out.println("$3 = " + re.getParen(3) );


Además, índice = 0 nos devuelve todo el texto (en este caso la frase completa). Si no queremos saber a priori cuanto paréntesis tenemos, podemos ir llamando a getParen(índice) hasta que este nos devuelva null.


Las clases de expresiones regulares (java.util.regexp) que viene a partir del SE 1.4 (y que comentamos en el post de pruebas con JUnit y expresiones regulares) no permiten hacer lo mismo. O, al menos, yo no he encontrado ninguna manera de hacerlo.

También estuve mirando el proyecto ORO de Jakarta (http://jakarta.apache.org/oro/index.html), por si tuviera algo que me permitiera hacer esto mismo de una manera más potente. Pero en un primer vistazo no he visto nada interesante.

8.5.06

Rompiendo una lanza a favor de las empresas de informática

Saludos.

Algunas veces escuchamos, o leemos, comentarios de que en tal o cual empresa se escriben aplicaciones deficientes y código malo. La causa principal, dicen, es la falta de tiempo. n las empresas hay que hacerlo ya, y por eso no hay tiempo para hacer una buena planificación y un buen diseño.

No creo que eso sea una buena justificación para hacer mal código. A continuación juego de abogado del diablo y expongo el por qué.

Dudo mucho que a un arquitecto le pidan el diseño de una torre o unos chalets y le digan que el tiempo no importa, que tarde lo que le apetezca. Lo mismo les pasa a los albañiles, seguro que ellos no tienen tiempo para planificar cómo van a poner los ladrillos. Sin embargo, los edificios no se caen, ni la gente deja de usarlos porque son inhabitables, ni hay que llamar constantemente a arquitectos y albañiles para que pongan parches.

A nadie se le ocurre que, por mucha prisa que haya en levantar un edificio, los albañiles comiencen a poner ladrillos por su cuenta. ¿por qué?. Pues porque los arquitectos, los albañiles, y tantos otros profesionales, saben hacerlo rápido y hacerlo bien.


Esto es algo que, según mi opinión, los informáticos aún no sabemos hacer. Si no hay tiempo para hacer un buen diseño, hay que hacer un buen diseño sobre la marcha.

Cuando escribamos el código debemos Ser capaces de identificar rápidamente las necesidades y decidir qué clases construir. Debemos tener muy en cuenta algunos conceptos de diseño fundamentes como las clases tímidas o la encapsulación (pedir a los objetos que hagan operaciones, en vez de pedirles información para hacerlo nosotros). Tampoco debemos olvidar los patrones, por ejemplo, utilizar siempre el patrón iterador (IEnumerator en .NET) para recorrer colecciones de objetos, o implementarlo para nuestras propias colecciones. Si Java y .NET o hacen, ¿por qué no nosotros?. Tampoco hay que escribir tanto código.

Desde mi opinión, la mayoría de las personas que empiezan en las empresas de informática, vengan de la universidad, de FP, de cursos de formación o autodidactas, no saben hacer estas cosas (incluso algunas que llevan muchos años si no se esfuerzan en mejorar). Aunque también tengo mi opinión de por qué pasa esto, no voy a entrar en ese debate.

Si nos formamos adecuadamente y ponemos en práctica lo aprendido nuestro código no va a ser bueno a la primera y cometeremos errores. Pero seguro que esos errores serán más pequeños y fáciles de arreglar (encapsular / desacoplar, encapsular / desacoplar, encapsular / desacoplar,....). Seguro que nuestro código es muchísimo mejor y seguro que

podremos ir convenciendo a nuestros jefes de que, si le dedicamos algo de tiempo al principio, nuestro código será aún mejor, seremos más productivos y más rápidos.

En resumen, si sabemos hacerlo bien, hacerlo rápido sólo implica pensar más rápido. Hacerlo mal nunca es hacerlo rápido.

Termino con otra opinión que, en principio, puede parecer que no tenga mucha relación pero creo que la tiene. Un sueldo bajo no debe ser excusa para hacer mal nuestro trabajo.

PD:

Por supuesto, esto es una opinión personal algo "extrema" y provocadora. Ni esto es la causa de todos los males ni su solución. Por descontado cualquier otra opinión a favor o en contra es bienvenida.

27.4.06

Un plan de pruebas para una aplicación gráfica.

Saludos a todos.

Recientemente me he visto involucrado en un interesante proyecto para desarrollar una aplicación de escritorio con una interfaz gráfica en Java. Esta aplicación permitirá diseñar formularios de una manera gráfica. Estos formularios se almacenan archivos XML, son específicos de una empresa y utilizan un juego de controles predeterminados y bien documentados.

La aplicación no es vital. De hecho, sus futuros usuarios llevan ya tiempo creando y trabajando con estos formularios. Sin embargo, ahora estos formularios se construyen a mano con editores de texto. Esto presenta varios problemas: no se puede ver el resultado o los cambios a medida que se hacen y hay que conocer un número muy alto de etiquetas y atributos.

A continuación expongo un resumen preliminar de cómo creo que debe ser un plan de pruebas y, en concreto, la parte que aborda las pruebas del sistema.


Pruebas del sistema.
Las pruebas del sistema (según Métrica v3) ejercitan a fondo el sistema completo para verificar que todo funciona bien. Dentro de las pruebas del sistema se encuentran varios tipos de prueba como pruebas funcionales, de carga, de seguridad, de usabilidad, de stress, etc. En este caso, yo me voy a centrar solo en las pruebas funcionales, esto es, las pruebas que verifican que el sistema hace lo que debe hacer a través de su interfaz externa, en este sistema, una interfaz de usuario gráfica.

Lo primero que vamos a definir es qué se puede hacer con la aplicación:

1. Crear un nuevo formulario.
2. Modificar un formulario existente.
2.1. Añadir nuevos controles.
2.2. Eliminar controles
2.3. Modificar las propiedades de los controles
2.4. Modificar la posición de los controles.

Una vez definida la funcionalidad a probar, hay que determinar los valores de prueba. Estos valores de prueba son, sin duda, los formularios. Dividimos los formularios en distintas categorías.

- Formularios básicos: Un único componente.
- Formulario simples: Entre dos y cuatro componentes.
- Formularios medios: entre cinco y ocho componentes.
- Formularios grandes: entre 9 y 14 componentes.
- Formularios enormes: quince o más componentes.

También será necesario, cuando haya más información sobre el proyecto, estudiar las características de cada componente que puede haber en un formulario y decidir si es necesario diseñar pruebas específicas para probar características particulares de componentes.


Diseño de pruebas.

Ahora, se trata de combinar las operaciones y los tipos de formularios para construir pruebas. Combinando las 5 operaciones a realizar sobre un formulario con los 5 tipos de formularios posibles tenemos un total de 25 tipos de pruebas iniciales. Algunos ejemplos son:

1. Crear un formulario básico.
2. Añadir un nuevo control a un formulario medio.
3. Eliminar un control de un formulario enorme.

Estas pruebas se repetirán varias veces con distintos tipos de controles. Además, estas pruebas se combinarán entre ellas para construir pruebas mayores. Por ejemplo, si combinamos las pruebas 2 y 3 para un formulario enorme obtenemos una prueba que añade nuevos controles y después elimina otros controles ya existentes.

Estimamos que, en las primeras iteraciones, la herramienta tendrá pocas opciones y pocos controles. Así, en las primeras iteraciones deben primar las pruebas de creación de formularios básicos y simples. Por la misma razón, en las últimas iteraciones deben primar las pruebas de modificaciones de formularios grandes y enormes.


Herramientas.

Queremos usar dos herramientas para realizar estas pruebas.

La primera herramienta nos permitirá definir una secuencia de interacciones con la interfaz gráfica (mover el ratón, hacer click, etc.) y ejecutarlas sobre nuestra aplicación. Además, esa herramienta debe permitir comprobar que los resultados de las interacciones son los esperados (un cambio en la pantalla, la aparición de una nueva ventana, etc.).
En un post anterior ya hemos comentado algunas de las herramientas que podemos utilizar para implementar este tipo de pruebas en sistemas Java, tales como Marathon (http://marathonman.sourceforge.net/) o Abbot (http://abbot.sourceforge.net/).


Hemos visto que queremos construir nuevas pruebas a partir de pruebas básicas (por ejemplo una prueba que sea crear un formulario nuevo, insertar tres controles, guardarlo, cargarlo, insertar dos controles más, borrar un control, modificar otro, etc). Esto presenta algunos problemas:
1) Estas pruebas son largas de codificar (perdemos recursos) y, cuanto más largas sean, más tendremos que escribir más errores podemos tener.
2) El número de posibles combinaciones de añadidos y modificaciones es muy alto.
3) Si hay algún cambio en la interfaz o bien damos por perdidas las pruebas que nos ha constado mucho tiempo implementar, o bien las modificamos con lo que perdemos más tiempo aún.

La solución es una herramienta que coja unas pocas pruebas simples, las combine y genere tantas pruebas complejas como queramos. Existen trabajos que hablan de como combinar pruebas pero no conozco ninguna herramienta que pueda hacerlo.

Como opino que las ventajas superan a los inconvenientes, nos planteamos construir una herramienta ad-hoc para construir pruebas a partir de otras. Una vez que tengamos decida una herramienta concreta y sepamos qué formato tienen las pruebas (por ejemplo archivos de código Python o archivos XML) podremos desarrollarla.



Siguientes pasos.

Como hemos comentado al principio, esto es una aproximación preliminar. A medida que el desarrollo del sistema avance, se definan con más precisión la funcionalidad del sistema, se obtengan formularios de ejemplo por parte de los clientes, etc. Este plan de pruebas y las pruebas diseñadas se irán completando. Así, durante cada iteración se dispondrá de un conjunto de pruebas del sistema listas para verificar que todo va bien.

La información necesaria para seguir completando este plan es:
- Una descripción detallada de los componentes.
- Los casos de uso.
- Formularios, a ser posible, reales.

Ojo, las pruebas contempladas aquí no son suficientes para desarrollar una buena fase de pruebas del sistema. Además se deben probar la correcta implementación de los casos de uso (http://es.wikipedia.org/wiki/UML#Ejemplo_de_diagrama_de_caso_de_uso) del sistema. Por ejemplo, probablemente exista un caso de uso que sea cargar formulario de archivo y otro que sea guardar formulario en archivo dónde se explicará como tiene que comportarse la aplicación al realizar esas operaciones. Por tanto, debemos desarrollar también algunas pruebas que comprueben no solo que los formularios se cargar y guardan correctamente y dichas operaciones se comportan como dicen los casos de uso.


Si este proyecto sigue adelante y, además, hay la oportunidad de poner estas ideas en prácticas, os lo comentaré.

12.4.06

Andalucía, tierra de OpenCMS

Saludos a todos.

En una entrada anterior comentada que, en Andalucía (al sur de España), las tencologías dominantes en la industria, sin duda, son Java y Oracle (http://rincew.blogspot.com/2005_10_01_rincew_archive.html). De un tiempo a esta parte, me he dado cuenta que varias empresas (de las más importantes, al menos en mi opninión) están utilizando OpenCMS como plataforma de desarrollo de portales (http://www.opencms.org/).

No conozco la herramienta, así que ignoro si es la mejor, pero si varias empresas importantes y la administración pública la usan, desde luego hay que tenerla en cuenta.

4.4.06

Referencias sobre generación de pruebas a partir de casos de uso.

Saludos.

Para quién le interese. A continuación pongo algunos enlaces sobre documentación para generar pruebas a partir de casos de uso.

En mi página web ha puesto los reusltados de un enlace comparativo con casos prácticos:
http://www.lsi.us.es/~javierj/publications.html
(descargar "Generación de casos de prueba a partir de la especificación funcional")

En STORM es posible encontrar varias listas de herramientas de prueba. Aunque no conozco ninguna herramienta de acceso público para generar pruebas, sí
se pueden encontrar algunas herramientas muy interesante para implementar las pruebas. Se puede encontrar alguna herramienta de libre descarga para
TSL, pero está a añados luz de distancia de WinRunner.
http://www.mtsu.edu/~storm/

Algunos artículos que pueden ser interesantes. Son fáciles de encontrar en Google. Si alguien no los localiza que no dude en pedírmelos:

Axel Ruder et-al. 2004. A Model-based Approach to Improve System Testing of Interactive Applications. ISSTA’04. Boston, USA.
L. Briand, Y. Labiche. 2002. A UML-Based Approach to System Testing. Carleton University Inner Report.
Heumann , Jim, 2002. Generating Test Cases from Use Cases. Journal of Software Testing Professionals.
Nebut, C. Fleurey, et-al. 2003. Requirements by contract allow automated system testing. Procedings of the 14th International symposium of Software Reliability Engineering (ISSRE'03). Denver, Colorado. EEUU.

UCTSystem. http://www.irisa.fr/triskell/results/ISSRE03/UCTSystem/UCTSystem.html

Espero que los disfruteis.

Si alguien tiene interés en el tema y quiere más información que no dude en comunicármelo.

22.3.06

Materiales de cursos

Saludos.

Estoy poniendo en mi página web materiales relacionados sobre algunos y seminarios que he tenido la suerte de poder impartir. Por si a alguien le interesa la dirección es:
http://www.lsi.us.es/~javierj/cursos.htm

Aún hay poco material. Espero ir subiendo más cosas a lo largo de estas semanas.

Nos leemos.

3.3.06

Trabajando con HipersonicDB

Saludos.

Existen varias bases de datos libres implementadas en Java. Una de ellas, la que uso habitualmente, es HipersonicSQL DB (http://hsqldb.org/).

¿Por qué utilizar una base de datos pequeña escrita en Java cuando existen sistemas como MySQL, PostgreSQL o Firebird?. En mi caso, la respuesta es comodidad. Puedo distribuir junto con mis programas todo el servidor de bases de datos (sólo son 200-300 KB de más) evitando que los usuarios tengan que instalar ni configurar nada. En las primeras etapas de desarrollo es una opción muy interesante, ya que podemos tener nuestra base de datos funcionando de manera autónoma sin depender de la instalación y configuración de ningún servidor.
Además, HSQLDB es bastante completa soportando vistas, integridad referencial, transacciones, consultas batch, etc. (verificar esto).

HipersonicSQL tiene varios modos de funcionamiento. Los dos principales son local y servidor. En el primer modo, el servidor se inicia y termina a la misma vez que el programa. En el segundo, hemos de iniciar y terminar el servidor a mano (como en cualquier otro sistema). La principal diferencia entre ambos es su cadena de conexión, como se muestra a continuación

Modo servidor: jdbc:hsqldb:hsql://localhost/base_de_datos
Modo local: jdbc:hsqldb:ruta_a_la_base_de_datos

A continuación pongo el código de un sencillo ejemplo que se conecta a una base de datos en modo local, realiza una consulta y muestra el resultado. Lo único necesario para que este ejemplo funcione (además de la base de datos), es tener hsqldb.jar en el CLASSPATH.


import java.sql.*;
public class SQLStatement {
public static void main(String args[]) {
try {
Class.forName("org.hsqldb.jdbcDriver");

} catch(java.lang.ClassNotFoundException e) {
System.err.println(e.getMessage());
}
try {
Connection con = DriverManager.getConnection("jdbc:hsqldb:./testdb/testdb",
"sa", "");

String query = "select COF_NAME from COFFEES";
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(query);
ResultSetMetaData rsmd = rs.getMetaData();
int numberOfColumns = rsmd.getColumnCount();
System.out.println("Columns: "+numberOfColumns);
while (rs.next()) {
System.out.println("Fila " + rowCount + ": ");
for (int i = 1; i <= numberOfColumns; i++) {
System.out.print(" Columna " + i + ": ");
System.out.println(rs.getString(i));
}
System.out.println("");
}
stmt.execute("Shutdown");
stmt.close();
con.close();

} catch(SQLException ex) {
System.err.println(ex.getMessage());
}
}
}


No todos son ventajas trabajando con HSQLDB. Por ejemplo, las bases de datos de la versión 1.7 no son compatibles con la versión 1.8, lo cuál es malo. Sin embargo, tampoco he encontrado la necesidad de actualizar, por lo que he seguido trabajando con el 1.7 sin problemas.

6.2.06

Mutaciones y Java.

Saludos.

Una técnica de prueba de código bastante antigua (de los 70) y famosa son las mutaciones. Sin embargo, esta técnica no suele ser muy conocida. Voy a hacer un breve resumen de ella y después comentaré una herramienta para Java.

A grandes rasgos, la técnica de las mutaciones surge de la idea de que todos los fallos de un programa vienen motivados por pequeños cambios en el código, como un error al escribir un número, un operador matemático o una comprobación. Obviamente, un fallo grande es un conjunto de fallos pequeños.

La técnica de las mutaciones propone un conjunto de operadores para obtener código mutado: por ejemplo cambiar un '>' por un '<' o un '+' por un '-'. Con esto, el código mutado que se genera es erróneo (aunque no siempre, puede dar la casualidad de que siga estando bien).

¿Pero para qué sirve el código mutado?. Principalmente tiene dos usos: 1) guiar la construcción de pruebas, 2) validar un conjunto de pruebas. Comentémoslos brevemente.

En primer lugar, el código mutado nos dice qué prueba hemos de construir. Por ejemplo, si hemos cambiado un '+' por un '-', debemos construir una prueba que sea capaz de detectar ese error. A más mutaciones, más pruebas y más fiable será nuestro código.

Además, las mutaciones pueden indicarnos lo bueno que es un conjunto de pruebas ya existente. Así, generamos un conjunto de mutaciones, ejecutamos las pruebas sobre el conjunto y, según el número de mutaciones detectadas podemos tener una idea de la eficacia de las pruebas.

Como ya he comentado, esta es una técnica antigua, por lo que ya existen muchos trabajos que describen operadores "mutacionales" para obtener mutaciones de código en muchos lenguajes como Fortran, C, o Java. También existen varias herramientas, aunque, personalmente, no conozco ninguna muy famosa, ni libre, ni fácil de usar.

La última que he visto (MuJava http://www.isse.gmu.edu/faculty/ofut/mujava/) es gratuita pero no libre. El trabajo con esta herramienta se divide en dos partes: en primer lugar la herramienta es capaz de generar ella sola un conjunto de mutantes a partir de las clases y métodos originales. Después, la herramienta permite ejecutar un conjunto de pruebas sobre los mutantes y detectar los mutantes eliminados (aquellos detectados con alguna prueba). Por desgracia, las pruebas hemos de escribirlas a mano. A la hora de la verdad, la herramienta se comía la primera letra de mis clases y solo era capaz de generar un montón de NullPointerException. Lástima que no sea libre y pueda corregir el código.

Aunque las herramientas que conozco no permitan sacar todo el jugo de la técnica de mutaciones, creo que los fundamentos son muy importantes para cualquier que escriba pruebas para código. Conocer los operadores para crear mutaciones nos va a dar muchas buenas ideas sobre qué pruebas escribir para verificar que nuestro código funciona y siga funcionando en el futuro.

17.1.06

Herramientas libres para pruebas de sistema/aceptación en Java.

Saludos.


Una de las cosas que he estado haciendo aprovechando la tranquilidad de las navidades es ojear herramienta que me permitan hacer
pruebas del sistema/aceptación sobre aplicaciones Java. A continuación os cuento un resumen superficial de mis opiniones.



El problema:


Quiero escribir una prueba que verifique que una aplicación tipo bloc de notas carga correctamente un archivo del sistema. La
aplicación elegida fue, al principio, el Notepad que se incluye como ejemplo en el paquete J2SE. Como me dio problemas con
algunas herramientas, al final hice las pruebas con Stylepad, que también se incluye como ejemplo en J2SE.



Las herramientas:


Lo que buscaba es una herramienta que me permitiera indicar las mismas secuencias que haría una persona que cargara un archivo. Es decir: pulsar en el menú file > open, navegar hasta una carpeta predeterminada, seleccionar en un archivo concreto y pulsar el botón open. Para evitar tener que pelearme con lenguajes o archivos XML, busqué herramientas que me permitan grabar y reproducir todas esas acciones.

Las elegidas fueron 3: Abbot (link) , JFCUnit (link)y Marathon (link).



Abbot:


Abbot sirve tanto para probar componentes de manera aislada como para grabar y reproducir una secuencia de acciones. La herramienta viene con un editor (llamado Costello) muy completo que facilita la tarea de grabar secuencias, construir casos de prueba, y reproducirlas. No fue nada difícil echarla a andar.

Con el editor ejecuté la aplicación y capturé todas las pulsaciones de ratón perfectamente. El editor, además, registró todos los componentes (JMenuBar, JPane, JTextPane, etc. ) implicados en la secuencia. A la hora de añadir asertos, no tuve más que elegir el componente, elegir la propiedad que quería comprobar y el valor al que iba a comparara. Realmente sencillo.

Ahora vienen las cosas malas. Abbot almacena los casos de prueba en un XML bastante complejo, lo que hace difícil hacer pruebas sin grabar/reproducir. Además, el editor siempre se colgaba con bastante rapidez. La descripción de errores es muy poco clara, ya que se limita simplemente a mostrar el texto de la excepción, lo cual no tiene ninguna utilidad para mí que no conocía como estaba hecha la herramienta por dentro.



JFCUnit:


JFCUnit permite realizar pruebas unitarias de interfaces gráficas de usuario y pruebas del sistema. A diferencia de Abbot y Marathon, esta herramienta no trae ningún editor que permita capturar/reproducir. Para grabar y reproducir hay que escribir un par de programas en programa Java, fácil de hacer una vez que se investiga a fondo en la documentación y ejemplos. Además, es necesario modificar el código de la prueba a mano para añadir los asertos. Además, la prueba no fue capaz de grabar mi interacción con el FileDialog para seleccionar el archivo a abrir.

Las pruebas también se almacenan en XML. Aunque la documentación de JFCUnit es muy completa, falta una buena referencia de las etiquetas y sus atributos. algo indispensable ya que no dispone de un editor.



Marathon:


Esta herramienta sólo sirve para pruebas de sistema/aceptación, no permitirnedo escribir pruebas para componentes de manera individual. En esta herramienta las pruebas nos e guardan en XML sino en Python (y se procesan con JPython). Esto hace que el código sea muy compacto, muy legible y que tengamos toda la potencia de Python a nuestra disposición.

Aunque el editor no es tan completo como el editor de Abbot, incluye un menú contextual sobre la aplicación a prueba que permite añadir comprobaciones al mismo tiempo que se graba. Sin embargo también presenta problemas. El más importante es que, al igual que en la herramienta anterior, no se ha capturado la interacción con el diálogo para abrir un archivo. La documentación, aunque muy buena para empezar, se queda muy corta cuando quieres profundizar.


 


Conclusiones:


En una primera aproximación, no creo que ninguna de estas tres sea la herramienta definitiva. La herramienta que más me ha gustado es Marathon, sin duda por trabajar en Python. He encontrado mucho más cómodo entender código en Python que lenguajes propios de etiquetas. Marathon, además, es capaz de ocultar los detalles de la interfaz gráfica mejor que las demás. Esto es muy útil cuando queremos escribir las pruebas antes de tener la interfaz gráfica, o para evitar tener que volver a grabar todas las pruebas por algún cambio en la interfaz.



Estas navidades también estuve investigando algunas herramientas para probar aplicaciones web. En un futuro post, dentro de un mes más o menos, hablaré de ellas. Por supuesto escribiré más mensajes mientras.




 

4.1.06

Probando excepciones con JUnit.

Saludos.

Escribir una prueba con JUnit que compruebe si salta o no una excepción es muy sencillo. Por ejemplo, supongamos que queremos probar un método con la
siguiente cabecera:


public Empleado obtenerEmpleado(String DNI)
throws DNIErroneo



Vamos a escribir, en primer lugar, una prueba que compruebe que cuando le indicamos un DNI correcto, no lanza la excepción:


public void testDNICorrectoNoExcepcion() {
try {
// Nota: es un DNI inventado. Supongamos que es correcto
obtenerEmpleado("18665230F");
} catch (DNIErroneo e) {
fail();
}
}



Como se puede ver, si surge una excepción se ejecuta fail(), lo cuál termina el caso de prueba con error. Si no, termina normalmente.

Ahora vamos a escribir una prueba que verifique lo contrario. Le indicaremos al método un DNI erróneo y verificaremos que nos devuelve una excepción.


public void testDNIErroneoExcepcion() {
try {
// Nota: es un DNI inventado. Supongamos que es correcto
obtenerEmpleado("18665230F");
} catch (DNIErroneo e) {
return;
}
fail();
}



Ahora, fail() se ejecutará si no ha aparecido la la excepción de DNI erróneo. Si aparee cualquier otra excepción, la prueba también fallará.