openxava / documentación / Lección 3: Pruebas automáticas

Curso: 1. Primeros pasos | 2. Modelar con Java | 3. Pruebas automáticas | 4. Herencia | 5. Lógica de negocio básica | 6. Validación avanzada | 7. Refinar el comportamiento predefinido | 8. Comportamiento y lógica de negocio | 9. Referencias y colecciones | A. Arquitectura y filosofía | B. Java Persistence API | C. Anotaciones

Tabla de contenidos

Lección 3: Pruebas automáticas
JUnit
ModuleTestBase para probar módulos
El código para la prueba
Ejecutar las pruebas desde Eclipse
Crear datos de prueba usando JPA
Los métodos setUp() y tearDown()
Crear datos con JPA
Borrar datos con JPA
Filtrar datos desde modo lista en una prueba
Usar instancias de entidad dentro de una prueba
Usar datos ya existentes para probar
Probar colecciones
Dividir la prueba en varios métodos
Verificar valores por defecto
Entrada de datos
Verificar los datos
Suite
Resumen
Las pruebas son la parte más importante del desarrollo de software. No importa cuan bonita, rápida o tecnológicamente avanzada sea tu aplicación, si falla, darás una impresión muy pobre.
Hacer pruebas manuales, es decir, abrir el navegador y ejecutar la aplicación exactamente como lo haría un usuario final, no es viable; porque el problema real no está en el código que acabas de escribir, sino en el código que ya estaba ahí. Normalmente pruebas el código que acabas de escribir, pero no pruebas todo el código que ya existe en tu aplicación. Y sabes muy bien que cuando tocas cualquier parte de tu aplicación puedes romper cualquier otra parte inadvertidamente.
Necesitas poder hacer cualquier cambio en tu código con la tranquilidad de que no vas a romper tu aplicación. Una forma de conseguirlo, es usando pruebas automáticas. Vamos a hacer pruebas automáticas usando JUnit.

JUnit

JUnit es una herramienta muy popular para hacer pruebas automáticas. Esta herramienta está integrada con Eclipse, por tanto no necesitas descargarla para poder usarla. OpenXava extiende las capacidades de JUnit para permitir probar un módulo de OpenXava exactamente de la misma forma que lo haría un usuario final. De hecho, OpenXava usa HtmlUnit, un software que simula un navegador real (incluyendo JavaScript) desde Java. Todo está disponible desde la clase de OpenXava ModuleTestBase, que te permite automatizar las pruebas que tú harías a mano usando un navegador de verdad de una forma simple.
La mejor manera de entender como funcionan las pruebas en OpenXava es verlo en acción.

ModuleTestBase para probar módulos

Para crear una prueba para un módulo de OpenXava extendemos de la clase ModuleTestBase del paquete org.openxava.tests. Esta clase te permite conectar con un módulo OpenXava como un navegador real, y tiene muchos métodos útiles para probar tu módulo. Creemos la prueba para tu módulo Cliente.

El código para la prueba

Crea un paquete nuevo llamado com.tuempresa.facturacion.pruebas y dentro de él una nueva clase llamada PruebaCliente con el siguiente código:
package com.tuempresa.facturacion.pruebas;
 
import org.openxava.tests.*;
 
public class PruebaCliente extends ModuleTestBase { // Ha de extender de ModuleTestBase
 
    public PruebaCliente(String nombrePrueba) {
        super(nombrePrueba, "Facturacion", // Indicamos el nombre de aplicación (Facturacion)
                "Cliente"); // y nombre de módulo (Cliente)
    }
 
