Facilitando seus testes de unidade no Java: um pouco de Mockito

Facilitando seus testes de unidade no Java: um pouco de Mockito
lacerdaph
lacerdaph

Compartilhe

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.

Imersão dev Back-end: mergulhe em programação hoje, com a Alura e o Google Gemini. Domine o desenvolvimento back-end e crie o seu primeiro projeto com Node.js na prática. O evento é 100% gratuito e com certificado de participação. O período de inscrição vai de 18 de novembro de 2024 a 22 de novembro de 2024. Inscreva-se já!

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 testessua influência no designno 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!

Veja outros artigos sobre Inovação & Gestão