openxava / documentación / Lección 4: Herencia

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 4: Herencia
Heredar de una superclase mapeada
Herencia de entidades
Nueva entidad Pedido
DocumentoComercial como entidad abstracta
Factura refactorizada para usar herencia
Crear Pedido usando herencia
Convención de nombres y herencia
Asociar Pedido con Factura
Herencia de vistas
El atributo extendsView
Vista para Factura usando herencia
Vista para Pedido usando herencia
Usar @ReferenceView y @CollectionView para refinar vistas
Herencia en las pruebas JUnit
Crear una prueba de módulo abstracta
Prueba base abstracta para crear pruebas de módulo concretas
Añadir nuevas pruebas a las pruebas de módulo extendidas
Resumen
La herencia es una forma práctica de reutilizar el código en el mundo de la orientación a objetos. Usar herencia con JPA y OpenXava es tan fácil como hacerlo con puro Java. Vamos a usar la herencia para quitar el código repetitivo y aburrido, como la definición de los UUID. También, añadiremos una nueva entidad, Pedido, y usaremos la herencia para hacerlo con muy poco código. Además, aprenderás cuán práctico es usar herencia incluso para código de pruebas.

Heredar de una superclase mapeada

Las clases Autor, Categoria y Factura tienen algo de código en común. Este código es la definición del campo oid:
@Id @GeneratedValue(generator="system-uuid") @Hidden
@GenericGenerator(name="system-uuid", strategy="uuid")
@Column(length=32)
private String oid;
 
public String getOid() {
    return oid;
}
 
public void setOid(String oid) {
    this.oid = oid;
}
Este código es exactamente el mismo para todas estas clases. Ya sabes que copiar y pegar es un pecado mortal, por eso tenemos que buscar una forma de quitar este código repetido y así evitar ir al infierno.
Una solución elegante en este caso es usar herencia. JPA permite varias formas de herencia. Una de ellas es heredar de una superclase mapeada. Una superclase mapeada es una clase Java con anotaciones de mapeo JPA, pero no es una entidad en sí. Su único objetivo es ser usada como clase base para definir entidades. Usemos una, y verás su utilidad rápidamente.
Primero, movemos el código común a una clase marcada como @MappedSuperclass. La llamamos Identificable:
package com.tuempresa.facturacion.modelo;
 
import javax.persistence.*;
import org.hibernate.annotations.*;
import org.openxava.annotations.*;
 
@MappedSuperclass // Marcada como una superclase mapeada en vez de como una entidad
public class Identificable {
 
    @Id @GeneratedValue(generator="system-uuid") @Hidden
    @GenericGenerator(name="system-uuid", strategy="uuid")
    @Column(length=32)
    private String oid; // La definición de propiedad incluye anotaciones de OpenXava y JPA
 
    public String getOid() {
        return oid;
    }
 
    public void setOid(String oid) {
        this.oid = oid;
    }
 
}
Ahora puedes definir las entidades Autor, Categoria y Factura de una manera más sucinta. Para ver un ejemplo aquí tienes el nuevo código para Categoria:
package com.tuempresa.facturacion.modelo;
 
import javax.persistence.*;
 
@Entity
public class Categoria extends Identificable { // Extiende de Identificable
                        // por tanto no necesita tener una propiedad id
    @Column(length=50)
    private String descripcion;
 
    public String getDescripcion() {
        return descripcion;
    }
 
    public void setDescripcion(String descripcion) {
        this.descripcion = descripcion;
    }
 
}
La refactorización es extremadamente simple. Categoria ahora desciende de Identificable y hemos quitado el campo oid y los métodos getOid() y setOid(). De esta forma, no sólo tu código es más corto, sino también más elegante, porque estás declarando tu clase como identificable (el qué, no el cómo), y has quitado de tu clase de negocio un código que era un tanto técnico.
Ahora, puedes aplicar esta misma refactorización a las entidades Autor y Factura. Además, a partir de ahora extenderás la mayoría de tus entidades de la superclase mapeada Identificable.
Hemos creado nuestra propia clase Identificable para ver las ventajas de usar superclases mapeadas, sin embargo OpenXava te provee una clase Identifiable lista para usar que puedes encontrar en el paquete org.openxava.model. Por tanto, en tu próximo proyecto no has de escribir la clase Identificable otra vez, simplemente usa la incluida en OpenXava.
Has aprendido, pues, que una superclase mapeada es una clase normal y corriente con anotaciones de mapeo JPA que puedes usar como clase base para tus entidades. También has aprendido como usar una superclase mapeada para simplificar tu código.