    // Los métodos de prueba han de empezar por 'test'
    public void testCrearLeerActualizarBorrar() throws Exception {
        login("admin", "admin"); // Identificación de usuario para acceder al módulo
 
        // Crear
        execute("CRUD.new"); // Pulsa el botón 'Nuevo'
        setValue("numero", "77"); // Teclea 77 como valor para el campo 'numero'
        setValue("nombre", "Cliente JUNIT"); // Pone valor en el campo 'nombre'
        setValue("direccion.viaPublica", "Calle JUNIT"); // Fíjate en la notación del punto
                                                // para acceder al miembro de la referencia
        setValue("direccion.codigoPostal", "77555"); // Etc
        setValue("direccion.municipio", "La ciudad JUNIT"); // Etc
        setValue("direccion.provincia", "La provincia JUNIT"); // Etc
        execute("CRUD.save"); // Pulsa el botón 'Grabar'
        assertNoErrors(); // Verifica que la aplicación no muestra errores
        assertValue("numero", ""); // Verifica que el campo 'numero' está vacío
        assertValue("nombre", ""); // Verifica que el campo 'nombre' está vacío
        assertValue("direccion.viaPublica", ""); // Etc
        assertValue("direccion.codigoPostal", ""); // Etc
        assertValue("direccion.municipio", ""); // Etc
        assertValue("direccion.provincia", ""); // Etc
 
        // Leer
        setValue("numero", "77"); // Pone 77 como valor para el campo 'numero'
        execute("CRUD.refresh"); // Pulsa el botón 'Refrescar'
        assertValue("numero", "77"); // Verifica que el campo 'numero' tiene un 77
        assertValue("nombre", "Cliente JUNIT"); // y 'nombre' tiene 'Cliente JUNIT'
        assertValue("direccion.viaPublica", "Calle JUNIT"); // Etc
        assertValue("direccion.codigoPostal", "77555"); // Etc
        assertValue("direccion.municipio", "La ciudad JUNIT"); // Etc
        assertValue("direccion.provincia", "La provincia JUNIT"); // Etc
 
        // Actualizar
        setValue("nombre", "Cliente JUNIT MODIFICADO"); // Cambia el valor del campo 'nombre'
        execute("CRUD.save"); // Pulsa el botón 'Grabar'
        assertNoErrors(); // Verifica que la aplicación no muestra errores
        assertValue("numero", ""); // Verifica que el campo 'numero' está vacío
        assertValue("nombre", ""); // Verifica que el campo 'nombre' está vacío
 
        // Verifica si se ha modificado
        setValue("numero", "77"); // Pone 77 como valor para el campo 'numero'
        execute("CRUD.refresh"); // Pulsa en el botón 'Refrescar'
        assertValue("numero", "77"); // Verifica que el campo 'numero' tiene un 77
        assertValue("nombre", "Cliente JUNIT MODIFICADO"); // y 'nombre' tiene
                                                        // 'Cliente JUNIT MODIFICADO'
        // Borrar
        execute("CRUD.delete"); // Pulsa en el botón 'Borrar'
        assertMessage("Cliente borrado satisfactoriamente"); // Verifica que el mensaje
                                // 'Cliente borrado satisfactoriamente' se muestra al usuario
    }
 
}
Esta prueba crea un nuevo cliente, lo busca, lo modifica y al final lo borra. Aquí ves como puedes usar métodos como execute() o setValue() para simular las acciones del usuario, y métodos como assertValue(), assertNoErrors() o assertMessage() para verificar el estado de la interfaz de usuario. Tu prueba actúa como las manos y los ojos del usuario:
testing_es010.png
En execute() tienes que especificar el nombre calificado de la acción, esto quiere decir NombreControlador.nombreAccion. ¿Cómo puedes saber el nombre de la acción? Pasea tu ratón sobre el vínculo de la acción, y verás en la barra inferior de tu navegador un código JavaScript que incluye el nombre calificado de la acción:
testing_es020.png
Ahora ya sabes como crear una prueba para probar las operaciones de mantenimiento básicas de un módulo. No es necesario escribir una prueba demasiado exhaustiva al principio. Simplemente prueba las cosas básicas, aquellas cosas que normalmente probarías con un navegador. Tu prueba crecerá de forma natural a medida que tu aplicación crezca y los usuarios vayan encontrando fallos.
Aprendamos como ejecutar tu prueba desde Eclipse.

Ejecutar las pruebas desde Eclipse

