Como você testa uma função cujo único objetivo é consultar uma API externa, mas a API usa uma sintaxe de consulta complexa?

16

A única lógica real está na sintaxe da consulta para a API externa. Eu não quero testar se ele consulta a API, quero testar se ele a consulta de forma que os dados corretos sejam retornados. Por exemplo, alguns pseudo-códigos:

function retrieve_related_data(id)
{
  query = "[potentially long, syntactically complex query that
            uses param id to get some data]";
  results = api_wrapper.query(query);
  return results;
}

Um exemplo mais concreto com uma API inventada:

function retrieveLifeSupportingObjectsWithinRegion(id)
{
  query = "
    within region(" + id + ") as r
    find objects matching hydration>0 and temp_range has 75
    send name, id, relative(position, r)        
  ";
  results = astronomicalObjectApiWrapper.query(query);
  return results;
}

A consulta está em uma sintaxe personalizada da API e é complexa e há várias maneiras de obter resultados iguais ou semelhantes. O objetivo da função não é obter dados identificados por, idmas encontrar um subconjunto de outros dados com base em um relacionamento difuso com os dados identificados por idque também atende a alguns outros requisitos. Os outros requisitos são sempre os mesmos, independentemente, idmas podem mudar com o tempo à medida que o sistema é modificado. Por exemplo, se a API de exemplo adicionou suporte para informações de gravidade, podemos alterar a consulta para também usar a gravidade para refinar os resultados. Ou talvez tenhamos uma maneira mais eficiente de verificar o intervalo de temperatura, mas isso não altera os resultados.

O que eu quero testar é que, para uma determinada entrada, ido conjunto correto de dados seja retornado. Desejo testar isso para que, se alguém atrapalhar a consulta de forma que não retorne mais os dados corretos com base na idfalha, mas também quero que as pessoas possam modificar a consulta para refiná-la sem precisar modificar também o teste.

Opções que eu considerei:

  1. Eu poderia stub a API, mas isso seria muito simples (verifique se idestá presente na consulta e, em seguida, retorne um conjunto de dados esperado, se for, ou um conjunto inesperado, se não for), muito frágil (verifique se a string de consulta está exatamente o que está na função), ou muito complexa (verificação de que a consulta usada é sintaticamente correto e irá resultar nos dados corretos estão sendo devolvidos).

  2. Eu poderia enviar a consulta para a API real, mas os resultados esperados podem mudar com o tempo, à medida que os dados no sistema externo são alterados, fora do controle do sistema de teste.

  3. Eu poderia olhar para configurar uma instalação de teste da API real, a fim de controlar os dados que ela possui, mas isso é muito esforço.

Estou me inclinando para a segunda posição e tornando isso mais um teste de integração que não é executado com frequência e vendo com que frequência as alterações nos dados do sistema externo fazem com que o teste seja interrompido. Eu acho que seria mais simples por enquanto, mas estou me perguntando se existem alternativas nas quais não estou pensando ou melhores maneiras de resolver esse problema. Qualquer conselho seria apreciado.

Joshua Coady
fonte
Eu estava pensando nisso como um teste de unidade, mas talvez seja realmente um teste de integração ou um teste de aceitação de baixo nível?
precisa
2
É uma API somente leitura? Ou você pode gravar dados nos quais pode verificar com segurança suas leituras ?
Svidgen #
Esta pergunta é diferente de "como testar se o meu sql (= sintaxe complexa) retorna os dados corretos"? Com bancos de dados você geralmente tem alguns testes de integração que teste o crud-sql-sintaxe e um fasade repositório zombou para verificar BusinessLogic
k3b

Respostas:

7

Pode parecer que, ao validar a resposta da API externa, estaríamos testando nossa função, mas isso não seria totalmente verdadeiro. De alguma forma, estaríamos testando a API externa e o ambiente em que a API está sendo executada.

Nossos testes devem ser direcionados para garantir o comportamento esperado do código que escrevemos, e não do código de terceiros.

