Testes unitários com JUnit e EJB 3.0
Neste artigo faremos uma abordagem prática de como implementar de forma simples e direta, testes unitários para componentes EJB. E seguindo a linha dos artigos anteriores, utilizaremos a especificação 3.0 do Enterprise Java Beans.
No momento do desenvolvimento de um componente eu prefiro seguir a ordem:
1º escrevo meu Entity
2º escrevo minhas interfaces local e remote (nem sempre as duas são necessárias)
3º escrevo meu Facade, um Stateless SessionBean
4º escrevo minha classe de testes unitários para o componente
Apenas passo para o próximo componente quando os testes do JUnit estão rodando sem problemas. Independente de você seguir ou não a minha ordem de desenvolvimento, recomendo que use o mesmo procedimento com relação aos testes unitários, passando para a próxima etapa do desenvolvimento somente quando os testes anteriores estiverem rodando sem erros. Isso evita que os problemas se acumulem ao longo do desenvolvimento…
Vamos escrever um componente “Usuario” para este exemplo. Seguindo nossa ordem, primeiro escrevemos o Entity. Note que vamos utilizar aqui as Named Queries, por meio das annotations “@NamedQueries” e “@NamedQuery”, este é um recurso que nos permite uma melhor implementação da Pattern Session Facade, conforme expliquei em um artigo anterior. Vamos ao código:
package com.model.ejb.entity;
import javax.persistence.Entity;
import javax.persistence.id;
import javax.persistence.Table;
import javax.persistence.GeneratorType;
import java.io.Serializable;
@Entity
@Table(name=”USUARIO”)
@NamedQueries({
@NamedQuery(name=”findUsuarioById”,
queryString=”from Usuario u where u.id =:id”),
@NamedQuery(name=”findUsuarioAll”,
queryString=”from Usuario u order by u.nome”),
@NamedQuery(name=”findUsuarioByNome”,
queryString=”from Usuario u where u.nome =:nome”),
@NamedQuery(name=”findUsuarioByNomeSenha”,
queryString=”from Usuario u where u.nome =:nome and u.senha =:senha”),
@NamedQuery(name=”findUsuarioByEmail”,
queryString=”from Usuario u where u.email =:email”)
})
public class Usuario implements Serializable{
private Integer id;
private String nome;
private String email;
private String senha;
@Id (generate = GeneratorType.AUTO)
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
Perceba que os nomes utilizados para as Named Queries tem um padrão, eu utilizo sempre o prefixo “find” + “nome-da-classe”. Como veremos depois, o método da facade terá este mesmo nome, sem o nome da classe. Por exemplo uma NamedQuery chamada “findUsuarioByNome” no entity se chamará “findByNome” na Facade (Stateless SessionBean).
Porque eu coloco o nome da classe no nome da NamedQuery? Bem, porque o container, ao subir seus componentes, carrega todas as NamedQueries e as coloca em uma área da memória, se durante este carregamento existirem queries com o mesmo nome, ele dá erro. Por isso eu gosto de colocar o nome da classe na declaração da NamedQuery, e depois eu já sei automaticamente qual o nome dela no SessionBean.
Agora vamos escrever a interface de negócio, para este exemplo usaremos apenas uma interface do tipo Remote.
package com.service;
import java.rmi.RemoteException;
import java.util.Collection;
import javax.ejb.Remote;
import com.model.ejb.entity.Usuario;
@Remote
public interface UsuarioService{
public void addUsuario(Usuario usuario) throws RemoteException;
public void removeUsuario(Integer id) throws RemoteException;
public void updateUsuario(Usuario usuario) throws RemoteException;
public Collection findAll() throws RemoteException;
public Usuario findById(Integer id) throws RemoteException;
public Usuario findByNome(String nome) throws RemoteException;
public Usuario findByNomeSenha(String nome, String senha) throws RemoteException;
public Usuario findByEmail(String email) throws RemoteException;
}
Simples assim. A tag “@Remote” indica que esta é uma interface remota para o SessionBean que a implementar. Vamos ao nosso SessionBean.
package com.model.ejb.session;
import java.rmi.RemoteException;
import java.util.Collection;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import com.service.UsuarioService;
import com.model.ejb.entity.Usuario;
public class UsuarioServiceBean implements UsuarioService throws RemoteException {
@PersistenceContext (unitName=”myUnit”)
protected EntityManager entityManager;
public void addUsuario(Usuario usuario) throws RemoteException {
entityManager.persist(usuario);
}
public void removeUsuario(Integer id){
entityManager.remove(findById(id));
}
public void updateUsuario(Usuario usuario) throws RemoteException {
Usuario old = findById(usuario,getId());
old.setNome(usuario.getNome());
old.setSenha(usuario.getSenha());
old.setEmail(usuario.getEmail());
}
public Usuario findById(Integer id) throws RemoteException{
Query query = entityManager.createNamedQuery(”findUsuarioById”);
query.setParameter(”id”, id);
return (Usuario)query.getSingleResult();
}
public Collection findAll() throwsRemoteException{
Query query = entityManager.createNamedQuery(”findUsuarioAll”);
return query.getResultList();
}
public Usuario findByNome(String nome) throws RemoteException{
Query query = entityManager.createNamedQuery(”findUsuarioByNome”);
query.setParameter(”nome”, nome);
return (Usuario)query.getSingleResult();
}
public Usuario findByNomeSenha(String nome, String senha) throws RemoteException{
Query query = entityManager.createNamedQuery(”findUsuarioByNomeSenha”);
query.setParameter(”nome”, nome);
query.setParameter(”senha”, senha);
return (Usuario)query.getSingleResult();
}
public Usuario findByEmail(String email) throws RemoteException{
Query query = entityManager.createNamedQuery(”findUsuarioByEmail”);
query.setParameter(”email”, email);
return (Usuario)query.getSingleResult();
}
}
Pronto. Temos prontos nosso componentes e nossa interface de negócio, que teoricamente estão funcionando. Eu preferi fazer um componente bem simples, para que fique mais fácil para o leitor entender o fluxo desde o Entity até nossos testes unitários do JUnit.
Antes de fazermos nossa classe do JUnit, permita-me dar uma breve explicação de como funciona a estrutura dos testes em uma suite do JUnit.
Em geral, para cada componente que fazemos, fazemos também uma classe de teste, que estende de TestCase. Na hora de executarmos o teste basta iniciarmos a suite do JUnit e passarmos como parâmetro a nossa classe que extende de TestCase.
Entretanto, se estamos falando de um sistema, certamente estaremos falando de vários componentes e classes que são testadas.
Para simplificar a inicialização do teste, podemos simplesmente criar um classe, que será a nossa suite de testes, e adicionarmos cada nova classe de teste a ela, na proporção em que formos desenvolvendo. Achou difícil? Vamos ver na prática.
Perceba que vou utizar o ServiceLocator para facilitar nosso trabalho de obter uma instância do nosso componente. Eu explico detalhadamente como fazer o nosso ServiceLocator na parte 4 do tutorial de EJB 3.0.
Primeiro eu vou criar a classe de teste para o nosso componente Usuario:
package com.testes;
import junit.framework.TestCase;
import com.model.ejb.entity.Usuario;
import com.model.ejb.session.UsuarioServiceBean;
import com.service.ServiceLocator;
import com.service.UsuarioService;
import java.util.Collection;
public class TestUsuarioService extends TestCase{
public TestUsuarioService(String nomeMetodo){
//aqui passamos para o construtor da super classe o nome do
//método que contém nossos testes. Isso é necessário
//para a nossa suite executar os testes:
super(nomeMetodo);
}
public void runtest(){
try{
UsuarioService usuarioService = (UsuarioService)ServiceLocator.getInstance().
get(UsuarioService.class.getName());
assertNotNull(”usuarioService, null”, usuarioService);
//testando primeiro o findAll:
Collection antes = usuarioService.findAll();
assertNotNull(”antes, null”, antes);
//criando um usuario para teste:
Usuario usuario = new Usuario();
String nome = “usuario teste”;
String senha = “teste”;
String email = “teste@teste.com”;
usuario.setNome(nome);
usuario.setEmail(email);
usuario.setSenha(senha);
usuarioService.addUsuario(usuario);
//testando se ele foi adicionado:
Collection depois = usuarioService.findAll();
assertNotNull(”depois, null”, depois);
assertTrue(”depois > antes”, depois.size() > antes.size());
//testando a busca por nome:
Usuario porNome = usuarioService.findByNome(nome);
assertNotNull(”porNome, null”, porNome);
assertEquals(”porNome, equals”, porNome.getEmail(), email);
//testando a busca por nome e senha:
Usuario porNomeSenha = usuarioService.findByNomeSenha(nome, senha);
assertNotNull(”porNomeSenha, null”, porNomeSenha);
assertEquals(”porNomeSenha, equals”, porNomeSenha.getEmail(), email);
//testando a busca por email:
Usuario porEmail = usuarioService.findByEmail(email);
assertNotNull(”porEmail, null”, porEmail);
assertEquals(”porEmail, equals”, porEmail.getNome(), nome);
//testando update:
String newName = “updated name”;
porEmail.setNome(newName());
usuarioService.updateUsuario(porEmail);
//verificando se foi feito corretamente:
Usuario updated = usuarioService.findByEmail(email);
assertNotNull(”updated, null”, porEmail);
assertEquals(”updated, equals”, porEmail.getNome(), nome);
//testando remove:
usuarioService.removeUsuario(updated);
Collection removed = usuarioService.findAll();
assertNotNull(”removed, null”, removed);
assertTrue(”removed < depois", removed.size() < depois.size());
}catch (Exception e){
e.printStackTrace();
fail();
}
}
}
Esta é uma classe de teste razoavelmente eficiente, onde testamos os métodos de inclusão, exlusão e alteração do nosso componente e também testamos os nosso métodos “finder”.
Note que tudo o que temos fazer em um TestCase são assertions, de forma a verificar o funcionamento dos métodos. Eu coloquei testes nos métodos “addUsuario”, “removeUsuario” e “updateUsuario” apenas por se tratar de um exemplo, na prática eu não costumo testar esses métodos, porque eles fazem apenas chamadas para métodos do EntityManager, e não possuem nenhuma inteligência de negócio.
Agora conforme mencionei anteriormente, vamos criar nossa suite, onde adicionaremos nossos testes na medida em que formos criando. Vamos chama-la de “TestAll”.
package com.testes;
import junit.framework.Test;
import junit.framework.TestSuite;
public class TestAll{
public static Test suite(){
//o objeto suite, onde adicionaremos nossos testes:
TestSuite suite = new TestSuite();
//agora adicionamos os testes:
suite.addTest(new TestUsuarioService(”runTest”));
return suite;
}
}
Fácil, não? Agora, quando formos executar nosso testes executamos esta classe que contém a suite, e ela faz a chamada aos nossos testes. No Eclipse por exemplo, basta clicar com o botão direto em TestAll e selecionar “Run as JUnit Test”, ele executa e exibe o relatório no final.
Para cada novo teste que você desenvolver basta adicionar mais uma linha na suite, fazendo a chamada para o teste. Se por exemplo criarmos um componente chamado “Cliente”, e fizermos um teste para ele chamado “TestClienteService”, adicionamos na nossa suite assim:
//o objeto suite, onde adicionaremos nossos testes:
TestSuite suite = new TestSuite();
//agora adicionamos os testes:
suite.addTest(new TestUsuarioService(”runTest”));
suite.addTest(new TestClienteService(”runTest”));
Fazendo isso, ao final do seu projeto você terá mais certeza de que está tudo funcionando e caso haja algum problema você saberá exatamente aonde ele está, e poderá corrigi-lo com mais agilidade.
Desenvolver fazendo testes pode parecer mais demorado quando não se está acostumado, mas com o tempo você verá que seu código fica muito mais organizado e ao final do desenvolvimento você ganha muito mais tempo, pois não há surpresas na hora de entregar o sistema para o cliente.
Fabricio Braga