DEV Community

loading...

Vamos falar de dublês de teste (Test Double) para testes unitários

HenriquePalote
・6 min read

Introdução

Este artigo basicamente trata sobre a utilização de dublês em testes unitários (ou de unidade), de forma assertiva e controlada, visando abordar outros conceitos e seus usos, fugindo de usar apenas o popular Mock.

O que são os testes unitários

Testes unitários ou de unidades, são basicamente implementações de código que visam testar a implementação de funcionalidades da sua aplicação de forma unitária e isolada, minimizando o custo de execução e assegurando o funcionamento e a regra de negócio daquele "trecho" de código.

Mas o que seria unitário ou unidade, nesse caso?

Podemos entender unitário como uma pequena fração relevante do seu código. Comumente, é abordado que uma unidade do código possa ser cada metódo público de uma classe. Mas eu acredito, que podemos elevar um pouco esse conceito, podemos considerar uma unidade como um conjuto relevante de código (métodos, funções e classes) que atende uma funcionalidade do projeto e que seja interessante e válido de ser testado. Como exemplo: o código referente a adição de um post em uma lista de posts.

Agora, o que são esses dublês?

Como visto acima, teste unitários buscam testar isoladamente trechos de código. Sendo assim, para que isso seja possível e válido, devemos abstrair algumas dependências do código, sem realmente executá-las durante o teste.

Mas como assim?

Basicamente, nós devemos simular dependências que gerem maior complexidade ao método e que não sejam muito acopladas aquela funcionalidade. Ou dependências que não estão sobre o controle do nosso código, ou seja, externas. Exemplificando essas dependências:

  • Dependências que geram maior complexidade: uma classe de validação de uma estrutura de dados.
  • Dependências externas: apis de terceiros, bibliotecas, banco de dados e etc.

E onde entram esses dublês?

Os dublês de teste, ou popularmente conhecido no inglês como test doubles, são formas/conceitos usados para realizar a abstração/simulação das dependências da unidade que será testada. Martin Fowler, no seu artigo sobre TestDouble, lista como doubles: Dummy, Fake, Stubs, Spies e Mocks. Mas nesse artigo vamos entrar em detalhe mais sobre Spies, Stubs e Mocks.

Então, o que são os Spies?

Spies, não buscam propriamente simular uma dependência, mas sim "vigiá-la", permitindo guardar o comportamento dessa dependências. Algumas implementações de Spies podem simular ou não, o comportamento do seu alvo. Sendo assim, eles tendem a teste mais comportamental daquela dependência.

Um exemplo disso seria verificar se uma dependência foi chamada com os parâmetros x e y, ou se retornou o valor z.

E os Stubs?

Stub, é uma reimplementação da dependência, buscando criar uma dependência bastante proxima a original, mas que apenas não executa a complexidade dela. Um Stub não valida o comportamento da dependências, o seu foco é sobre o estado daquela execução. Ele deve estar preparado para gerar os efeitos colaterais que a dependência original possa gerar.

Um exemplo disso seria criar uma dependência de abstração da classe que adiciona um post ao banco de dados, onde, no lugar de executar a query e salvar o dado no banco de dados, apenas adiciona aquele dado a uma lista interna da implementação, simulando o banco de dados.

Agora, e os Mocks?

Os Mocks, imagino que são os mais conhecidos, mas muitas vezes abordados e implementados de forma diferente, algumas vezes sendo uma união dos outros dublês.

Um Mock é uma implementação que visa simular apenas um único caminho daquela dependência, o abstraindo completamente, forçando um retorno específico para aquela chamada. Nele é possível testar o comportamento da execução daquele método, como feito no Spy.

Um exemplo de Mock, é fazer com que o método de adicionar um post na classe de persistência de dados apenas retorne o valor esperado, sem executar nenhuma implementação disso, podendo ser uma exceção.

Mas e agora, o que usar?

Basicamente depende do que precisa ser assegurado no teste e do quão vantajoso será o uso daquele dublê. Ou seja, depende do desenvolvedor entender a necessidade daquele teste. Mas isso é uma explicação muito superficial pra quem está começando a entender o que são testes unitários. Por isso, vou me aprofundar um pouco mais sobre as formas de abordagens, mas sem definir uma receita de bolo ou responder diretamente a pergunta. Apenas tentar ajudar e mostrar o caminho na hora da escolha.