JUnit está integrado dentro de Eclipse, por eso ejecutar tus prueba en Eclipse es más fácil que quitarle un caramelo a un niño. Pon el ratón sobre tu clase de prueba, PruebaCliente, y con el botón derecho escoge Run As > JUnit Test:
testing_es030.png
Si la prueba no es satisfactoria la barra sale roja. Puedes probarlo. Edita PruebaCliente y comenta la línea que da valor al campo nombre:
...
setValue("numero", "77");
// setValue("nombre", "Cliente JUNIT"); // Comenta esta línea
setValue("direccion.viaPublica", "Calle JUNIT");
...
Ahora, reejecuta la prueba. Ya que nombre es una propiedad requerida, un mensaje de error será mostrado al usuario, y el objeto no se grabará:
testing_es040.png
El assert culpable es assertNoErrors(), el cual además de fallar muestra en la consola los errores mostrados al usuario. Por eso, en la consola de ejecución de tu prueba verás un mensaje como este:
16-jul-2019 18:03 org.openxava.tests.ModuleTestBase assertNoMessages
SEVERE: Error unexpected: Es ogligado que Nombre en Cliente tenga valor
El problema es claro. El cliente no se ha grabado porque el nombre es obligatorio y éste no se ha especificado.
Has aprendido como se comporta la prueba cuando falla. Ahora, puedes descomentar la línea culpable y volver a ejecutar la prueba para verificar que todo sigue en su sitio.

Crear datos de prueba usando JPA

En tu primera prueba, PruebaCliente, la prueba misma empieza creando los datos que van a ser usados en el resto de la prueba. Este es un buen enfoque, especialmente si quieres probar la entrada de datos también. Pero a veces te interesa probar solo un pequeño caso que falla, o simplemente tu módulo no permite entrada de datos. En cualquier caso puedes crear los datos que necesites para probar usando JPA desde tu prueba.

Los métodos setUp() y tearDown()

Vamos a usar PruebaProducto para aprender como usar JPA para crear datos de prueba. Crearemos algunos productos antes de ejecutar cada prueba y los borraremos después. Veamos el código de PruebaProducto:
package com.tuempresa.facturacion.pruebas;
 
import java.math.*;
import com.tuempresa.facturacion.modelo.*;
import org.openxava.tests.*;
import static org.openxava.jpa.XPersistence.*;
 
public class PruebaProducto extends ModuleTestBase {
 
    private Autor autor; // Declaramos las entidades a crear
    private Categoria categoria; // como miembros de instancia para que
    private Producto producto1; // estén disponibles en todos los métodos de prueba
    private Producto producto2; // y puedan ser borradas al final de cada prueba
 
    public PruebaProducto(String testName) {
        super(testName, "Facturacion", "Producto");
    }
 
    protected void setUp() throws Exception { // setUp() se ejecuta siempre antes de cada prueba
        super.setUp(); // Es necesario porque ModuleTestBase lo usa para inicializarse, JPA se inicializa aquí
        crearProductos(); // Crea los datos usados en las pruebas
    }
 
    protected void tearDown() throws Exception { // tearDown() se ejecuta
                                                 // siempre después de cada prueba
        super.tearDown(); // Necesario, ModuleTestBase cierra recursos aquí
        borrarProductos(); // Se borran los datos usados en las pruebas
    }
 
    public void testBorrarDesdeLista() throws Exception { ... }
 
    public void testSubirFotos() throws Exception { ... }
 
    private void crearProductos() { ... }
 
    private void borrarProductos() { ... }
 
}
Aquí estamos sobrescribiendo los métodos setUp() y tearDown(). Estos métodos son métodos de JUnit que son ejecutados justo antes y después de ejecutar cada método de prueba. Creamos los datos de prueba antes de ejecutar cada prueba, y borramos los datos después de cada prueba. Así, cada prueba puede contar con unos datos concretos para ejecutarse. No importa si otras pruebas borran o modifican datos, o el orden de ejecución de las pruebas. Siempre, al principio de cada método de prueba tenemos todos los datos listos para usar.

Crear datos con JPA

