martes, 31 de agosto de 2021

Database Rider: Como mejorar los tests con base de datos

Hace poco estuvimos viendo ejemplos para testear la capa de acceso a datos de una aplicación. Hoy vamos a ver una librería que nos ayudará a realizar estos tests: Database Rider. La cual se basa principalmente en la librería DBUnit y la extensión de persistencia de Arquillian. 

DBUnit tiene como función principal permitirnos poner la BBDD en un estado concreto, para el correcto funcionamiento de los tests JUnit, manteniendo el aislamiento entre los distintos tests. Y Arquilliam por su parte es una framework de testing perteneciente a JBoss para testear componentes EJB que son ejecutados o interactuan con un contenedor. Este framework permite que permite realizar más facilmente tests de integración.  

Lo que conseguiremos con Database Rider que no conseguiremos con los otros dos, es simplicidad y poder ejecutarlos sin necesidad de otro complemento con JUnit 5. Ahora veremos como. Para empezar necesitamos añadir la dependencia de Database Rider y la BBDD y la configuración de esta. Esta configuración se realizará a través del fichero persistence.xml pues Database Rider esta pensado para el uso de un Entity Manager en las pruebas. Aparte, Database Rider tiene varias librerías en función de como queramos usarla, nosotros hemos seleccionado la ideada para ser usada con JUnit 5. 

Basicamente la configuración que hagamos con Database Rider y su uso tendremos que realizar tres pasos:

  • Añadir la extensión de JUnit5 @ExtendWith(DBUnitExtension.class).
  • Crear una instancia de EntityManagerProvider que nos permita la conexión con la BBDD.
  • La anotación @DataSet que nos permita la configuración de la librerías. 
En el modo más básico de uso, podemos indicarle un fichero YAML como el que mostramos a continuación, que nos permita rellenar la base de datos antes del test. 

book:
  - id: 1
    name: "Ender's game"
    author: "Orson S. Card"
  - id: 2
    name: "The starts my destination"
    author: "Alfred Bester"
  - id: 3
    name: "Dune"
    author: "Frank Herbert"

A continuación podemos ver un ejemplo de toda la configuración básica  y como funciona la librería en base a los assertions que indicamos. 

@ExtendWith(DBUnitExtension.class)
@TestMethodOrder(MethodOrderer.MethodName.class)
public class BookDaoImplBeforeAllTest {

    private static BookDaoImpl dao;
    private static ConnectionHolder connectionHolder = 
        () -> EntityManagerProvider.instance("persistence-unit-test").connection();
    @BeforeAll
    @DataSet("books.yml")
    public static void setUpOne() {
	dao = new BookDaoImpl();
	dao.em = EntityManagerProvider.em();
    }
    @Test
    public void a_create() {
	List<Book> lst = dao.findAll();
	assertThat(lst.size(), equalTo(3));
	Book book = new Book(4, "Foundation", "Isaac Asimov");
	dao.em.getTransaction().begin(); // we are testing with non-jta datasource
	dao.create(book);
	dao.em.getTransaction().commit();
	lst = dao.findAll();
	assertThat(lst.size(), equalTo(4));
    }
    @Test
    public void b_findAll_postcreate() {
	List<Book> lst = dao.findAll();
	assertThat(lst.size(), equalTo(4));
    }
}

La anotación principal de @DataSet la podremos usar a nivel de método también o hacer que se invoque cada vez que vayamos a hacer un test al usar la anotación @BeforeEach. Pero en función de la misma, el resultado será diferente. 

@BeforeEach
@DataSet("books.yml")
    public void setUpSeveral() {
    dao = new BookDaoImpl();
    dao.em = em();
}
@Test
public void a_create() {
    List<Book> lst = dao.findAll();
    assertThat(lst.size(), equalTo(3));
    Book book = new Book(4, "Foundation", "Isaac Asimov");
    dao.em.getTransaction().begin(); // we are testing with non-jta datasource
    dao.create(book);
    dao.em.getTransaction().commit();
    lst = dao.findAll();
    assertThat(lst.size(), equalTo(4));
}
@Test
public void b_findAll_postcreate() {
    List<Book> lst = dao.findAll();
    assertThat(lst.size(), equalTo(3));
}