Herencia de entidades

Una entidad puede heredar de otra entidad. Esta herencia de entidades es una herramienta muy útil para simplificar tu modelo. Vamos a usarla para añadir una nueva entidad, Pedido, a tu aplicación Facturacion.

Nueva entidad Pedido

Queremos añadir un nuevo concepto a la aplicación Facturacion: pedido. Mientras que una factura es algo que quieres cobrar a tu cliente, un pedido es algo que tu cliente te ha solicitado. Estos dos conceptos están fuertemente unidos, porque cobrarás por las cosas que tu cliente te ha pedido y tú le has servido.
Sería interesante poder tratar pedidos en tu aplicación y asociar estos pedidos con sus correspondientes facturas. Tal como muestra el siguiente diagrama UML:
inheritance_es010.png
Y con código Java:
@Entity
public class Factura {
 
    @OneToMany(mappedBy="factura")
    private Collection<Pedido> pedidos;
    ...
}
 
@Entity
public class Pedido {
 
    @ManyToOne // Sin carga vaga (1)
    private Factura factura;
    ...
}
Es decir, una factura tiene varios pedidos y un pedido puede referenciar a una factura. Fíjate como no usamos inicialización vaga (lazy fetching) para la referencia factura (1), esto es por un bug de Hibernate cuando la referencia contiene la relación bidireccional (es decir, es la referencia declarada en el atributo mappedBy del @OneToMany).
¿Cómo es Pedido? Bien, tiene un cliente, unas líneas de detalle con producto y cantidad, un año y un número. Algo así como esto:
inheritance_es020.png
Curiosamente, este diagrama UML es idéntico al diagrama de Factura. Es decir, para crear tu entidad Pedido puedes copiar y pegar la clase Factura, y asunto zanjado. Pero, ¡espera un momento! ¿“Copiar y pegar” no era un pecado mortal? Hemos de encontrar una forma de reutilizar Factura para Pedido.

DocumentoComercial como entidad abstracta

Una manera práctica de reutilizar el código de Factura para Pedido es usando herencia, además es una excusa perfecta para aprender lo fácil que es usar la herencia con JPA y OpenXava.
En la mayoría de las culturas orientadas a objetos has de observar el precepto sagrado es un. Esto significa que no podemos hacer que Factura herede de Pedido, porque una Factura no es un Pedido, y por la misma regla no podemos hacer que Pedido descienda de Factura. La solución para este caso es crear una clase base para ambos, Pedido y Factura. Llamaremos a esta clase DocumentoComercial.
Aquí tienes el diagrama UML para DocumentoComercial:
inheritance_es030.png
Y aquí tienes la misma idea expresada con Java:
public class DocumentoComercial { ... }
public class Pedido extends DocumentoComercial { ... }
public class Factura extends DocumentoComercial { ... }
Empecemos a refactorizar el código actual. Primero, renombra (usando Refactor > Rename) Factura como DocumentoComercial. Después, edita el código de DocumentoComercial para declararla como una clase abstracta, así:
abstract public class DocumentoComercial // Añadimos el modificador abstract
Queremos crear instancias de Factura y Pedido, pero no queremos crear instancias de DocumentoComercial directamente, por eso la declaramos abstracta.

Factura refactorizada para usar herencia

Ahora, has de crear la entidad Factura extendiéndola de DocumentoComercial. Puedes ver el nuevo código de Factura:
package com.tuempresa.facturacion.modelo;
 
import javax.persistence.*;
 
