Unidade de teste de um cliente de API e wrappers

14

Eu tenho andado em círculos, tentando descobrir a melhor maneira de testar a unidade de uma biblioteca cliente da API que estou desenvolvendo. A biblioteca possui uma Clientclasse que basicamente possui um mapeamento 1: 1 com a API e uma Wrapperclasse adicional que fornece uma interface mais amigável na parte superior da Client.

Wrapper --> Client --> External API

Escrevi pela primeira vez vários testes contra os dois Cliente Wrapper, efetivamente, apenas testando que eles encaminham para as funções apropriadas de qualquer operação ( Wrapperopera Cliente Clientopera em uma conexão HTTP). Comecei a me sentir desconfortável com isso, no entanto, porque sinto que estou testando a implementação dessas classes, e não a interface. Em teoria, eu poderia alterar as classes para ter outra implementação perfeitamente válida, mas meus testes falhariam porque as funções que eu esperava ser chamadas não estavam sendo chamadas. Isso soa como testes frágeis para mim.

Depois disso, pensei na interface das classes. Os testes devem verificar se as classes realmente fazem o trabalho que devem fazer, e não como o fazem. Então, como posso fazer isso? A primeira coisa que vem à mente é remover as solicitações externas da API. No entanto, estou nervoso por simplificar demais o serviço externo. Muitos dos exemplos de APIs stubbed que eu vi apenas fornecem respostas enlatadas, o que parece ser uma maneira muito fácil de testar apenas se seu código é executado corretamente na API falsa. A alternativa é zombar do serviço, o que é inviável e precisaria ser atualizado sempre que o serviço real mudar - isso parece exagero e perda de tempo.

Por fim, li isso de outra resposta nos programadores SE :

O trabalho de um cliente remoto da API é emitir determinadas chamadas - nem mais nem menos. Portanto, seu teste deve verificar se emite essas chamadas - nem mais nem menos.

E agora estou mais ou menos convencido - ao testar Client, tudo o que preciso é fazer as solicitações corretas para a API (é claro, sempre há a possibilidade de a API mudar, mas meus testes continuam a passar - mas isso é onde os testes de integração seriam úteis). Como Clienté apenas um mapeamento 1: 1 com a API, minha preocupação antes sobre a alteração de uma implementação válida para outra não se aplica realmente - há apenas uma implementação válida para cada método de Client.

No entanto, ainda estou preso à Wrapperturma. Eu vejo as seguintes opções:

  1. Eu esqueço a Clientclasse e apenas testo se os métodos apropriados são chamados. Dessa forma, estou fazendo o mesmo que acima, mas tratando Clientcomo um substituto para a API. Isso me coloca de volta onde comecei. Mais uma vez, isso me dá a sensação desconfortável de testar a implementação, não a interface. A Wrapperpoderia muito bem ser implementado usando um cliente completamente diferente.

  2. Eu crio uma zombaria Client. Agora eu tenho que decidir até onde ir com a zombaria - criar uma zombaria completa do serviço exigiria muito esforço (mais trabalho do que foi feito na própria biblioteca). A API em si é simples, mas o serviço é bastante complexo (é essencialmente um armazenamento de dados com operações nesses dados). E, novamente, terei que manter minha zombaria sincronizada com o real Client.

  3. Acabei de testar se as solicitações HTTP apropriadas estão sendo feitas. Isso significa que a Wrapperchamada será feita através de um Clientobjeto real para fazer essas solicitações HTTP, portanto, na verdade, não estou testando isso isoladamente. Isso faz com que seja um teste de unidade terrível.

Portanto, não estou particularmente feliz com nenhuma dessas soluções. O que você faria? Existe um caminho certo para fazer isso?

Joseph Mansfield
fonte
Costumo evitar testes de unidade nesses cenários em que uma biblioteca de terceiros faz a maior parte do trabalho pesado e eu apenas tenho um invólucro (principalmente porque não tenho idéia de como fazer isso de uma maneira que teste algo realmente significativo). Geralmente faço testes de integração nesses casos, possivelmente com um serviço simulado. Talvez alguém saiba como fazer um teste de unidade realmente significativo para isso - eu tendem a priorizar os componentes mais críticos do sistema sob nosso controle. Aqui a parte crítica está fora de nosso controle. :-(

Respostas:

10

TLDR : Apesar da dificuldade, você deve stub o serviço e usá-lo para testes de unidade do cliente.


Não tenho tanta certeza de que "o trabalho de um cliente remoto da API seja emitir determinadas chamadas, nem mais, nem menos ...", a menos que a API consista apenas em terminais que sempre retornam um status fixo e não consomem nem produzem quaisquer dados. Essa não seria a API mais útil ...

Você também deseja verificar se o cliente não apenas envia as solicitações corretas, mas também que ele lida adequadamente com o conteúdo da resposta, erros, redirecionamentos etc. E teste para todos esses casos.

Como você observa, você deve ter testes de integração que cubram toda a pilha do wrapper -> client -> service -> DB e além, mas para responder à sua pergunta principal, a menos que você tenha um ambiente no qual os testes de integração possam ser executados como parte de cada Com a criação de IC sem muitas dores de cabeça (bancos de dados de teste compartilhados etc.), você deve investir tempo na criação de um stub da API.

O stub permitirá criar uma implementação funcional do serviço, mas sem a necessidade de implementar nenhuma camada abaixo do próprio serviço.

Você pode considerar o uso de uma solução baseada em DI para fazer isso, com uma implementação do padrão Repository abaixo dos recursos REST:

  • Substitua todo o código funcional nos manipuladores REST pelas chamadas para um IWhateverRepository.
  • Crie um ProductionWhateverRepository com o código extraído dos recursos REST e um TestWhateverRespository que retorne respostas em lata para uso durante o teste de unidade.
  • Use o contêiner de DI para injetar a DLL / classe ProductionWhateverRepository ou TestWhateverRepository, etc., dependendo da configuração.

De qualquer forma, a menos que desconsiderar e / ou refatorar o serviço esteja fora de questão, política ou praticamente, eu provavelmente adotaria algo semelhante ao acima. Se não for possível, eu me certificaria de ter uma cobertura de integração realmente boa e as executaria o mais rápido possível, de acordo com a configuração de teste disponível.

HTH

Dan1701
fonte