Até certo ponto, temos que confiar no bom funcionamento das APIs e bibliotecas nas quais confiamos. Por exemplo, geralmente não testamos os componentes da estrutura que implementamos.

Por que eu digo isso?

O que eu quero testar é que, para um determinado ID de entrada, o conjunto correto de dados seja retornado

O que seria testado aqui? Como você disse, os dados e sua correção não estão sob nosso controle; portanto, estaríamos restringindo o sucesso da fase de teste a um agente externo que não temos nenhum controle. Esses testes são candidatos a tornarem - se não determinísticos e, definitivamente, não queremos esse tipo de teste em nosso pipeline de construção .

Uma preocupação diferente é validar o contrato. Eu consideraria bastante útil um teste de contrato 1 para garantir que a integração ainda funcione conforme o esperado, antes de qualquer release ou implantação.

Desejo testar isso para que, se alguém atrapalhar a consulta, de modo que não retorne mais os dados corretos com base no ID, que falhará

E se a consulta estiver ok, mas os dados estiverem incorretos devido a erros na API? Não apenas os dados estão fora de nosso controle. A lógica também é.

A implementação de testes funcionais ou de ponta a ponta pode ajudar aqui. Você pode abordar esses testes para validar certos caminhos de execução, de modo que, se as APIs retornarem dados incorretos, isso provavelmente causará comportamentos e saídas inesperados. Por outro lado, eu esperaria que a API gerasse erros se minhas consultas tivessem um formato incorreto.

Mas também quero que as pessoas possam modificar a consulta para refiná-la sem precisar também modificar o teste.

Sugiro implementar uma ferramenta para esse fim. Pode ser tão simples quanto:

  • Uma classe que é executada como teste, mas não pertence ao plano de teste
  • Um script de shell + ondulação

Ou algo mais sofisticado. Por exemplo, um cliente autônomo.

De qualquer forma, a função em questão vale bem dois tipos de testes:

  • Teste de unidade. Como você disse, você precisa stubb da API externa, mas esse é o objetivo de testes de unidade. Testando nosso código isolando as dependências.

  • Teste de integração. Verifique se o código não apenas envia a solicitação correta, mas também lida com o conteúdo da resposta, erros, redirecionamentos etc. Faça testes para todos esses casos, mas não os dados .

Nota lateral: Sua pergunta é semelhante a como você testa as instruções SQL do aplicativo?

Questões relacionadas :


1: você pode estar interessado na resposta da @ DocBrown sobre este tópico

Laiv
fonte
"O problema (IMO) é que você está muito focado em testar a API externa". - Não vejo nada indicando que o solicitante esteja interessado em testar a API externa. Além disso, você diz "stub the API externo", mas você tem alguma sugestão sobre se o solicitante deve usar a opção "muito simples", a opção "muito quebradiça", a opção "muito complexa" ou alguma quarta opção?
precisa
A questão do OP é perguntar como testar uma função que chama uma API externa. Mas, lendo as suas dúvidas, parece-me que ele coloca muita ênfase no teste da consulta e de seus resultados. Fiz 4 sugestões: (1) não faça teste da API. (2) não use os testes de integração como ambiente de trabalho para ajustar a consulta. Faça uma ferramenta. (3) de volta à questão principal, faça seu teste unitário e de integração. Mas não validando o conteúdo da resposta da API. (4) Pergunte ao gerente de projeto se ele deve / pode fazer um conjunto de testes da API externa como parte do plano de teste do projeto.
`` Mariv '27
1
Considero a consulta em si como "código escrito por nós". O objetivo final é o teste automatizado para nos alertar se introduzirmos um bug em nosso código. Levando isso ao que você disse sobre instruções SQL, acho que é semelhante a isso - acho que minha pergunta é: como você testa se seu código está consultando uma API externa de uma maneira que a API responderá como pretendido (assumindo uma resposta nominal). Acho que o que você está dizendo é deixar isso de fora dos testes de unidade e integração, mas se as consultas forem críticas, poderíamos configurar outros testes automatizados separadamente para testar a API externa ao vivo.
precisa
1
Na IMO, a melhor maneira de fazer um teste funcional, a mudança na cláusula where que nunca será verdadeira, causará um comportamento diferente em um ou mais dos meus testes funcionais. UT são apenas uma pequena parte do plano de teste.
LAIV
2
Obrigado por ser uma caixa de ressonância. Acho que, em última análise, mesmo que a consulta tenha lógica personalizada, ela está fora do domínio do teste de unidade, porque a consulta é um código que é "executado" fora do sistema que está sendo testado. Eu só precisava de alguém para me dizer que várias vezes de maneiras diferentes antes que eu vi ele;)
Joshua Coady
2

