JUnit com HSQLDB, JPA e Hibernate

Olá, bom dia.

Vamos falar hoje em como integrar seus testes unitários utilizando um banco de dados? Uma das melhores soluções do mercado atualmente é utilizarum banco de dados em memória.

O banco de dados HSQLDB faz todo esse trabalho de, criar aestrutura de tabelas, relacionamento entre as chaves e permitir que o JPAconsiga trabalhar sem problemas.

Nesse tutorial vamos ver como criar teste unitário (TDD) comJPA e HSQLDB.

Para ver o outro post sobre TDD você pode clicar aqui: TDD – Primeiros passos.

Você irá precisar fazer o download do JAR do HSQLDB aqui (hsqldb.org/Versão2.25 – Última versão).

Vou utilizar o Hibernate como provider do JPA. Nessetutorial (Tutorial Hibernate 3 com JPA 2) você irá encontrar os links necessários para download.

Crie um projeto Java em File > New Project > JavaProject.

Crie uma pasta lib e dentro dela coloque todas asbibliotecas necessárias dentro dessa pasta. Os arquivos são as bibliotecas do HSQLB e do Hibernate.

Vou resumir daqui para frente, mas no tutorial (Tutorial Hibernate 3 com JPA 2) mostracomo colocar seu projeto para executar com sucesso o Hibernate.

Clique com o botão direito do mouse sobre o Projeto >Properties. Vá a Java Build Path > aba Libraries > Add Jars.Selecione as bibliotecas que lá estão e aperte Ok.

Crie uma pasta chamada “src/META-INF” e dentro dela coloqueo seu persistence.xml:

<?xml version="1.0" encoding="UTF-8"?>

<persistence version="2.0"
    xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">

    <persistence-unit name="HsqldbWithTDD" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>

        <properties>
            <property name="javax.persistence.jdbc.driver" value="org.hsqldb.jdbcDriver" />
            <property name="javax.persistence.jdbc.url" value="jdbc:hsqldb:mem:." />
            <property name="javax.persistence.jdbc.user" value="sa" />
            <property name="javax.persistence.jdbc.password" value="" />
            <property name="hibernate.show_sql" value="true" />
            <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
            <property name="hibernate.connection.shutdown" value="true"/>
            <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
        </properties>
    </persistence-unit>
</persistence>

Vamos criar a classe Dog que será persistida.

package com.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQuery;
import javax.persistence.Table;

@Entity
@Table(name = "dog")
@NamedQuery(name="listALL", query="select d from Dog d")
public class Dog {

    public static final String LIST_ALL = "listALL";

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;
    private String name;
    private double weight;

    // Getters and Setters
}

Vamos criar agora a classe DAO que irá ter um CRUD básico da classe Dog (Na classe Dog você irá a anotação @NamedQuery, ainda não vimos aqui no blog mas basicamente é um SQL que irá realizar a consulta):

package com.dao;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

import com.model.Dog;

public class DogDAO {

    private EntityManagerFactory emf;
    private EntityManager em;

    public void startConnection(){
        emf = Persistence.createEntityManagerFactory("HsqldbWithTDD");
        em = emf.createEntityManager();
        em.getTransaction().begin();
    }

    public void closeConnection(){
        em.getTransaction().commit();
        emf.close();
    }

    public void save(Dog dog){
        em.persist(dog);
    }

    public void edit(Dog dog){
        em.merge(dog);
    }

    public Dog find(int dogId){
        return em.find(Dog.class, dogId);
    }

    public void remove(Dog dog){
        em.remove(dog);
    }

    public List listALL(){
        return em.createNamedQuery(Dog.LIST_ALL, Dog.class).getResultList();
    }
}

Vamos testar se nossa informação está sendo persistida? Vamos criar uma classe Main e executar um teste, o resultado da classe Main deve ser como a figura abaixo:

package com;

import com.dao.DogDAO;
import com.model.Dog;

public class Main {

    public static void main(String[] args) {
        DogDAO dogDAO = new DogDAO();

        dogDAO.startConnection();

        try {
            Dog dog = new Dog();
            dog.setName("Beethoven");
            dog.setWeight(45);

            dogDAO.save(dog);

            // It was the first saved dog, so its id is 1
            Dog persistedDog = dogDAO.find(1);

            System.out.println("Name: " + persistedDog.getName());
            System.out.println("Weight: " + persistedDog.getWeight());
        } catch (Exception e) {
            System.out.println("Ops, something happen: " + e.getMessage());
            e.printStackTrace();
        }finally{
            dogDAO.closeConnection();
        }
    }
}


Agora, vamos utilizar a metodologia de TDD para criar um método/classe? Vamos simular que, precisamos de um método que calcule a média de peso de todos os cachorros cadastrados no sistema.

Vou criar a classe DogFacade que irá fazer esse trabalho, einclusive será ela quem irá trabalhar com o DAO.

Vamos configurar a biblioteca do JUnit em nosso sistema, namesma tela em que adicionamos as bibliotecas do Hibernate e do HSQLDB. Cliqueem Add Library.

Selecione JUnit 4 e pronto.

Seguindo boas práticas do TDD, vamos criar nossa classe deteste sem ter a classe DogFacade criada.

package test.com.facade;

import static junit.framework.Assert.assertEquals;

import org.junit.Test;

import com.model.Dog;

public class DogFacadeTest {

    @Test
    public void isWeightOfAllDogsCorret(){
        DogFacade dogFacade = new DogFacade();
        dogFacade.startConnection();

        createData(dogFacade);
        assertEquals(30, dogFacade.getAverageDogsWeight());

        dogFacade.closeConnection();
    }