Nisso vamos abordar a "pegada" comportamental ou a verificação do estado daquela execução.

Comportamental

Ao meu ver, inicialmente, testar o comportamento de uma dependência é para casos em que seja estritamente necessário, ou seja, a única forma de testar aquela execução. Caso a unidade que você esteja testando, chame alguma dependência que não gere retorno ou não modifique o estado do contexto da unidade, a opção é testar o comportamento daquela dependência, como assegurar que ela foi realmente chamada com o parâmetro xyz.

Assim caberia um Spy, caso a execução daquela dependências não cause impacto no teste, como diminuir a performance do teste. Exemplo disso, seria uma classe que valide os dados de post, onde a implementação dela seja caso válido, não retornar nada e caso erro retornar uma exceção. Nesse caso, ao testar o fluxo do dado válido, a forma de assegurar que seu código executa a validação é a partir de um Spy, assegurando que a validação foi chamada da forma correta.

Já no caso de uma dependência que não retorna nada ou que não afeta o estado do contexto da unidade a ser testada, em que sua implementação seja custosa, como uma classe de persistência de dados no banco de dados, é oportuno utilizar um Mock, onde simulará a execução daquela dependência, permitindo assegurar o comportamento do código.

Verificação do Estado

Já em casos em que a dependência afete o contexto de execução da unidade a ser testada, imagino ser válido executar uma abordagem que tente deixar a execução do código o mais próximo do real.

No caso de uma dependência que seja custosa de ser executada, criar um Stub para simulá-la, como no caso de um método que salve no banco de dados um novo post e que retorne o id do mesmo.

Agora, no caso da execução não aumentar complexidade ao teste da unidade alvo, uma atitude interessante pode ser não simular aquela dependência, mas sim executá-la.

Nesses dois casos, nosso teste, não se preocuparia diretamente com a dependência, mas sim com o resultado da unidade em que estamos testando, já que essa dependência o impactará.

Evitar "Mockar" o máximo possível?

Eu acho que a abstração do Mock em si, deve ser usado com parcimônia, quando for relevante ser usada e não em todas as depências do trecho de código alvo do teste. Mas é válido destacar um pouco a complexidade de escrever um Stub inteiro de uma dependência, que será reaplicado para todos os casos e efeitos do seu código. Acredito ser o cenário ideal, mas acho bem mais complexo.

Nesse caso, acho pode ser válido aproveitar o que a ferramente de teste usada nos proporciona, como no Jest, uma opção mais fácil, é, a partir do mockImplementation, gerar uma implementação que simule aquele caso, ou até mesmo, utilizar um mockReturnValue retornando um valor fixo. Mas em nenhum dos dois casos, utilizar para testar o comportamento da dependência e sim, só criar uma simulação do caso necessário.

Mas, e evitar simular as dependências?

Essa já é uma questão que imagino ser relativa. Depende do projeto, do time e/ou desenvolvedor que irá implementar os testes. Simular ou não, tem suas vantagens e desvantagens.

Evitar simular as dependências de uma unidade, ajuda a deixar seu teste mais assertivo, pois estará realmente executando o mais próximo do real, fora que os impactos da dependência, serão causado durante a execução do teste. Mas isso irá deixar seu teste mais complexo e caso a implementação da dependência dê problema, não irá "quebrar" apenas o teste daquela dependência, mas sim, de tudo de depende dela. O que pode deixar o feedback do teste mais poluído.

Já simular todas as dependências, irá deixar seu teste mais simples, focado no objetivo de testar unicamente aquela unidade e talvez, mais legível. Mas não será tão assertivo, pois caso alguém mude a dependência e esqueça de garantir o impacto gerado nos códigos que dependem dela, provavelmente passará despercebido no teste.

Mocks Aren't Stubs

Em relação a questão de usar Mocks e simular ou não uma depedência, tem um artigo sensacional do Martin Fowler, Mocks Aren't Stubs, que irá aprofundar muito mais nesses assuntos. Nele será citado as metodologias (se é que posso chamar assim) mockist e classicist, que remetem bastante sobre essa discussão. Acho bastante interessante e recomendo a todos a leitura desse artigo.

Mas concluindo, acho devemos equilibrar cada uma das formas e como a maioria das metodologias, ferramenta e etc. desse mundo, devemos saber qual momento válido usar e adaptar para nossa necessidade.

Discussion (0)

Forem Open with the Forem app