[1] O documento discute testes de unidade com o framework Junit, incluindo conceitos de testes de unidade, vantagens dos testes de unidade, como escrever testes com Junit e práticas recomendadas para testes de unidade. [2] Também aborda o uso de bibliotecas como EasyMock e DbUnit para isolar dependências e testar a camada de persistência e [3] discute o desenvolvimento guiado por testes (TDD).
2. Palestra Café com Tapioca:
Testes de Unidade com Junit
Instrutor: Fabrício Lemos 15/12/2007
3. Agenda
● Conceito de Testes de Unidade
● Junit
● EasyMock
● DbUnit
● Desenvolvimento Guiado por Testes - TDD
4. Testes de Software
● É um tópico importante e vital no
desenvolvimento de software
● Todo mundo sabe que testes precisam ser feitos
● Existe uma quantidade enorme de aspectos que
precisam ser testados em um sistema
5. Testes de Software
● O sistema deve passar por diversos tipos de teste
antes de entrar em produção
– Unidade
– Integração
– Funcional
– Stress e performance
– Aceitação
6. Testes de Software
● É praticamente impossível evitar que erros sejam
inseridos no código
– Atividade complexa
– Manipula uma grande quantidade de elementos
abstratos
– Inclusões de novas funcionalidades podem influenciar
nas já existentes
– Muitas vezes é difícil ter certeza que o código está
totalmente correto até que ele seja executado
– Geralmente os desenvolvedores do projeto possuem
diferentes níveis de habilidades
7. Testes de Software
● Mesmo tendo reconhecida importância no
desenvolvimento de software, os teste costumam
ser vistos como uma atividade chata e repetitiva
– Só costumam receber atenção quando os problemas
começam a ser tornar comuns
● Geralmente só são iniciados quando o produto está todo
“pronto”
8. Testes de Software
● Muitos projetos assumem que o código
desenvolvido está livre de erros e postergam os
testes para as fases finais do desenvolvimento
– Preferem remediar do que prevenir
– “Está tudo pronto, só falta testar”
9. Testando o Código da Aplicação
● Como ter certeza de que o código escrito faz o
que deveria fazer e não contém erros?
● Abordagens comumente usadas
– Escreve-se um método main que chama o método
escrito e verifica-se o retorno
– Espera-se que a funcionalidade esteja pronta e realiza-
se um teste funcional manual
● Os testes são feitos de maneira pontual
– Não é possível a realização de testes de regressão
10. Testando o Código da Aplicação
● Testes não são automatizados
– Não é possível utiliza-los na integração contínua
● Não são gerados relatórios sobre a execução dos
testes
● Testes não são executados de maneira isolada
– Maior dificuldade em encontrar a fonte de um erro
● Não há disciplina para a realização dos testes
11. Testes de Unidade
● Examinam o comportamento de uma “unidade de
trabalho” distinta
– Em java, geralmente representada por um único
método
● Unidades de trabalho são testadas de maneira
isolada
● Possuem natureza diferente dos testes de
integração e de aceitação
– examinam como vários componentes interagem
12. Objetivos
● Verificar se um pedaço de código faz o que o
desenvolvedor acha que ele faz
● Verificar se um pedaço de código SEMPRE faz o
que o desenvolvedor acha que ele faz
● Identificar bugs prematuramente
● Permitir que novas funcionalidades sejam
adicionadas sem comprometer as já existentes
● Diminuir o impacto das refatorações de código
13. Vantagens dos Testes de Unidade
● Permite uma grande cobertura dos testes
● Pode facilmente simular condições de erro
● Melhora o trabalho em equipe
● Permite ao desenvolvedor entregar código de
qualidade (testado) sem esperar que outras partes
estejam prontas
● Dá-lhe a confiança que seu código funciona
● “Alguém” assistindo a alteração/inclusão de novas
funcionalidades
14. Vantagens
● Diminui o tempo gasto com depuração de código
● Quando há algum erro, te diz o método que falhou e o
motivo da falha
● Melhora a implementação
● Testes de unidade são os primeiros “clientes” do
código
● Serve como uma documentação do código
● Aprendizagem por exemplos
15. Vantagens
● São facilmente incorporados na Integração
Contínua
● Relatórios sobre a corretude do código e a
cobertura dos testes são gerados de maneira
automática
16. Junit
● Framework de teste criado por Erich Gamma e
Kent Beck
● Possui uma api bastante fácil e simples
● Rapidamente se tornou o framework padrão para
testes em Java
● Possui suporte para diversas ferramentas: Eclipse,
Maven, Ant, etc....
17. Junit
● Foram desenvolvidas diversas extensões para
facilitar os testes de partes específicas de um
projeto
– EasyMock
– DbUnit
– StrutsTestCase
– Cactus
– Shale Test Framework
– etc...
24. Boas Práticas
● Cada classe do sistema deve ter sua própria classe
testadora
● Cada método do sistema deve ter seu próprio
método testador
● O nome das classes testadoras devem seguir o
padrão TestNomeClasseTestada
– Não prejudica o code completion
– Classes reconhecidas automaticamente pelo Maven
25. Boas Práticas
● Pacote da classe testadora deve ser o mesmo da
classe testada
– Permite o teste de métodos protected
● Classes testadoras devem ser armazenadas em
pastas separadas
– Facilita o empacotamento da aplicação
– src/test/java
● Padrão do Maven
27. Validação de um Usuário
public boolean validarUsuario(Usuario usr) {
if (usr.getIdade() > 0 &&
usr.getNome().length() > 2) {
return true;
}
return false;
}
28. Testando a Validação
@Test
public void testValidarUsuario() {
Usuario usuario = new Usuario();
usuario.setIdade(18);
usuario.setNome("Cejug");
boolean resultado = new
UsuarioManager().validarUsuario(usuario);
assertTrue(resultado);
}
29. Testando a Validação
@Test
public void testValidarInvalido() {
Usuario usuario = new Usuario();
usuario.setIdade(18);
usuario.setNome("");
boolean resultado = new
UsuarioManager().validarUsuario(usuario);
assertFalse(resultado);
}
30. Isolando os Métodos Testados
● A maioria dos métodos de um sistema possui
dependências externas à classe
● Umas das principais características dos testes de
unidade é o isolamento das unidades testadas
– Permite facilmente identificar o local do erro
● Diminui o tempo de debug
– As dependências não precisam estar prontas para que
o método seja testado
● Para isolar os métodos testados, as dependências
devem ser substituídas por dependências “falsas”
31. Método com Dependência
public void inserirUsuario(Usuario usuario) {
usuario.setDataCadastro(new Date());
if(validarUsuario(usuario)){
usuarioDAO.inserirUsuario(usuario);
} else {
throw new IllegalArgumentException();
}
}
32. Objetos Mock
● Na realização dos testes (em tempo de execução),
as dependências são substituídas por objetos
Mock
– Injeção de dependência
● Simulam as dependências dos métodos testados
● São objetos “vazios”
– Não contém nenhuma lógica
● Podem ser feitos pelo próprio desenvolvedor ou a
partir de alguma biblioteca
33. EasyMock
● Biblioteca para geração dinâmica de Mocks
● Gera objetos mock que implementam qualquer
interface da aplicação
– Possui uma extensão para geração de mocks para
classes
34. EasyMock
● Permite verificar se o método testado colaborou
corretamente com a dependência
– Permite a configuração dos objetos mock
● Quais os argumentos que o mock deve receber do método
testado
● Qual deve ser o retorno do método chamado
● Se a chamada deve lançar alguma exceção
● Facilidade para simular condições de erro
35. Utilizando Mocks
1.Criar o mock da interface a ser simulada
2.Configurar o comportamento do mock
3.Fazer com que a unidade testada utilize o objeto
mock ao invés da dependência original
1.Injetar o Mock
4.Chamar a unidade a ser testada
5.Verificar o valor retornado
6.Verificar se a unidade testada colaborou
corretamente com o mock
37. Utilizando Mocks
// 2. configurando o comportamento do
// mock
Usuario usuario = new Usuario();
usuario.setIdade(25);
usuario.setNome("Café com Tapioca");
mock.inserirUsuario(usuario);
EasyMock.replay(mock);
38. Utilizando Mocks
// 3. Injetando o Mock
UsuarioManager manager =
new UsuarioManager();
manager.setUsuarioDAO(mock);
// 4. chamando o método a ser
// testado
manager.inserirUsuario(usuario);
39. Utilizando Mocks
// 5. Verificando o comportamento do
// método
assertNotNull(usuario.getDataCadastro());
// 6. Verificando se o método
// usuarioDao.inserir() foi chamado com o
// usuário passado
EasyMock.verify(mock);
40. Mocks – Boas Práticas
● Sempre utilize injeção de dependências em suas
classes
● Acesse as dependências através de suas Interfaces
e não através da implementação
● Tenha bem definido qual o comportamento de
seus métodos nas interações com as dependências
41. Testando a Camada de Persistência
● Como ter certeza de que seu código de acesso a
dados está lendo e escrevendo corretamente na
base de dados?
● É necessária uma integração com o banco de
dados na realização dos testes
– Misto entre teste de unidade e teste de integração
● Quando feita de forma não automática, a
verificação depende da inspeção visual do
conteúdo da base de dados
42. Testando a Camada de Persistência
● Para automatizar os testes duas condições tem
que ser satisfeitas
– O conteúdo da base de dados tem ser conhecido no
início de cada teste
– Deve ser possível verificar o conteúdo da base de
dados após cada teste
● Os testes ainda devem ser realizados
independentemente
– Testar a inserção independentemente da seleção
43. DbUnit
● Extensão do Junit especializada em testes
integrados ao banco de dados
● Permite o controle do conteúdo armazenado na
base de dados
– Exporta dados para a base através de arquivos XML
● Possui métodos de verificação do estado da base
de dados
– Importa dados da base e compara com arquivos XML
44. Testando com o DbUnit
● O conteúdo da base de dados é configurado
através de DataSet no formato XML
● Por default, antes de cada teste a base de dados é
esvaziada e re-populada com o conteúdo do
DataSet
<dataset>
<Usuario codigo="-1" nome="josefino" idade="20" />
<Usuario codigo="-2" nome="maria" idade="40" />
<Usuario codigo="-3" nome="jose" idade="40" />
</dataset>
45. Testando com o DbUnit
● Classe de teste deve herdar de DatabaseTestCase
● Implementar os métodos
– IDatabaseConnection getConnection()
● Retorna uma conexão com a base de dados
– IDataSet getDataSet()
● Retorna o DataSet para o povoamento da base de dados
46. Testando com o DbUnit
protected IDatabaseConnection getConnection()
throws ClassNotFoundException,
SQLException {
Class.forName("org.hsqldb.jdbcDriver");
Connection jdbcConnection =
DriverManager.getConnection(
"jdbc:hsqldb:sample", "sa", "");
return new
DatabaseConnection(jdbcConnection);
}
47. Testando com o DbUnit
protected IDataSet getDataSet() throws
DataSetException, IOException {
FileInputStream fileIS = new
FileInputStream("dataset.xml");
return new FlatXmlDataSet(fileIS);
}
48. Testando a Seleção de Usuários
public interface UsuarioDAO {
List<Usuario> selecionarUsuarios(
String nome);
}
49. Realizando o Teste
● Montar o(s) parâmetro(s) para a busca
● Criar o retorno esperado de acordo com o
DataSet de povoamento da base de dados
● Realizar a busca
● Verificar se o resultado da busca é igual ao
resultado esperado
51. Desenvolvimento Guiado por Testes
(TDD)
● A escrita de testes ajuda na melhoria do design do
código
● Ao se perceber os benefícios dos testes, a
tendência é escreve-los o quanto antes
● Com o alcance da maturidade, durante a
implementação de um método o desenvolvedor já
imagina como irá testa-lo
52. TDD
● Quanto mais cedo os testes são implementados,
maiores são os benefícios
– Evita que bugs se espalhem pela aplicação e os
tornam mais fáceis de ser corrigidos
● Os adeptos do TDD levam essa prática ao
estremo
– Os testes são escritos antes do código a ser testado
53. TDD
● A regra básica do TDD é somente escrever
código se existir algum teste falhando
– Regra válida para implementação de novas
funcionalidades ou para correção de bugs
● Somente o código estritamente necessário para
fazer o teste passar deve ser escrito
– Refatorações são permitidas
54. Codificando com TDD
● Escreva o teste para a funcionalidade desejada
– O teste irá falhar
● Escreva o código necessário e suficiente para o
teste passar
● Refatore o código
– Elimine possíveis duplicações de código
● Repita os passos quantas vezes necessário
● Entregue o código
55. Vantagens do TDD
● Antes mesmo de começar a implementar o
método, o desenvolvedor já tem definido qual
será seu comportamento
● O código já nasce sem vícios de design
● Evita que bugs sejam adicionados ao código
56. Vantagens do TDD
● Mantém o desenvolvedor focado no problema a
ser resolvido
– Código é o mais simples que funciona
● Não procura antecipar mudanças
– Manutenção é garantida pela não duplicidade e pela
simplicidade do código