La anotación @DataSet permite diferentes opciones de configuración que nos pueden ayudar a la hora de preparar los tests y ayudar en el aislamiento de los mismos. Como por ejemplo limpiar la BBDD antes y/o después de la ejecución del test, o si lo preferimos ejecutar un script SQL.

@BeforeAll
@DataSet("books.yml")
public static void setUpOne() {
	dao = new BookDaoImpl();
	dao.em = em();
}
@Test
@DataSet(cleanBefore = true, executeScriptsAfter = { "bookInsert1.sql" })
public void a_findAll() {
    List<Book> lst = dao.findAll();
    assertThat(lst.size(), equalTo(0));
}
@Test
@DataSet
public void b_findAll() {
    List<Book> lst = dao.findAll();
    assertThat(lst.size(), equalTo(1));
}

Todas las posibles opciones de la configuración puedes verlas aquí.

Además la librería contará con una anotación con la que podremos validar el contenido de la BBDD una vez que se ha ejecutado la prueba. Esta anotación es @ExpectedDataSet. Entre otras cosas podremos:

  • Validar el columnas y contenido exacto pasándole un fichero YAML con el contenido de la tabla.
  • Validar el registros parcial, indicando algunas columnas que queremos omitir.
  • Validar el contenido parcialmente, indicando expresiones regulares para validar dicho contenido. 

@Test
@ExpectedDataSet(value = "expectedBooks.yml", ignoreCols = "id")
public void create() {
	List<Book> lst = dao.findAll();
	assertThat(lst.size(), equalTo(3));
	Book book = new Book(4, "Foundation", "Isaac Asimov");
	dao.em.getTransaction().begin(); // we are testing with non-jta datasource
	dao.create(book);
	dao.em.getTransaction().commit();
	lst = dao.findAll();
	assertThat(lst.size(), equalTo(4));
}
@Test
@ExpectedDataSet(value = "expectedRegExBooks.yml")
public void createRegEx() {
	List<Book> lst = dao.findAll();
	assertThat(lst.size(), equalTo(3));
	Book book = new Book(4, "Foundation", "Isaac Asimov");
	dao.em.getTransaction().begin(); // we are testing with non-jta datasource
	dao.create(book);
	dao.em.getTransaction().commit();
	lst = dao.findAll();
	assertThat(lst.size(), equalTo(4));
}

El contenido de los ficheros YAML para validar las pruebas se muestra a continuación. En el primero podemos ver como los datos son correctos menos los correspondientes a la columna id. Y en el segundo utilizamos las expresiones regulares para verificar dicho contenido. Un aspecto importante en las expresiones regulares es indicar un punto antes del asterisco. 

#expectedBooks.yml
book:
  - id: 11
    name: "Ender's game"
    author: "Orson S. Card"
  - id: 22
    name: "The starts my destination"
    author: "Alfred Bester"
  - id: 3
    name: "Dune"
    author: "Frank Herbert"
  - id: 33
    name: "Foundation"
    author: "Isaac Asimov"
#expectedRegExBooks.yml
book:
  - id: "regex:\\d+"
    name: regex:^Ender's.*
    author: "Orson S. Card"
  - id: 2
    name: regex:.*starts.*
    author: Alfred Bester
  - id: 3
    name: Dune
    author: Frank Herbert
  - id: 5
    name: regex:.*tion$
    author: Isaac Asimov

Por último indicar un par de errores que pueden ser mas o menos comunes:

  • java.lang.IllegalStateException: Call instance('PU_NAME') before calling em(): Esto puede ser indicativo de que no hemos indicado la anotación de @ExtendWith@DataSet.
  • NullPointerException al obtener la conexión: Esto es debido a errores en el fichero persistence.xml. 

Con esto hemos dado un paso más hacia las pruebas unitarias o de integración de nuestras aplicaciones. Si quieres puedes ver todo el código aquí. 

No hay comentarios:

Publicar un comentario