Facilitando seus testes de unidade no Java: um pouco de Mockito
Após um bom tempo de aulas ministradas, encontrei uma linha de aprendizagem que acho interessante para chegar até as consideradas boas práticas. A linha é formada pelos conceitos básicos de Orientação a Objetos, Testes, Injeção de Dependências, Programação orientada a interfaces e Mocks. Obviamente há uma interdependência dos tópicos. Há espaço aí para separação de responsabilidades com aspectos, além de design patterns.
Não é a toa que mocks sejam complicados para quem está começando, ele é totalmente obscuro (experiência própria), além de que, dependendo das unidades, é possível testá-las sem mocks. Portanto, é mais interessante lidar com outros conceitos, como injeção de dependências, para só depois partir para Mocks.
O artigo não tem como objetivo explicar o conceito de Mocks, mas sim explicar a aplicabilidade do framework de testes Mockito nos seus projetos. Se você está começando agora, uma boa dica é procurar as edições da MundoJ (em quase toda edição há artigos relacionados a testes e consequentemente mocks) e depois ler o artigo do Martin Fowler.
Mesmo sem conhecer Mocks, lanço um desafio. Continue lendo o artigo e ao final sentirá quanto absorveu do conteúdo. Certamente terá dificuldades. Faça o bookmark do post, volte aos seus estudos, pratique e retorne depois. Ao reler o texto, perceberá o quanto evoluiu, resultado direto de seus estudos.
Então vamos ao código. Temos uma classe FuncionarioDAO
e um método buscarFuncionario
. No nosso exemplo, para buscar o Funcionario
, o sistema deve se comunicar com um mainframe por meio de uma interface Transacao
. Mas perceba aqui que poderia ser uma interface EJB comunicando-se remotamente ou até mesmo uma transação simples JDBC. Essa transação mainframe retorna uma String
contendo as informações do usuário, separada por colunas. Então o que devemos fazer é uma lógica para montar um Funcionario
a partir dessa resposta. Começando pelos testes, teríamos o seguinte:
class FuncionarioDAOTest {
private FuncionarioDAO funcionarioDAO;
@Mock private Transacao transacao;
@Before public void init(){ MockitoAnnotations.initMocks(this); funcionarioDAO = new FuncionarioDAO(transacao); }
@Test public void quandoUmUsuarioValidoForPesquisado(){ when(transacao.executar("12345")).thenReturn("RAPHAEL 12345 2045 "); Funcionario funci = funcionarioDAO.buscarFuncionario("12345"); Assert.assertEquals("RAPHAEL", funci.getNome()); Assert.assertEquals(2045, funci.getSetor()); Assert.assertEquals("12345", funci.getMatricula()); verify(transacao, atMostOnce()).executar("12345"); }
@Test(expected=UsuarioInexistenteException.class) public void quandoUmUsuarioInexistenteForPesquisado(){ when(transacao.executar("123")).thenThrow(new TransacaoOnlineException()); Funcionario funci = funcionarioDAO.buscarFuncionario("123"); } }
O grande problema é a interface Transacao
. Como estamos testando a unidade, não queremos que um objeto real se comunique com o mainframe: seria lento e difícil de testar o resultado, além de testar mais de uma unidade. Esse assunto já foi bastante discutido em artigos do blog da Caelum que falam de testes, sua influência no design, no acoplamento e na velocidade do seu projeto. Perceba o uso da annotation @Mock
, ela facilita a criação de mocks, deixando o código mais limpo.
A primeira linha do primeiro teste (when(transacao.executar("12345")).thenReturn(" ... ")
)define como queremos que o mock se comporte durante a chamada do método executar
. O Mockito leva uma grande vantagem aqui sobre os outros frameworks por ser extremamente refactoring friendly. Para isso, usamos o método when
. Já na primeira linha do segundo teste podemos emular um erro através do thenThrow
, ou seja, caso não haja nenhum funcionario, o método deverá lançar uma exceção. Com isso isolamos o teste apenas à unidade em questão, sem que código de outras unidades sejam executados.
Podemos ainda verificar se durante a execução de buscarFuncionario
o método do nosso mock foi acionado. Para isso usamos o método verify. O segundo argumento pode receber alguns outros métodos do Mockito como never
, atMostOnce
, alLeastOnce
, times
().
Há algumas pessoas que acham desnecessário verificar se o método do objeto mocado foi realmente invocado. Inclusive a própria documentação do mockito comenta a respeito. Porém o uso do verify
evita que alguém retire essa linha do seu código, algo bem difícil de acontecer, mas por experiência própria, podemos esquecer algum assert
.
Feito os testes, partimos então para a lógica.
class FuncionarioDAO { private Transacao transacao; FuncionarioDAO(Transacao tx) { this.transacao = tx; }
public Funcionario buscarFuncionario(String matricula) { try { String resposta = transacao.executar(matricula); return montarFuncionario(resposta); } catch(TransacaoOnlineException e){ throw new UsuarioInexistenteException(e); } }
private Funcionario montarFuncionario(String resposta) { String nome = resposta.substring(0,10); String matricula = resposta.substring(10,16); String setor = resposta.substring(16,21); return new Funcionario(nome.trim(), matricula.trim(), Integer.parseInt(setor.trim())); } }
Agora, ao rodar os testes, você obterá a green bar!.
Vamos imaginar um cenário que você esteja lidando com um sistema com design mais pobre, com muito uso de métodos estáticos, e seu design ficasse assim:
class FuncionarioDAO { public Funcionario buscarFuncionario(String matricula){ String resposta = TransacaoServiceLocator.executar(matricula); return montarFuncionario(resposta); } }
Há algumas formas de testar esse código, ou seja, mocar o comportamento estático. A primeira é manipulação de bytecode (finamente explicado pelo meu amigo André Breves). Entretanto, não me arriscaria a fazer isso na mão, já existem frameworks para tal finalidade e um deles é o PowerMock, que se integra facilmente com Mockito e JUnit. É muito útil principalmente quando o framework que você está usando lhe impõe invocações estáticas a ele. Exemplificando, mesmo tendo D.I, inevitavelmente, uma hora ou outra utilizando o Seam 2.2, você invocará Component.getInstance()
.
Mas é sempre bom lembrar que fazer um design mais orientado a interfaces e desacoplado de implementações concretas lhe dará uma maior flexibilidade tanto na programação do sistema quanto na construção de testes. A discussão sobre utilização de métodos estáticos já está batida na comunidade, vários argumentos, dentre eles a programação mais estruturada e menos O.O.
A segunda seria encapsular o TransacaoServiceLocator
em uma outra classe e mockar essa classe, sem ter de fazer malabarismos.
A terceira forma seria extrair a chamada para um método e na classe FuncionarioDAOTest
, na criação de FuncionarioDAO
, poderíamos criar uma classe anônima e fazer o override do método buscarTransacao
, retornando um mock.
public Funcionario buscarFuncionario(String matricula){ Transacao tx = buscarTransacao(); String resposta = tx.executar(matricula) ; return montarFuncionario(resposta); }
public Transacao buscarTransacao() { return TransacaoServiceLocator.buscarTransacaoMainFrame(); }
Mas tudo isso é muito complicado, como o mockito te ajuda? Aí vêm os Spy Objects, a próposito, brilhantemente explicado aqui. Em suma, Spy Objects são objetos reais até que se prove o contrário. E provamos o contrário quando definimos algum comportamento para ele. Exemplificando:
class FuncionarioDAOTest{
private FuncionarioDAO funcionarioDAO;
@Mock private Transacao transacao;
@Before public void init(){ MockitoAnnotations.initMocks(this); funcionarioDAO = spy(new FuncionarioDAO(transacao)); doReturn(transacao).when(funcionarioDAO).buscarTransacao(); }
Agora definimos que todos os métodos de FuncionarioDAO
serão invocados normalmente, com exceção do método buscarTransacao
, que teve o comportamento alterado. É importante reparar que eu usei doReturn
ao invés do when
.
Concluindo, o importante mesmo é saber lidar com as várias ferramentas que existem, tirando o maior proveito delas para construção de testes. Por exemplo, o meu framework não faz injeção de dependências por construtores, então já que eu não tenho construtores para receber os atributos, nos meus testes eu faço a injeção usando o Mirror, uma DSL que ajuda na manipulação da API reflection. Hoje mesmo já fui apresentando a uma outra ferramenta, fixture-factory. Indubitavelmente, conhecer o poder que cada ferramenta pode lhe oferecer e principalmente saber quando as utilizar são duas importantes tarefas de um arquiteto de software.
E quais ferramentas você utiliza para fazer os seus testes?
Créditos especiais ao companheiro de trabalho Taciano Tres,que me ajudou com ótimas referências e sempre me obriga a estar atualizado!