El método crearProductos() es el responsable de crear los datos de prueba usando JPA. Examinémoslo:
private void crearProductos() {
    // Crear objetos Java
    autor = new Autor(); // Se crean objetos de Java convencionales
    autor.setNombre("JUNIT Author"); // Usamos setters como se suele hacer con Java
    categoria = new Categoria();
    categoria.setDescripcion("Categoria JUNIT");
    producto1 = new Producto();
    producto1.setNumero(900000001);
    producto1.setDescripcion("Producto JUNIT 1");
    producto1.setAutor(autor);
    producto1.setCategoria(categoria);
    producto1.setPrecio(new BigDecimal("10"));
    producto2 = new Producto();
    producto2.setNumero(900000002);
    producto2.setDescripcion("Producto JUNIT 2");
    producto2.setAutor(autor);
    producto2.setCategoria(categoria);
    producto2.setPrecio(new BigDecimal("20"));
 
    // Marcar los objetos como persistentes
    getManager().persist(autor); // getManager() es de XPersistence
    getManager().persist(categoria); // persist() marca el objeto como persistente
    getManager().persist(producto1); // para que se grabe en la base de datos
    getManager().persist(producto2);
 
    // Confirma los cambios en la base de datos
    commit(); // commit() es de XPersistence. Graba todos los objetos en la base de datos
              // y confirma la transacción
}
Como puedes ver, primero creas los objetos al estilo convencional de Java. Fíjate que los asignamos a miembros de instancia, así puedes usarlos dentro de la prueba. Entonces, los marcas como persistentes, usando el método persist() del EntityManager de JPA. Para obtener el PersistenceManager solo has de escribir getManager() porque tienes un import estático arriba:
import static org.openxava.jpa.XPersistence.*;
...
getManager().persist(autor);
    // Gracias al static import de XPersistence es lo mismo que
XPersistence.getManager().persist(autor);
...
commit();
    // Gracias al static import de XPersistence es lo mismo que
XPersistence.commit();
Para finalizar, commit() (también de XPersistence) graba todos los objetos a la base de datos y entonces confirma la transacción. Después de eso, los datos ya están en la base de datos listos para ser usados por tu prueba.

Borrar datos con JPA

Después de que se ejecute la prueba borraremos los datos de prueba para dejar la base de datos limpia. Esto se hace en el método borrarProductos():
private void borrarProductos() { // Llamado desde tearDown()
                                 // por tanto ejecutado después de cada prueba
    borrar(producto1, producto2, autor, categoria); // borrar() borra
    commit(); // Confirma los cambios en la base de datos, en este caso borrando datos
}
 
private void borrar(Object ... entidades) { // Usamos argumentos varargs
    for (Object entidad : entidades) { // Iteramos por todos los argumentos
        getManager().remove(getManager().merge(entidad)); // Borrar(1)
    }
}
Es un simple bucle por todas las entidades usadas en la prueba, borrándolas. Para borrar una entidad con JPA has de usar el método remove(), aunque en este caso has de usar el método merge() también (1). Esto es porque no puedes borrar una entidad desasociada (detached entity). Al usar commit() en crearProductos() todas la entidades grabadas pasaron a ser entidades desasociadas, porque continúan siendo objetos Java válidos pero el contexto persistente (persistent context, la unión entre las entidades y la base de datos) se perdió en el commit(), por eso tienes que reasociarlas al nuevo contexto persistente. Este concepto es fácil de entender con el siguiente código:
getManager().persist(autor); // autor está asociado al contexto persistente actual
commit(); // El contexto persistente actual se termina, y autor pasa a estar desasociado
 
getManager().remove(autor); // Falla porque autor está desasociado
 
autor = getManager().merge(autor); // Reasocia autor al contexto actual
getManager().remove(autor); // Funciona
A parte de este curioso detalle sobre el merge(), el código para borrar es bastante sencillo.

Filtrar datos desde modo lista en una prueba

Ahora que ya sabes como crear y borrar datos para las pruebas, examinemos los métodos de prueba para tu módulo Producto. El primero es testBorrarDesdeLista() que selecciona una fila en el modo lista y pulsa en el botón “Borrar seleccionados”. Veamos su código:
public void testBorrarDesdeLista() throws Exception {
    login("admin", "admin");
    setConditionValues("", "JUNIT"); // Establece los valores para filtrar los datos
    setConditionComparators("=", "contains_comparator"); // Pone los comparadores para filtrar los datos
    execute("List.filter"); // Pulsa el botón para filtrar
    assertListRowCount(2); // Verifica que hay 2 filas
    checkRow(1); // Seleccionamos la fila 1 (que resulta ser la segunda)
    execute("CRUD.deleteSelected"); // Pulsa en el botón para borrar
    assertListRowCount(1); // Verifica que ahora solo hay una fila
}
Aquí filtramos en modo lista todos los productos que contienen la palabra “JUNIT” (recuerda que has creado dos de estos en el método crearProductos()), entonces verificamos que hay dos filas, seleccionamos el segundo producto y lo borramos, verificando al final que la lista se queda con un solo producto.
Has aprendido como seleccionar una fila (usando checkRow()) y como verificar el número de filas (usando assertListRowCount()). Quizás la parte más intrincada es usar setConditionValues() y setConditionComparators(). Ambos métodos reciben una cantidad variable de cadenas con valores y comparadores para la condición, tal como se muestra aquí:
testing_es050.png
Los valores son asignados al filtro de la lista secuencialmente (de izquierda a derecha). En este caso hay dos valores, pero puedes usar todos los que necesites. No es necesario que especifiques todos los valores. El método setConditionValues() admite cualquier cadena mientras que setConditionComparators() admite los siguientes valores posibles: contains_comparator, starts_comparator, ends_comparator, not_contains_comparator, empty_comparator, not_empty_comparator, =, <>, >=, <=, >, <, in_comparator, not_in_comparator y range_comparator.