@Entity
public class Factura extends DocumentoComercial {
 
}
Factura tiene ahora un código bastante breve, de hecho el cuerpo de la clase está, por ahora, vacío.
Este nuevo código necesita un esquema de base de datos ligeramente diferente, ahora las facturas y los pedidos se almacenarán en la misma tabla (la tabla DocumentoComercial) usando una columna discriminador. Por tanto has de borrar las viejas tablas ejecutando las siguientes sentencias SQL:
DROP TABLE FACTURACION.FACTURA_DETALLES;
DROP TABLE FACTURACION.FACTURA;
Para ejecutar estas sentencias SQL, primero asegurate de que tu aplicación se esté ejecutando, después ve a la clase DBManager en Facturacion/src/_run, y con el botón derecho del ratón escoge Run As > Java Application:
inheritance_es040.png
Después:
inheritance_es050.png
Ya puedes ejecutar el módulo Factura y verlo funcionando en tu navegador. Lanza también PruebaFactura. Tiene que salirte verde.

Crear Pedido usando herencia

Gracias a DocumentoComercial el código para Pedido es más sencillo que el mecanismo de un sonajero:
package com.tuempresa.facturacion.modelo;
 
import javax.persistence.*;
 
@Entity
public class Pedido extends DocumentoComercial {
 
}
Después de escribir la clase Pedido, aunque de momento esté vacía, ya puedes usar el módulo Pedido desde tu navegador. Sí, a partir de ahora crear una nueva entidad con una estructura parecida a Factura, es decir cualquier entidad para un documento comercial, es simple y rápido. Un buen uso de la herencia es una forma elegante de tener un código más simple.
El módulo Pedido funciona perfectamente, pero tiene un pequeño problema. El nuevo número de pedido lo calcula a partir del último número de factura, no de pedido. Esto es así porque el calculador para el siguiente número lee de la entidad Factura. Un solución obvia es mover la definición de la propiedad numero de DocumentoComercial a Factura y Pedido. Aunque, no lo vamos a hacer así, porque en la siguiente lección refinaremos la forma de calcular el número de documento, de momento simplemente haremos un pequeño ajuste en el código actual para que no falle. Edita la clase CalculadorSiguienteNumeroParaAnyo y en la consulta cambia “Factura” por “DocumentoComercial”, dejando el método calculate(), así:
public Object calculate() throws Exception {
    Query query = XPersistence.getManager().createQuery(
        "select max(f.numero) from " +
        "DocumentoComercial f " + // DocumentoComercial en vez de Factura
        "where f.anyo = :anyo");
    query.setParameter("anyo", anyo);
    Integer ultimoNumero = (Integer) query.getSingleResult();
    return ultimoNumero == null?1:ultimoNumero + 1;
}
Ahora buscamos el número máximo de cualquier tipo de documento comercial del año para calcular el nuevo número, por lo tanto la numeración es compartida para todos los tipos de documentos. Esto lo mejoraremos en la siguiente lección para separar la numeración para facturas y pedidos; y para tener un mejor soporte de entornos multiusuario.

Convención de nombres y herencia

Fíjate que no has necesitado cambiar el nombre de ninguna propiedad de Factura para hacer la refactorización. Esto es por el siguiente principio práctico: No uses el nombre de clase en los nombres de miembro, por ejemplo, dentro de una clase Cuenta no uses la palabra “Cuenta” en ningún método o propiedad:
public class Cuenta { // No usaremos Cuenta en los nombres de los miembros
 
    private int numeroCuenta; // Mal, porque usa “cuenta”
    private int numero; // Bien, no usa “cuenta”
 
    public void cancelarCuenta() { } // Mal, porque usa “Cuenta”
    public void cancelar() { } // Bien, no usa “cuenta”
    ...
}
Usando esta nomenclatura puedes refactorizar la clase Cuenta en una jerarquía sin renombrar sus miembros, y además puedes escribir código polimórfico con Cuenta.

Asociar Pedido con Factura

Hasta ahora, Pedido y Factura son exactamente iguales. Vamos a hacerles sus primeras extensiones, que va a ser asociar Pedido con Factura, como se muestra en el diagrama:
inheritance_es010.png
Solo necesitas añadir una referencia desde Pedido a Factura:
package com.tuempresa.facturacion.modelo;
 
import javax.persistence.*;
 
@Entity
public class Pedido extends DocumentoComercial {
 
    @ManyToOne
    private Factura factura; // Referencia a factura añadida
 
    public Factura getFactura() {
        return factura;
    }
 
