Palestra Café com Tapioca: Palestra Café com Tapioca: Testes de Unidade com JunitTestes de Unidade com Junit
Instrutor: Fabrício Lemos 15/12/2007
Agenda
● Conceito de Testes de Unidade● Junit● EasyMock● DbUnit● Desenvolvimento Guiado por Testes - TDD
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
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
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
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”
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”
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
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
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
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
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
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
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
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....
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...
Escrevendo os Testes
public class Calculadora {public int somar(int op1, int op2) {return op1 + op2;
}}
Escrevendo os Testesimport static org.junit.Assert.*;import org.junit.Test;public class TestCalculadora {
@Testpublic void testSomar() {
Calculadora calc = new Calculadora();int resultado = calc.somar(2, 3);assertEquals(5, resultado);
}}
Se o método passar no teste...
Se o método não passar no teste...
Outros Assertsvoid assertEquals(Object expected, Object actual)void assertTrue(boolean condition)void assertFalse(boolean condition)void fail()void assertArrayEquals(Object[] expecteds, Object[] actuals)void assertNull(Object object)void assertNotNull(Object object)void assertEquals(String message, Object expected, Object
actual)
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
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
Classe Usuario
public class Usuario {private String nome;private Integer idade;//get e set...
}
Validação de um Usuário
public boolean validarUsuario(Usuario usr) {if (usr.getIdade() > 0 &&
usr.getNome().length() > 2) {return true;
}return false;
}
Testando a Validação
@Testpublic void testValidarUsuario() {
Usuario usuario = new Usuario();usuario.setIdade(18);usuario.setNome("Cejug");boolean resultado = new
UsuarioManager().validarUsuario(usuario);assertTrue(resultado);
}
Testando a Validação@Testpublic void testValidarInvalido() {
Usuario usuario = new Usuario();usuario.setIdade(18);usuario.setNome("");boolean resultado = new
UsuarioManager().validarUsuario(usuario);assertFalse(resultado);
}
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”
Método com Dependência
public void inserirUsuario(Usuario usuario) {usuario.setDataCadastro(new Date());if(validarUsuario(usuario)){
usuarioDAO.inserirUsuario(usuario);} else {
throw new IllegalArgumentException();}
}
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
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
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
Utilizando Mocks
1.Criar o mock da interface a ser simulada2.Configurar o comportamento do mock3.Fazer com que a unidade testada utilize o objeto
mock ao invés da dependência original1.Injetar o Mock
4.Chamar a unidade a ser testada5.Verificar o valor retornado6.Verificar se a unidade testada colaborou
corretamente com o mock
Utilizando Mocks
// 1. criando o mockUsuarioDAO mock = EasyMock.createMock(UsuarioDAO.class);
Utilizando Mocks
// 2. configurando o comportamento do // mockUsuario usuario = new Usuario();usuario.setIdade(25);usuario.setNome("Café com Tapioca");mock.inserirUsuario(usuario);EasyMock.replay(mock);
Utilizando Mocks
// 3. Injetando o MockUsuarioManager manager =new UsuarioManager();
manager.setUsuarioDAO(mock);// 4. chamando o método a ser// testadomanager.inserirUsuario(usuario);
Utilizando Mocks
// 5. Verificando o comportamento do// métodoassertNotNull(usuario.getDataCadastro());// 6. Verificando se o método// usuarioDao.inserir() foi chamado com o // usuário passadoEasyMock.verify(mock);
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
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
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
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
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>
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
Testando com o DbUnitprotected IDatabaseConnection getConnection()
throws ClassNotFoundException, SQLException {
Class.forName("org.hsqldb.jdbcDriver");Connection jdbcConnection =
DriverManager.getConnection("jdbc:hsqldb:sample", "sa", "");
return new DatabaseConnection(jdbcConnection);
}
Testando com o DbUnit
protected IDataSet getDataSet() throws DataSetException, IOException {FileInputStream fileIS = new
FileInputStream("dataset.xml");return new FlatXmlDataSet(fileIS);
}
Testando a Seleção de Usuários
public interface UsuarioDAO {List<Usuario> selecionarUsuarios(String nome);
}
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
@Testpublic void testSelecionarUsuarios() {
String parametroBusca = "jose";List<Usuario> esperado = new
ArrayList<Usuario>();esperado.add(new Usuario(-3L));esperado.add(new Usuario(-1L));List<Usuario> retorno usuarioDAO.
selecionarUsuarios(parametroBusca);assertEquals(esperado, retorno);
}
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
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
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
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
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
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
Duvidas???
● Contato– [email protected]
Referências● http://www.junit.org/● http://www.easymock.org/● http://dbunit.sourceforge.net/● JUnit in Action
– Vincent Massol● JUnit Recipes: Practical Methods for Programmer
Testing– J. B. Rainsberger
● Extreme Programming– Vinicius Manhaes Teles