Usar instancias de entidad dentro de una prueba

La prueba que queda, testSubirFotos(), escoge un producto y sube fotos en él. Vamos a usar en la prueba una entidad creada en crearProductos():
public void testSubirFotos() throws Exception { 
	login("admin", "admin");
 
	// Buscar producto1
	execute("CRUD.new");
	setValue("numero", Integer.toString(producto1.getNumero())); // (1)
	execute("CRUD.refresh");
	assertFilesCount("fotos", 0);
 
	// Subir fotos
	uploadFile("fotos", "web/xava/images/add.gif"); // (2)
	uploadFile("fotos", "web/xava/images/attach.gif"); // (2)
 
	// Verificar
	execute("CRUD.new");
	assertFilesCount("fotos", 0);
	setValue("numero", Integer.toString(producto1.getNumero())); // (1)
	execute("CRUD.refresh");
	assertFilesCount("fotos", 2);
	assertFile("fotos", 0, "image");
	assertFile("fotos", 1, "image");
	
	// Quitar fotos
	removeFile("fotos", 1);
	removeFile("fotos", 0);
}
Lo interesante de esta prueba es que para dar valor al número usado para buscar el producto, lo obtenemos de producto1.getNumero() (1). Recuerda que producto1 es una variable de instancia de la prueba a la que se asigna valor en crearProductos(), el cual es llamado desde setUp(), es decir se ejecuta antes de cada prueba. También has aprendido como usar los métodos uploadFile(), assertFileCount(), assertFile() y removeFile() para trabajar con las fotos. Estos métodos funcionan con cualquier propiedad que permita subir archivos (GALERIA_IMAGENES, FOTO, IMAGEN, ARCHIVO, ARCHIVOS, etc). En este caso usamos imágenes incluidas en OpenXava, gifs de web/xava/images (2), pero puedes crear tu propia carpeta con tus propias imágenes para las pruebas.

Ya tienes la prueba para Producto y al mismo tiempo has aprendido como probar usando datos de prueba creados mediante JPA. Ejecútalo, debería salir verde.

Usar datos ya existentes para probar

A veces puedes simplificar la prueba usando una base de datos que contenga los datos necesarios para la prueba. Si no quieres probar la creación de datos desde el módulo, y no borras datos en la prueba, ésta puede ser una buena opción.
Por ejemplo, puedes probar Autor y Categoria con una prueba tan simple como esta:
package com.tuempresa.facturacion.pruebas;
 
import org.openxava.tests.*;
 
public class PruebaAutor extends ModuleTestBase {
 
    public PruebaAutor(String nombrePrueba) {
        super(nombrePrueba, "Facturacion", "Autor");
    }
 
    public void testReadAuthor() throws Exception {
        login("admin", "admin");
        assertValueInList(0, 0, "JAVIER CORCOBADO"); // El primer autor en la
                                                    // lista es JAVIER CORCOBADO
        execute("List.viewDetail", "row=0"); // Pulsamos en la primera fila
        assertValue("nombre", "JAVIER CORCOBADO");
        assertCollectionRowCount("productos", 2); // Tiene 2 productos
        assertValueInCollection("productos", 0, // Fila 0 de productos
                                "numero", "2"); // tiene “2” en la columna “numero”
        assertValueInCollection("productos", 0, "descripcion", "Arco iris de lágrimas");
        assertValueInCollection("productos", 1, "numero", "3");
        assertValueInCollection("productos", 1, "descripcion", "Ritmo de sangre");
    }
 
}
Esta prueba verifica que el primer autor en la lista es “JAVIER CORCOBADO”, recuerda crearlo antes de ejecutar la prueba. Entonces va al detalle y confirma que tiene una colección llamada "productos" con 2 productos: “Arco iris de lágrimas” y “Ritmo de sangre”, antes de ejecutar la prueba créalos y asocialos a "JAVIER CORCOBADO". De paso, has aprendido como usar los métodos assertValueInList(), assertValueInCollection() y assertCollectionRowCount().
Podemos usar la misma técnica para probar el módulo Categoria:
package com.tuempresa.facturacion.pruebas;
 