    public void setFactura(Factura factura) {
        this.factura = factura;
    }
 
}
Por otra parte en Factura añadimos una colección de entidades Pedido:
package com.tuempresa.facturacion.modelo;
 
import java.util.*; // Añade este import para usar Collection
import javax.persistence.*;
 
@Entity
public class Factura extends DocumentoComercial {
 
    @OneToMany(mappedBy="factura")
    private Collection<Pedido> pedidos; // Colección de entidades Pedido añadida
 
    public Collection<Pedido> getPedidos() {
        return pedidos;
    }
 
    public void setPedidos(Collection<Pedido> pedidos) {
        this.pedidos = pedidos;
    }
 
}
Después de escribir este código tan simple, ya puedes probar estas, recién creadas, relaciones. Reinicia tu aplicación y abre tu navegador y explora los módulos Pedido y Factura. Fíjate como al final de la interfaz de usuario de Pedido tienes una referencia a Factura. El usuario puede usar esta referencia para asociar una factura al pedido actual. Por otra parte, si exploras el módulo Factura, verás una colección de pedidos al final. El usuario puede usarla para añadir pedidos a la factura actual.
Intenta añadir pedidos a la factura, y asociar una factura a un pedido. Funciona, aunque la interfaz de usuario es un poco fea, de momento.

Herencia de vistas

La herencia no solo sirve para reutilizar código Java y mapeos, sino también para reutilizar la definición de la interfaz de usuario, las definiciones @View. Esta sección muestra como funciona la herencia de vistas.

El atributo extendsView

Tanto Pedido como Factura usan una interfaz de usuario generada por defecto con todos sus miembros uno por cada línea. Nota como la @View que hemos declarado en DocumentoComercial no se ha heredado. Es decir, si no defines una vista para la entidad se genera una por defecto, la @View de la entidad padre no se usa. Como se muestra aquí:
@View(members = "a, b, c;") // Esta vista se usa para visualizar Padre, pero no para Hijo
public class Padre { ... }
 
public class Hijo extends Padre { ... } // Hijo se visualiza usando la vista
                                        // generada automáticamente, no la vista de Padre
Generalmente la vista de la entidad padre “tal cual” no es demasiado útil porque no contiene todas las propiedades de la entidad actual. Por tanto este comportamiento suele venir bien como comportamiento por defecto.
Aunque, en una entidad no trivial necesitas refinar la interfaz de usuario y quizás sea útil heredar (en lugar de copiar y pegar) la vista del padre. Puedes hacer esto usando el atributo extendsView en @View:
@View(members = "a, b, c;") // Esta vista sin nombre es la vista DEFAULT
public class Padre { ... }
 
@View(name="A" members = "d", // Añade d a la vista heredada
    extendsView = "super.DEFAULT") // Extienda la vista por defecto de Padre
@View(name="B" members = "a, b, c; d") // La vista B es igual a la vista A
public class Hijo extends Padre { ... } // Hijo se visualiza usando la vista
                                        // generada automáticamente, no la vista de Padre
Usando extendsView los miembros a mostrar serán aquellos en la vista que extendemos más aquellos en members de la actual.
Vamos a usar esta característica para definir las vistas para DocumentoComercial, Pedido y Factura.

Vista para Factura usando herencia