    private void createData(){
        Dog dogA = new Dog();
        dogA.setName("Big Dog");
        dogA.setWeight(45);

        Dog dogB = new Dog();
        dogB.setName("Medium Dog");
        dogB.setWeight(30);

        Dog dogC = new Dog();
        dogC.setName("Small Dog");
        dogC.setWeight(15);
    }
}

Ao criar essa classe vários erros serão exibidos, masseguindo o princípio do TDD, “Vermelho, Verde, Refatorar”!  Para mais detalhes veja aqui (TDD – Primeiros passos).

Como nosso exemplo é simples, estou deixando o Facadegerenciando a conexão. Em uma aplicação web uma boa saída seria utilizar ainjeção de recursos, para continuar utilizando essa abordagem você pode adotaro que for melhor para você (algumas sugestões minhas, mas você poderá achar outraspela internet):

  • Passar o EntityManager por construtores. Sua classe de teste criaria o EntityManager através do EntityManagerFactory e o enviaria através de um construtor sobrecarregado. Ficaria assim: new DogFacade(entityManager); e dentro do Facade você iria fazer new DogDao(entityManager).
  • Criar o DAO na classe de teste com a conexão ativa e passar o DAO através de construtores sobrecarregados nos Facades.

Será necessário adotar alguma estratégia ou então vai choverNullPointer em seus testes. A parte ruim dessa estratégia é que sua classeFacade vai ficar conhecendo o EntityManager, ou ter uma dependência em umconstrutor de DAO. Mas esses construtores serão utilizados apenas em testes,não irão/devem interferir na aplicação em tempo real.

Vamos ver como ficou nossa classe DogFacade:

package com.facade;

import java.util.List;

import com.dao.DogDAO;
import com.model.Dog;

public class DogFacade {

    private DogDAO dogDAO;

    public DogFacade() {
        dogDAO = new DogDAO();
    }

    public void startConnection() {
        dogDAO.startConnection();
    }

    public double getAverageDogsWeight() {
        double totalWeight = 0;

        List dogs = dogDAO.listALL();

        for(Dog dog : dogs){
            totalWeight += dog.getWeight();
        }

        return totalWeight / dogs.size();
    }

    public void save(Dog dog){
        dogDAO.save(dog);
    }

    public void closeConnection() {
        dogDAO.closeConnection();
    }
}

Vamos executar nosso teste novamente?


Considerações Finais:

  • Eu prefiro essa abordagem a realizar Mocks. Um objeto “mocado” não irá se comportar como você não espera, esse objeto irá apenas se comportar do modo que ele foi programado. Em nosso caso o DAO não teve nenhum método “mocado”. Utilizamos o Facade de modo natural e não alteramos nenhum dos seus métodos.
  • Vejo o mock sendo realmente útil em casos onde são necessárias conexões com outras aplicações ou em aplicações de código legado onde o grau de complexidade é alta e utilizar essa abordagem seria muito custosa.
  • Com esse tipo de abordagem poderemos inclusive criar testes para as classes de view: JSF, Struts, Seam, etc. Próximo post que vou escrever sobre esse assunto! ;)

Espero que esse post possa te ajudar.

Qualquer dúvida ou colocação, basta postar.

Até +! o_

7 thoughts on “JUnit com HSQLDB, JPA e Hibernate

  1. Olá Lazaro, obrigado pela opinião.

    Realmente um Factory é necessário e ideal para as aplicações.

    Nesse post eu não criei para não fugir muito ao tema do post, e não deixá-lo ainda maior.

    Fique a vontade para opinar/criticar/sugerir mais.

    [=

  2. Excelente artigo. Mas tenho uma duvida que to pesquisando a tempo e não consegui solucionar.

    E no caso de não estar conectado direto no Banco de Dados e sim num servidor de camadas (o Glassfish no caso), ai como eu declaro/uso o recurso JDBC que esta no glassfish?

    Ou esse lance de persistencia/jpa só funciona quando se esta conectado direto no banco de dados?

    • Olá Anderson, boa noite.

      O JPA funciona como uma máscara do banco de dados.

      Por isso que ao testar uma aplicação que roda no Glassfish com JUnit você terá que criar um datasource apenas para os testes.

      Indico a você o livro: Pro JPA 2: Mastering the Java™ Persistence API – Mike Keith, Merrick Schincariol
      Muito bom e explicativo. E no final do livro tem um capítulo sobre testes.

      Obrigado pela visita.

  3. Muito legal !

    Com relação a mock, o seu teste foi de integração (com o BD, neste caso) e normalmente não se usa mock mesmo. Mocks são usados para testes unitários já que sua preocupação neste momento não é a integração entre as partes e sim as regras de negócio do método testado.
    Quando se está fazendo teste unitário vc se preocupa apenas com aquele trecho e por isso usamos o mock, para abstrair as demais coisas.

    Ótimo blog !

    • Olá Wendel, boa tarde.

      Concordo com você, mas a minha idéia é passar que mock para integração facade/dao pode ser trocado por banco de dados.

      O mesmo trabalho que se teria para testar um Facade baseado em mock, utilizando o HSQLDB não precisaria realizar mock do DAO para utilizar no Facade.

      O mock eu vejo mais necessário ao utilizar integrações com a visa por exempo, mas agora até a visa já tem um simulado de testes que podem ser utilizados. Existem diversos casos onde podemos utilizar o mock. [=

      Obrigado pelo apoio.

Leave a Comment