import org.openxava.tests.*;
 
public class PruebaCategoria extends ModuleTestBase {
 
    public PruebaCategoria(String nombrePrueba) {
        super(nombrePrueba, "Facturacion", "Categoria");
    }
 
    public void testCategoriasEnLista() throws Exception {
        login("admin", "admin");
        assertValueInList(0, 0, "MÚSICA"); // Fila 0 columna 0 tiene “MÚSICA”
        assertValueInList(1, 0, "LIBROS"); // Fila 1 columna 0 tiene “LIBROS”
        assertValueInList(2, 0, "SOFTWARE"); // Fila 2 columna 0 tiene “SOFTWARE”
    }
 
}
En este caso solo verificamos que en la lista las tres primeras categorías son “MÚSICA”, “LIBROS” y “SOFTWARE”. Acuerdate de crearlos antes de ejecutar esta prueba.
Puedes ver como la técnica de usar datos preexistentes de una base de datos de prueba te permite crear pruebas más simples. Empezar con una prueba simple e ir complicándolo bajo demanda es una buena idea. Recuerda añadir los datos correspondientes usando los módulos antes de ejecutar estas pruebas.

Probar colecciones

Es el momento de enfrentarnos a la prueba del módulo principal de tu aplicación, PruebaFactura. Por ahora la funcionalidad del módulo Factura es limitada, solo puedes añadir, borrar y modificar facturas. Aun así, esta es la prueba más extensa; además contiene una colección, por tanto aprenderás como probar las colecciones.

Dividir la prueba en varios métodos

La prueba para crear una factura está dividida en varios métodos:
package com.tuempresa.facturacion.pruebas;
 
import java.time.*;
import java.time.format.*;
import javax.persistence.*; import org.openxava.tests.*; import static org.openxava.jpa.XPersistence.*; // Para usar JPA public class PruebaFactura extends ModuleTestBase { private String numero; // Para almacenar el número de la factura que probamos public PruebaFactura(String nombrePrueba) { super(nombrePrueba, "Facturacion", "Factura"); } public void testCrear() throws Exception { // El método de prueba login("admin", "admin"); verificarValoresDefecto(); escogerCliente(); anyadirDetalles(); ponerOtrasPropiedades(); grabar(); verificarCreado(); borrar(); } private void verificarValoresDefecto() throws Exception { … } private void escogerCliente() throws Exception { … } private void anyadirDetalles() throws Exception { … } private void ponerOtrasPropiedades() throws Exception { … } private void grabar() throws Exception { … } private void verificarCreado() throws Exception { … } private void borrar() throws Exception { … } private String getAnyoActual() { … } private String getFechaActual() { … } private String getNumero() { … } }
El único método de prueba de esta clase es testCrear(), pero dado que es bastante extenso, es mejor dividirlo en varios métodos más pequeños. De hecho, es una buena práctica de orientación a objetos escribir métodos cortos.
Ya que el método es corto puedes ver con un solo golpe de vista que es lo que hace. En este caso verifica los valores por defecto para una factura nueva, escoge un cliente, añade las líneas de detalle, añade otras propiedades, graba la factura, verifica que ha sido guardada correctamente y al final la borra. Entremos en los detalles de cada uno de estos pasos.

Verificar valores por defecto

Lo primero es verificar que los valores por defecto para una factura nueva son calculados correctamente. Esto se hace en el método verificarValoresDefecto():
private void verificarValoresDefecto() throws Exception {
    execute("CRUD.new");
    assertValue("anyo", getAnyoActual());
    assertValue("numero", getNumero());
    assertValue("fecha", getFechaActual());
}
Cuando el usuario pulsa en “Nuevo”, los campos año, número y fecha tienen que rellenarse con datos válidos. El método verificarValoresDefecto() precisamente comprueba esto. Usa varios métodos de utilidad para calcular los valores esperados:
private String getAnyoActual() { // Año actual en formato cadena
    return Integer.toString(LocalDate.now().getYear()); // La forma
                                            // típica de hacerlo con Java
}
 