Dado que la @View de DocumentoComercial no se hereda, la interfaz de usuario actual para Factura es bastante fea: todos los miembros, uno por línea. Vamos a definir una interfaz de usuario mejor. Una vista parecida a la que ya teníamos, pero añadiendo una pestaña para pedidos, así:
inheritance_es060.png
Nota que ponemos todos los miembros de la parte de DocumentoComercial de Factura en la cabecera y la primera pestaña (datos), y la colección de pedidos en la otra pestaña. El siguiente código muestra la forma de definir esto sin herencia.
@View( members=
    "anyo, numero, fecha;" +
    "datos {" +
        "cliente;" +
        "detalles;" +
        "observaciones" +
    "}" +
    "pedidos { pedidos } "
)
Puedes notar como todos los miembros, excepto la parte de pedidos, son comunes para todos los DocumentoComercial. Por lo tanto, vamos a mover esta parte común a DocumentoComercial y redefinir la vista usando herencia.
Quita el viejo @View de DocumentoComercial, y escribe esto:
@View(members=
    "anyo, numero, fecha," + // Los miembros para la cabecera en una línea
    "datos {" + // Una pestaña 'datos' para los datos principales del documento
        "cliente;" +
        "detalles;" +
        "observaciones" +
    "}"
)
abstract public class DocumentoComercial extends Identifiable {
Esta vista indica como distribuir los datos comunes para todos los documentos comerciales. Ahora podemos redefinir la vista de Factura a partir de esta:
import org.openxava.annotations.*; // Añade este import para usar @View
 
@Entity
@View(extendsView="super.DEFAULT", // Extiende de la vista de DocumentoComercial
    members="pedidos { pedidos }" // Añadimos pedidos dentro de una pestaña
)
public class Factura extends DocumentoComercial {
De esta forma declarar la vista para Factura es más corto, es más, la distribución común para Pedido, Factura y todos los demás posibles DocumentoComercial está en un único sitio, por tanto, si añades una nueva propiedad a DocumentoComercial solo has de tocar la vista de DocumentoComercial.

Vista para Pedido usando herencia

Ahora que tienes una vista adecuada para DocumentoComercial, declarar una vista para Pedido es lo más fácil del mundo. La vista que queremos contiene toda la información del pedido, y su factura asociada en otra pestaña:
inheritance_es070.png
Para obtener este resultado, puedes definir la vista de Pedido extendiendo la vista por defecto de DocumentoComercial, y añadiendo la referencia a factura, así:
import org.openxava.annotations.*; // Añade este import para usar @View
 
@Entity
@View(extendsView="super.DEFAULT", // Extiende de la vista de DocumentoComercial
    members="factura { factura } " // Añadimos factura dentro de una pestaña
)
public class Pedido extends DocumentoComercial {
Con esto conseguimos toda la información de DocumentoComercial más una pestaña con la factura.

Usar @ReferenceView y @CollectionView para refinar vistas

Queremos que cuando un pedido sea visualizado desde la interfaz de usuario de Factura la vista a usar sea simple, sin cliente ni factura, porque estos datos son redundantes en este caso:
inheritance_es080.png
Para obtener este resultado define una vista más simple en Pedido:
@View( extendsView="super.DEFAULT", // La vista por defecto
    members="factura { factura } "
)
@View( name="SinClienteNiFactura", // Una vista llamada SinClienteNiFactura
    members=                       // que no incluye cliente ni factura
        "anyo, numero, fecha;" +   // Ideal para ser usada desde Factura
        "detalles;" +
        "observaciones"
)
public class Pedido extends DocumentoComercial {
Esta nueva vista definida en Pedido llamada SinClienteNiFactura puede ser referenciada desde Factura para visualizar elementos individuales de la colección pedidos usando @CollectionView:
@OneToMany(mappedBy="factura")
@CollectionView("SinClienteNiFactura") // Esta vista se usa para visualizar pedidos
private Collection<Pedido> pedidos;
Y tan solo con este código la colección pedidos usará una vista más apropiada de Factura para visualizar elementos individuales.
Además, queremos que desde la interfaz de usuario de Pedido la factura no muestre el cliente y los pedidos, porque son datos redundantes en este caso. Para conseguirlo, vamos a definir una vista más simple en Factura:
@View( extendsView="super.DEFAULT", // La vista por defecto
    members="pedidos { pedidos } "
)
@View( name="SinClienteNiPedidos", // Una vista llamada SinClienteNiPedidos
    members=                       // que no incluye cliente ni pedidos
        "anyo, numero, fecha;" +   // Ideal para usarse desde Pedido
        "detalles;" +
        "observaciones"
)
public class Factura extends DocumentoComercial {
Esta nueva vista definida en Factura llamada SinClienteNiPedidos puede ser referenciada desde Pedido para visualizar la referencia a Factura usando @ReferenceView:
public class Pedido extends DocumentoComercial {
 
    @ManyToOne
    @ReferenceView("SinClienteNiPedidos") // Esta vista se usa para visualizar factura
    private Factura factura;
 
