Introdução
• Testes são essenciais para garantir a qualidade do código.
• No entanto:
– Escrever testes de unidade pode se tornar um desafio devido ao
código de preparação que deve ser escrito: inicialização de objetos,
povoamento do banco de dados, comunicação remota, classes
colaboradoras, ...
– A medida que a quantidade de testes cresce, a sua execução pode
levar um tempo considerável devido à comunicação com o banco de
dados ou outros serviços externos.
• A principal razão dos testes de unidade serem difíceis de escrever e
lentos de executar é por causa de suas classes colaboradoras.
– Esse é um fato relevante em programas fortemente dependentes de
sistemas de banco dados. É comum que as regras de negócio
dependam de informações armazenadas.
2
Introdução
• A colaboração é geralmente um elemento bom e
necessário, pois mantém a coesão da classe. No
entanto, pode acarretar em alguns problemas:
– Ao implementar a classe a ser testada, o desenvolvedor
deve pensar e escrever os detalhes de todos os
colaboradores envolvidos.
– Criar e inicializar os colaboradores torna os testes de uma
classe mais complicados pois alguns objetos requerem
inicialização complexa até atingirem o estado correto para
o teste.
– Colaboradores introduzem acoplamento indesejado. Por
exemplo, utilizar implementações reais de DAOs acoplaria
o modelo de domínio ao banco de dados e nos forçaria a
resolver questões de persistência.
3
Benefícios ao utilizar mock objects
• Testes de unidade devem ser isolados dos agentes
externos ao código que está sendo testado.
– O objetivo é testar o método em questão, e não os objetos
externos.
• Mock objects permitem o isolamento dos testes de
unidade fornecendo implementações falsas dos
objetos colaboradores da unidade em teste.
• Quando utilizado com TDD, mock objects permitem
que o desenvolvedor concentre-se na implementação
do código em questão, livrando-o de escrever o código
dos colaboradores.
4
Mockito
• Existem vários frameworks em Java para
facilitar a geração de mock objects
– Mockito, JMock, EasyMock, ...
• Vantagens do Mockito
– Escrita clara.
– API simples.
– Permite a criação de mocks a partir de classes
concretas.
5
EXEMPLO 1 – JOGO CRAPS
7
Introdução
• Este é um exemplo simples para demonstrar o uso do
Mockito e de objetos mock.
• Craps (jogo de dados):
– O jogador lança dois dados de seis faces. Depois que os dados
param de rolar, calcula-se a soma dos pontos obtidos nos dois
dados. Se a soma for 7 ou 11 no primeiro lance, o jogador
ganha. Se a soma for 2, 3 ou 12 no primeiro lance, o jogador
perde (a mesa ganha).
– Se a soma for 4, 5, 6, 8, 9 ou 10 no primeiro lance, essa soma
torna-se o ponto do jogador. Para ganhar, o jogador deve
continuar a rolar os dados até atingir seu ponto (isto é, a soma
deve ser igual ao ponto do jogador).
– O jogador perde se obtiver um 7 antes de atingir seu ponto.
8
Fluxo do jogo
9
Projeto – visão estática
10
Fluxo da aplicação
public class Main{
public static void main(String[] args){
Craps craps = new Craps();
Scanner scan = new Scanner([Link]);
while(![Link]()){
[Link]("Digite enter para continuar.");
[Link](); int soma = [Link]();
[Link]("Soma: " + soma);
}
[Link]();
if([Link]() == 1)
[Link]("Você ganhou!");
else if([Link]() == 2)
[Link]("A banca ganhou!");
else
throw new IllegalStateException();
}
}
11
Classe Craps (1)
import [Link];
public class Craps{
private int soma, ponto, vencedor;
private boolean primeiraRodada = true;
private Random rand = new Random();
private int rolarUmDado(){
return [Link](6) + 1;
}
public boolean isFimDeJogo(){
return vencedor == 1 || vencedor == 2;
}
public int getVencedor(){
return vencedor;
}
12
Classe Craps (2)
public int rolarDados(){
soma = rolarUmDado() + rolarUmDado();
if(primeiraRodada){
if(soma == 7 || soma == 11)
vencedor = 1;
else if(soma == 2 || soma == 3 || soma == 12)
vencedor = 2;
else
ponto = soma;
primeiraRodada = false;
}
else{
if(soma == ponto)
vencedor = 1;
else if(soma == 7)
vencedor = 2;
}
return soma;
}
}
13
Teste 1: jogador perde na primeira
rolagem de dados
package [Link];
import static [Link].*;
import [Link];
public class CrapsTest {
@Test
public void testRolarDados_1(){
Craps craps = new Craps();
[Link]();
assertTrue([Link]());
assertEquals(2, [Link]());
}
}
14
Problema com o teste 1
• Como garantir a obtenção de 2, 3 ou 12 na
primeira rolagem já que a geração dos
números é aleatória?
• Solução: substituir o objeto gerador de
números aleatórios (rand) por um objeto
mock.
– Para tal, é necessário implementar o método
setRand na classe Craps.
15
Teste 1 usando objeto mock
@Test
public void testRolarDados_1(){
Random randMock = [Link]([Link]);
//notar que são feitas duas rolagens de dado
[Link]([Link](6)).thenReturn(0);
Craps craps = new Craps();
[Link](randMock); [Link]();
assertTrue([Link]());
assertEquals(2, [Link]());
}
16
Teste 1 usando objeto mock
Criação do objeto mock.
@Test
public void testRolarDados_1(){
Random randMock = [Link]([Link]);
//notar que são feitas duas rolagens de dado
[Link]([Link](6)).thenReturn(0);
Craps craps = new Craps();
[Link](randMock); [Link]();
assertTrue([Link]());
assertEquals(2, [Link]());
}
17
Teste 1 usando objeto mock
@Test
public void testRolarDados_1(){
Random randMock = [Link]([Link]);
//notar que são feitas duas rolagens de dado
[Link]([Link](6)).thenReturn(0);
Craps craps = new Craps();
[Link](randMock); [Link]();
assertTrue([Link]());
assertEquals(2, [Link]());
}
Configuração de comportamento do objeto mock. Neste caso, foi
definido que as chamadas ao método nextInt retornem zero como
resultado. Note que a configuração do mock foi realizada com base no
código do método rolarUmDado da classe Craps para que a soma
resultante seja igual a dois.
18
Melhorando o projeto
• A configuração de comportamento do mock
foi baseada no código do método
rolarUmDado da classe Craps. Além de ferir o
encapsulamento, a configuração do mock não
ficou intuitiva.
• Vamos melhorar o projeto movendo a geração
dos números aleatórios para a classe Dado.
Esta mudança melhora a coesão da classe
Craps e melhora a legibilidade do teste.
19
Visão estática atualizada
20
Classe Dado
package [Link];
import [Link];
public class Dado {
private Random rand = new Random();
public int rolar(){
return [Link](6) + 1;
}
}
21
Classe Craps atualizada (1)
public class Craps {
private int soma, ponto, vencedor;
private boolean primeiraRodada = true;
private Dado dado = new Dado();
public boolean isFimDeJogo(){
return vencedor == 1 || vencedor == 2;
}
public int getVencedor(){
return vencedor;
}
void setDado(Dado novoDado) {
[Link] = novoDado;
}
22
Classe Craps atualizada (2)
public int rolarDados(){
soma = [Link]() + [Link]();
if(primeiraRodada){
if(soma == 7 || soma == 11)
vencedor = 1;
else if(soma == 2 || soma == 3 || soma == 12)
vencedor = 2;
else
ponto = soma;
primeiraRodada = false;
}
else{
if(soma == ponto)
vencedor = 1;
else if(soma == 7)
vencedor = 2;
}
return soma;
}
}
23
Teste 1 atualizado
@Test
public void testRolarDados_1(){
Dado dadoMock = [Link]([Link]);
//notar que são feitas duas rolagens de dado
[Link]([Link]()).thenReturn(1);
Craps craps = new Craps();
[Link](dadoMock);
[Link]();
assertTrue([Link]());
assertEquals(2, [Link]());
}
24
Teste 2: jogador ganha na primeira
rolagem
@Test
public void testRolarDados_2(){
Dado dadoMock = [Link]([Link]);
//notar que são feitas duas rolagens de dado
[Link]([Link]()).thenReturn(5, 2);
Craps craps = new Craps();
[Link](dadoMock);
[Link]();
assertTrue([Link]());
assertEquals(1, [Link]());
}
25
Teste 2: jogador ganha na primeira
rolagem
@Test
public void testRolarDados_2(){
Dado dadoMock = [Link]([Link]);
//notar que são feitas duas rolagens de dado
[Link]([Link]()).thenReturn(5, 2);
Craps craps = new Craps();
[Link](dadoMock);
[Link]();
assertTrue([Link]()); Configuração do seguinte
comportamento: retorne,
assertEquals(1, [Link]());
} respectivamente, 5 e 2 na
primeira e segunda chamada
ao método rolar.
26
Teste 3: jogador perde na terceira
rolagem
@Test
public void testRolarDados_3(){
Dado dadoMock = [Link]([Link]);
Craps craps = new Craps();
[Link](dadoMock);
[Link]([Link]()).thenReturn(3, 5);
[Link]();
assertFalse([Link]());
[Link]([Link]()).thenReturn(6, 5);
[Link]();
assertFalse([Link]());
[Link]([Link]()).thenReturn(5, 2);
[Link]();
assertTrue([Link]());
assertEquals(2, [Link]());
}
27
Teste 4: jogador ganha na segunda
rolagem
@Test
public void testRolarDados_4(){
Dado dadoMock = [Link]([Link]);
Craps craps = new Craps();
[Link](dadoMock);
[Link]([Link]()).thenReturn(1, 3);
[Link]();
assertFalse([Link]());
[Link]([Link]()).thenReturn(2, 2);
[Link]();
assertTrue([Link]());
assertEquals(1, [Link]());
}
28