private String getFechaActual() { // Fecha actual como una cadena
    return LocalDate.now().format( // La forma típica de hacerlo con Java
        DateTimeFormatter.ofPattern("dd/MM/yyyy"));
}
 
private String getNumero() { // El número de factura para una factura nueva
    if (numero == null) { // Usamos inicialización vaga
        Query query = getManager(). // Una consulta JPA para obtener el último número
                createQuery("select max(f.numero) from Factura f where f.anyo = :anyo");
        query.setParameter("anyo", LocalDate.now().getYear());  
        Integer ultimoNumero = (Integer) query.getSingleResult();
        if (ultimoNumero == null) ultimoNumero = 0;
        numero = Integer.toString(ultimoNumero + 1); // Añadimos 1 al
                                                     // último número de factura
    }
    return numero;
}
Los métodos getAnyoActual() y getFechaActual() usan técnicas clásicas de Java para formatear la fecha como una cadena.
El método getNumero() es un poco más complejo: usa JPA para calcular el último número de factura del año en curso y después devuelve este valor más uno. Dado que acceder a la base de datos es más pesado que un simple cálculo Java, usamos una inicialización vaga. Una inicialización vaga retrasa el cálculo hasta la primera vez que se necesita y después lo almacena para futuros usos. Esto lo hacemos guardando el valor en el campo numero.

Entrada de datos

Ahora es el momento de escogerCliente() de la factura:
private void escogerCliente() throws Exception {
    setValue("cliente.numero", "1");
    assertValue("cliente.nombre", "JAVIER PANIZA"); // El cliente 1 debe de existir en la DB
}
Al introducir el número de cliente el nombre del cliente se rellena con un valor apropiado. La prueba confía en que el cliente 1 con nombre "JAVIER PANIZA" existe, deberías crearlo antes de ejecutar la prueba. Ya hemos asociamos el cliente 1 con la factura actual.
Y ahora viene la parte más peliaguda de la prueba: añadir las líneas de detalle:
private void anyadirDetalles() throws Exception {
    assertCollectionRowCount("detalles", 0); // La colección esta vacía
 
    // Añadir una línea de detalle
    setValueInCollection("detalles", 0, // 0 es la primera fila
        "producto.numero", "1");
    assertValueInCollection("detalles", 0,
        "producto.descripcion", "Peopleware: Productive Projects and Teams");
    setValueInCollection("detalles", 0, "cantidad", "2");
 
    // Añadir otro detalle
    setValueInCollection("detalles", 1, "producto.numero", "2");
    assertValueInCollection("detalles", 1, "producto.descripcion", "Arco iris de lágrimas");
    setValueInCollection("detalles", 1, "cantidad", "1");
 
    assertCollectionRowCount("detalles", 2); // Ahora tenemos 2 filas
}
Probar una colección es exactamente igual que probar cualquier otra parte de tu aplicación, solo has de seguir los mismos pasos que un usuario haría con el navegador. Tienes métodos como setValueInCollection(), assertValueInCollection() o assertCollectionRowCount() para trabajar con colecciones. Nota que estos métodos tienen el nombre de la colección como primer argumento y algunos reciben el número de fila siendo el 0 la primera fila. Recuerda añadir a la base de datos los productos 1 y 2 con sus correspondientes descripciones antes de ejecutar esta prueba.
Ahora que tenemos los detalles añadidos, vamos a llenar los datos restantes y grabar la factura. Los datos restantes se establecen en el método ponerOtrasPropiedades():
private void ponerOtrasPropiedades() throws Exception {
    setValue("observaciones", "Esto es una prueba JUNIT");
}
Aquí ponemos valor al campo observaciones. Y ahora estamos listos para grabar la factura:
private void grabar() throws Exception {
    execute("CRUD.save");
    assertNoErrors();
    assertValue("cliente.numero", "");
    assertCollectionRowCount("detalles", 0);
    assertValue("observaciones", "");
}
Simplemente pulsa en “Grabar”, entonces verifica que no ha habido errores y la vista se ha limpiado.