Eu vi verificações de unidade que verificam se a string de consulta gerada corresponde a um valor esperado.

Contudo. Isso foi na minha opinião se uso limitado. A sintaxe da consulta era complicada, possivelmente com erros, portanto, havia A infinitas possibilidades de verificação e B, mesmo que a sequência fosse 'corretamente' gerada, resultados inesperados poderiam ser retornados no ambiente ativo.

Eu acho que você está certo em escolher sua opção 2. executar testes de integração na instância ao vivo.

Desde que não sejam destrutivos, esses são os primeiros testes que você deve escrever, pois capturam, embora não identifiquem a causa de qualquer erro.

A opção 3 'implantar uma instância de teste com dados fictícios' é superior. Mas isso não afeta a gravação do teste, pois você pode apontar os mesmos testes no servidor de teste se e quando se tornar um bom uso do tempo para implantar um.

Ewan
fonte
0

Depende da API, mas se possível, vá para a opção 3 (instância de teste particular).

O stubbing da API (opção 1) é a pior opção, pelas razões mencionadas, e seguir essa rota provavelmente fará mais mal do que bem (muito tempo perdido).

A execução na API real (opção 2) torna os testes difíceis e pouco confiáveis ​​e, após alguns falsos positivos, as pessoas param de usá-los. Não apenas os dados podem mudar, mas o serviço também pode estar inativo. Na minha opinião, isso é semelhante a não ter testes para as consultas e depender de testes de integração / sistema para encontrar os problemas. Dito isto, se os dados da API raramente mudam e a própria API está quase sempre ativa, essa pode ser uma opção viável. A maioria das APIs não se encaixa nessa descrição.

Eventualmente, tudo se resume à importância e complexidade dessas consultas: se houver mais do que algumas, e algumas delas forem complexas o suficiente para que você sinta necessidade de testá-las, eu investiria o esforço de configurar uma instância privada para teste. . Pagará por si mesmo, assim como outros testes de unidade.

Michal Tenenberg
fonte
Então, basicamente, você está dizendo que testes de unidade (# 1) e testes de integração (# 2) são prejudiciais? Embora o número 3 possa parecer o melhor entre eles, também pode ser o mais caro. Ele deve ser mantido sempre que a API for alterada. Sem o item 2, você não estará ciente de possíveis alterações de bugs na API real até que o aplicativo esteja em produção (tarde demais para tomar medidas). Ok, nº 1 parece uneeded porque não são linhas de código para testar ... hoje ... Amanhã, como sabe ...
LAIV
Estou dizendo que testes ruins são prejudiciais, definitivamente. Os testes esquisitos desperdiçam muito tempo e esforço e fazem com que as pessoas percam a fé nos testes de unidade como um todo. Testes que quebram com as alterações de implementação (nº 1) ou apenas aleatoriamente quando as alterações de dados (nº 2) não são bons testes.
Michal Tenenberg
Teste de integração não testa dados. É isso aí. Eles não podem interromper o teste, apenas validam a integração. Testar não é uma questão de fé, é uma questão de ter bons hábitos que agregam valor ao aplicativo
Laiv 04/04/19