Desbravando testes unitários
A idéia de escrever esse post surgiu após eu fazer uma apresentação sobre testes unitários na empresa que trabalho. Durante a apresentação, fiz um live code em que criei um endpoint na qual utilizei os conceitos, que irei abordar, para fazermos a isolação do componente. Gostaria muito de fazer uma aplicação aqui no blog, mas pode ser que fique muito tedioso fazer um passo a passo. Então, decidi fazer um resumo do que foi apresentado, com exemplos de código para melhor entendimento.
O que é um teste unitário?
Um teste unitário consiste em testar a menor parte do código que estamos desenvolvendo, que, no nosso caso, seriam os nossos métodos. Ele não é muito confiável, pois testamos somente a nossa lógica e não testamos a integração entre os componentes do sistema.
Algumas dúvidas que vejo sobre testes unitários são: como posso testar apenas uma parte do código quando ele depende de uma interface ou de uma classe específica?
Para atingir esse nível de isolamento, nós temos alguns conceitos que podemos utilizar:
Stub
Criamos um dublê de teste com os resultados esperados, assim podemos simular nossos casos específicos.
Aqui nesse exemplo eu criei a classe chamada PessoaFisica
:
Com a ajuda da biblioteca Mockery do Laravel criamos um stub para o método buscaDadosPorCpf, com o retorno que esperamos:
Mock
Tem as mesmas características do stub, mas podemos fazer asserções sobre seu comportamento, garantindo que um método será ou não chamado.
Nesse exemplo, queremos além de definir uma saída para o metodo, queremos também saber o seu comportamento. No teste abaixo, passamos o metodo time(1)
que nos diz que o método tem que ser chamado uma vez:
Dummies
Dummie é um objeto que criamos no teste que não precisamos indicar um retorno ou fazer asserções sobre o comportamento, ele serve apenas para satisfazer um parâmetro de uma classe ou de um método.
Na figura abaixo, temos uma dependência com o cep na nossa classe PessoaFisica
. Apesar de não usarmos no metódo que vamos testar, isso impacta na hora que formos instanciar a classe, pois devemos satisfazer essa depêdencia:
Então criamos o Dummie para satisfazer o que se pede no construtor da classe , $cep = \Mockery::mock(Cep::class)
. Com dummie criado, podemos apenas passá-lo no construtor:
Spies
O objetivo dos spies é fazer asserções sobre uma chamada de um método, em vez de fazer a asserção sobre o comportamento do objeto.
Para exemplificar, vou criar uma classe chamada “UsuarioService”:
Nós podemos criar um teste para verficar se o método “criarUsuario” é chamado com os argumentos corretos:
Com o Mockery criamos um spy para a classe UsuarioService. Em seguida, o método criarUsuario é chamado com os argumentos “Diego França” e “[email protected]”. Finalmente, o método shouldHaveReceived é usado para verificar se o método criarUsuario foi chamado com os mesmos argumentos uma única vez.
Fake
Os fakes têm a mesma funcionalidade de uma classe real, mas são usados apenas em testes. Um exemplo de fake é o banco de dados, no qual podemos criar um método como “salvar”, mas salvar apenas em memória(array). Dessa forma, temos a funcionalidade de um salvamento, mas somente naquele momento.
Para exemplificar, criei um “repository” para salvar um cadastro no banco de dados, mas nós não queremos que a nossa aplicação utilize o banco.
Para construir o fake, irei utilizar um array onde os dados serão armazenados na memória do computador:
Conclusão
Na primeira vez que você for fazer um teste unitário, pode passar na sua cabeça que esse teste não tem função alguma, já que muitas vezes emulamos os dados gerados. Mas gostaria de deixar uma experiência pessoal: o teste unitário ele não vai garantir que sua aplicação não irá quebrar, ele somente lhe mostra que seu algoritmo não tem erro. Por isso, é de EXTREMA importância aplicar os outro tipos de testes, como o end to end e o teste de exploração(não abordado neste artigo), pois eles sim garantem o funcionamento correto do sistema em geral.
Outra dica para quem está começando agora e quer escrever bons testes, é seguir este passo a passo que pode servir como base para você:
- Criar test data builders(Criação dados de entrada para nossos testes.);
- Criar nossos métodos de inicialização afim de não repertir código(tearDown e setUp);
- Criar testes com nomes que faz sentido;
- Criar boas asserções;
Todos esses conceitos que abordei estão disponíveis em todas as linguagens de programação.
Espero que este artigo possa ajudá-lo de alguma forma. Caso tenha alguma duvida crítica (sempre são muito bem vindas), deixe nos comentários.