Verificar los datos

Ahora, buscamos la factura recién creada para verificar que ha sido grabada correctamente. Esto se hace en el método verificarCreado():
private void verificarCreado() throws Exception {
    setValue("anyo", getAnyoActual()); // El año actual en el campo año
    setValue("numero", getNumero()); // El número de la factura usada en la prueba
    execute("CRUD.refresh"); // Carga la factura desde la base de datos
 
    // En el resto de la prueba confirmamos que los valores son los correctos
    assertValue("anyo", getAnyoActual());
    assertValue("numero", getNumero());
    assertValue("fecha", getFechaActual());
    assertValue("cliente.numero", "1");
    assertValue("cliente.nombre", "JAVIER PANIZA");
    assertCollectionRowCount("detalles", 2);
 
    // Fila 0
    assertValueInCollection("detalles", 0, "producto.numero", "1");
    assertValueInCollection("detalles", 0, "producto.descripcion",
        "Peopleware: Productive Projects and Teams");
    assertValueInCollection("detalles", 0, "cantidad", "2");
 
    // Fila 1
    assertValueInCollection("detalles", 1, "producto.numero", "2");
    assertValueInCollection("detalles", 1, "producto.descripcion",
        "Arco iris de lágrimas");
    assertValueInCollection("detalles", 1, "cantidad", "1");
    assertValue("observaciones", "Esto es una prueba JUNIT");
}
Después de buscar la factura creada verificamos que los valores que hemos grabado están ahí. Si la prueba llega a este punto tu módulo Factura funciona bien. Solo nos queda borrar la factura creada para que la prueba se pueda ejecutar la siguiente vez. Hacemos esto en el método borrar():
private void borrar() throws Exception {
    execute("CRUD.delete");
    assertNoErrors();
}
Simplemente presiona en “Borrar” y verifica que no se han producido errores.

¡Enhorabuena! Has completado tu PruebaFactura. Ya puedes ejecutarla, debería salir verde, si no comprueba que los datos en la base de datos están bien, puede que tengas que añadir los productos, cliente, etc. correspondientes.

Suite

Tienes 5 casos de prueba que velan por tu código, preservando la calidad de tu aplicación. Cuando termines alguna mejora o corrección en tu aplicación ejecuta todas tus pruebas unitarias para verificar que la funcionalidad existente no se ha roto.
Tradicionalmente, para ejecutar todos las pruebas de tu aplicación deberías crear una suite de pruebas, y ejecutarla. Una suite de pruebas es una clase que agrega todas tus pruebas JUnit para que puedas ejecutarlas todas de un golpe. Afortunadamente, si trabajas con Eclipse no necesitas escribir una clase de suite, Eclipse te permite ejecutar todas las pruebas de tu aplicación automáticamente:
testing_es060.png
Es decir, si ejecutas Run As > JUnit Test en el proyecto, se ejecutarán todas sus pruebas JUnit.

Resumen

Has automatizado las pruebas de toda la funcionalidad actual de tu aplicación. Puede parecer que este código de prueba es mucho más largo y aburrido que el código real de la aplicación. Pero recuerda, el código de prueba es el tesoro más valioso que tienes. Quizás ahora no me creas, pero trata de hacer pruebas JUnit y una vez te hayan salvado la vida, ya no podrás desarrollar sin pruebas automáticas nunca más.
¿Qué probar? No hagas pruebas exhaustivas al principio. Es mejor probar poco que no probar nada. Si tratas de hacer pruebas muy exhaustivas acabarás no haciendo pruebas en absoluto. Empieza haciendo algunas pruebas JUnit para tu código, y con cada nueva característica o nuevo arreglo añade nuevas pruebas. Al final, tendrás una suite de pruebas muy completa. En resumen, prueba poco, pero prueba siempre.
Sí, hacer pruebas automáticas es una tarea continua. Y para predicar con el ejemplo a partir de ahora escribiremos todas las pruebas para el código que desarrollemos en el resto del libro. De esta manera aprenderás más trucos sobre las pruebas JUnit en las siguientes lecciones.

Descargar código fuente de esta lección

¿Problemas con la lección? Pregunta en el foro ¿Ha ido bien? Ve a la lección 4