    ...
 
}
Ahora la referencia factura será visualizada desde Pedido sin incluir el cliente y los pedidos, por lo que tendrás la interfaz de usuario más simple:
inheritance_es090.png

Herencia en las pruebas JUnit

Factura ha sido refactorizada para usar herencia, y también hemos usado herencia para añadir una nueva entidad, Pedido. Además, esta entidad Pedido tiene relación con Factura. Lo cual es una nueva funcionalidad, por ende has de probar todas estas nuevas características.
Dado que Factura y Pedido tienen bastantes cosas en común (la parte de DocumentoComercial) podemos refactorizar las pruebas para usar herencia, y así eludir el dañino “copiar y pegar” también en tu código de prueba.

Crear una prueba de módulo abstracta

Si examinas la prueba para crear una factura, en el método testCrear() de PruebaFactura. Puedes notar que probar la creación de una factura es exactamente igual que probar la creación de un pedido. Porque ambos tienen año, número, fecha, cliente, detalles y observaciones. Por tanto, aquí la herencia es una buena herramienta para la reutilización de código.
Vamos a renombrar PruebaFactura como PruebaDocumentoComercial, y entonces crearemos PruebaFactura y PruebaPedido a partir de él. Éste es el diagrama UML de esta idea:
inheritance_es100.png
Primero renombra tu actual clase PruebaFactura a PruebaDocumentoComercial, y después haz los cambios indicados en el siguiente código:
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.*; 
 
abstract public class PruebaDocumentoComercial // Añade abstract a la definición de clase
    extends ModuleTestBase {
 
    private String numero;
 
    public PruebaDocumentoComercial(
        String nombrePrueba,
        String nombreModulo) // nombreModulo añadido como argumento en el constructor
    {
        super(nombrePrueba, "Facturacion", nombreModulo); // nombreModulo en vez de "Factura"
    }
 
    public void testCrear() throws Exception { … } // Como el original
 
    private void anyadirDetalles() throws Exception { … } // Como el original
 
    private String getNumero() {
        if (numero == null) {
            Query query = getManager().
                createQuery(
                    "select max(f.numero) "
                    + "from DocumentoComercial f " // Factura cambiada por DocumentoComercial
                    + "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);
        }
        return numero;
    }
 
    private void borrar() throws Exception { … } // Como original
 
    private void verificarCreado() throws Exception { … } // Como original
 
    private void grabar() throws Exception { … } // Como original
 
    private void ponerOtrasPropiedades() throws Exception { … } // Como original
 
    private void escogerCliente() throws Exception { … } // Como original
 
    private void verificarValoresDefecto() throws Exception { … } // Como original
 
    private String getAnyoActual() { … } // Como original
 
    private String getFechaActual() { … } // Como original
 
}
Como ves has tenido que hacer unos pocos cambios para adaptar PruebaDocumentoComercial. Primero, la has declarado abstracta, de esta forma esta clase no es ejecutada por Eclipse como una prueba JUnit, es solo válida como clase base para crear pruebas, pero ella misma no es una prueba.
Otro cambio importante nos lo encontramos en el constructor, donde ahora tienes nombreModulo en vez de “Factura”, así puedes usar esta prueba para Pedido, Factura o cualquier otro módulo que quieras. El otro cambio es un simple detalle: has de cambiar “Factura” por “DocumentoComercial” en la consulta para obtener el siguiente número.
Ahora ya tienes una clase base lista para crear los módulos de prueba para Pedido y Factura. Hagámoslo sin más dilación.

Prueba base abstracta para crear pruebas de módulo concretas

Crear tu primera versión para PruebaFactura y PruebaPedido es simplemente extender de PruebaDocumentoComercial. Nada más. Mira el código de PruebaFactura:
package com.tuempresa.facturacion.pruebas;
 
public class PruebaFactura extends PruebaDocumentoComercial {
 
    public PruebaFactura(String nombrePrueba) {
        super(nombrePrueba, "Factura");
    }
 
}
Y PruebaPedido:
package com.tuempresa.facturacion.pruebas;
 
public class PruebaPedido extends PruebaDocumentoComercial {
 
    public PruebaPedido(String nombrePrueba) {
        super(nombrePrueba, "Pedido");
    }
 
}
Ejecuta estas dos prueba y verás como testCrear(), heredado de PruebaDocumentoComercial, se ejecuta en ambos casos, contra su módulo correspondiente. Con esto estamos probando el comportamiento común para Pedido y Factura. Probemos ahora la funcionalidad particular de cada uno.

Añadir nuevas pruebas a las pruebas de módulo extendidas

Hasta ahora hemos probado como crear una factura y un pedido. En esta sección probaremos como añadir pedidos a una factura en el módulo Factura, y como establecer la factura a un pedido en el módulo Pedido.
Para probar como añadir un pedido a una factura añade el método testAnyadirPedidos() a PruebaFactura:
// Esta prueba confía en que al menos exista una factura y un pedido
public void testAnyadirPedidos() throws Exception {
    login("admin", "admin");
    assertListNotEmpty(); // Esta prueba confía en que ya existen facturas
    execute("List.orderBy", "property=numero"); // Para usar siempre el mismo pedido
    execute("List.viewDetail", "row=0"); // Va al modo detalle editando la primera factura
    execute("Sections.change", "activeSection=1"); // Cambia a la pestaña 1
    assertCollectionRowCount("pedidos", 0); // Esta factura no tiene pedidos
    execute("Collection.add", // Pulsa el botón para añadir un nuevo pedido, esto te lleva
        "viewObject=xava_view_section1_pedidos"); // a la lista de pedidos
    execute("AddToCollection.add", "row=0"); // Escoge el primer pedido de la lista
    assertCollectionRowCount("pedidos", 1); // El pedido se ha añadido a la factura
    checkRowCollection("pedidos", 0); // Marca el pedido, para borrarlo
    execute("Collection.removeSelected", // Borra el pedido recién añadido
        "viewObject=xava_view_section1_pedidos");
    assertCollectionRowCount("pedidos", 0); // El pedido ha sido borrado
}
En este caso asumimos que hay al menos una factura, y que la primera factura de la lista no tiene pedidos. Antes de ejecutar esta prueba, si no tienes facturas todavía, crea una sin pedidos, o si ya tienes facturas, asegúrate de que la primera no tiene pedidos.
Para probar como asignar una factura a un pedido añade el método testPonerFactura() a PruebaPedido:
public void testPonerFactura() throws Exception {
    login("admin", "admin");
    assertListNotEmpty(); // Esta prueba confía en que existen pedidos
    execute("List.viewDetail", "row=0"); // Va a modo detalle editando la primera factura
    execute("Sections.change", "activeSection=1"); // Cambia a la pestaña 1
    assertValue("factura.numero", ""); // Este pedido todavía no tiene
    assertValue("factura.anyo", ""); // una factura asignada
    execute("Reference.search", // Pulsa en el botón para buscar la factura, esto te
        "keyProperty=factura.anyo"); // lleva a la lista de facturas
    String anyo = getValueInList(0, "anyo"); // Memoriza el año y el número de
    String numero = getValueInList(0, "numero"); // la primera factura de la lista
    execute("ReferenceSearch.choose", "row=0"); // Escoge la primera factura
    assertValue("factura.anyo", anyo); // Al volver al detalle del pedido verificamos
    assertValue("factura.numero", numero); // que la factura ha sido seleccionada
}
En este caso asumimos que hay al menos un pedido, y el primer pedido de la lista no tiene factura. Antes de ejecutar esta prueba, si no tienes pedidos, crea uno sin factura, o si ya tienes pedidos, asegúrate de que el primero no tiene factura.
Con esto ya tienes tus pruebas listas. Ejecútalas, y obtendrás el siguiente resultado:
inheritance_es110.png
Fíjate que la prueba base PruebaDocumentoComercial no se muestra porque es abstracta. Y testCrear() de PruebaDocumentoComercial se ejecuta para PruebaFactura y PruebaPedido.
No solo has adaptado tu código de pruebas al nuevo código de Facturacion, sino que también has aprendido como usar herencia en el mismo código de pruebas.

Resumen

Esta lección te ha mostrado algunos ejemplos prácticos sobre como usar herencia con Java y JPA para simplificar tu código. Hemos usado la configuración por defecto de JPA para la herencia, aunque puedes refinar el comportamiento de JPA para la herencia con anotaciones como @Inheritance, @DiscriminatorColumn, @DiscriminatorValue, etc. Para aprender más acerca de la herencia en JPA puedes leer la documentación del apéndice B.

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 5