Como escrevo testes contra um serviço eventualmente consistente?


17

Estou criando um serviço sobre o armazenamento de dados do Google App Engine, que é um armazenamento de dados eventualmente consistente. Para minha aplicação, isso está bem.

No entanto, estou desenvolvendo testes que fazem coisas como o objeto PUT e, em seguida, o objeto GET e a verificação de propriedades no objeto retornado. Infelizmente, como o armazenamento de dados é eventualmente consistente, esses testes simples não são reproduzíveis.

Como você testa um serviço eventualmente consistente?


2
Por que você está testando a expectativa de reprodutibilidade em relação a um serviço externo?

... e o que você está realmente tentando testar? seu código? ou do Google?

5
Estou testando todo o sistema. Ou seja, são testes de integração, não testes de unidade.
Doug Richardson

3
How can I reproducibly test an eventually consistent service? Você não pode. Você precisa remover a palavra "reproduzível" ou a palavra "eventualmente"; você não pode ter os dois.
Robert Harvey

1
Se, eventualmente, for consistente, reproduzível ou não, qualquer resultado será bem-sucedido. Você já disse que está bom para o seu aplicativo, então o que você está realmente testando? A eventualidade? A integração com o GAE? Seu código?
LAIV

Respostas:


16

Considere os requisitos não funcionais ao projetar seus testes funcionais - se o seu serviço tiver um requisito não funcional de "Consistente dentro de x (segundos / minutos / etc)", basta executar as solicitações PUT, aguarde x e, em seguida, as solicitações GET.

Nesse ponto, se os dados ainda não 'chegaram', você pode considerar que a solicitação PUT não é compatível com seus requisitos.


7

Você realmente deseja que seus testes sejam rápidos e consistentes. Se você começar a criar testes que ocasionalmente falhem devido a uma consistência eventual, ignorará o teste quando ele falhar e, de que serve?

Crie um serviço falso que lide com as solicitações PUT e GET, mas tenha uma operação adicional para torná-lo consistente. Seu teste é então:

datastore.do_put(myobj);
datastore.make_consistent();
validate(datastore.do_get(), myobj);

Isso permite que você teste o comportamento do seu software quando o GET recupera com êxito o objeto PUT. Também permite testar o comportamento do seu software quando o GET não encontra o objeto (ou o objeto correto) devido ao serviço ainda não ser consistente. Apenas deixe de fora a ligação para make_consistent().

Ainda vale a pena ter testes que interagem com o serviço real, mas eles devem ser executados fora do fluxo de trabalho de desenvolvimento normal, pois nunca serão 100% confiáveis ​​(por exemplo, se o serviço estiver inoperante). Esses testes devem ser usados ​​para:

  1. fornecer métricas, em média e no pior dos casos, entre um PUT e um GET subsequente, tornando-se consistente; e
  2. verifique se o seu serviço falso se comporta de maneira semelhante ao serviço real. Consulte https://codewithoutrules.com/2016/07/31/verified-fakes/

6

OK então. "O que você está testando" é a questão principal.

  • Estou testando minha lógica interna do que acontece, assumindo que o material do Google funcione

Nesse caso, você deve zombar dos serviços do Google e sempre retornar uma resposta.

  • Estou testando minha lógica pode lidar com os erros transitórios que eu sei que o Google produzirá

Nesse caso, você deve zombar dos serviços do Google e sempre retornar o erro transitório antes da resposta correta

  • Estou testando se meu produto realmente funcionará com o serviço real do google

Você deve injetar os serviços reais do Google e executar o teste. Mas! O código que você está testando deve ter a manipulação (nova tentativa) de Erro Transitório incorporada. Portanto, você deve obter uma resposta consistente. (a menos que o Google seja muito mal comportado)


+1 para a sugestão trocada - eu daria mais votos positivos para as opções adicionais, se pudesse.
Mcottle

6

Use um dos seguintes:

  • Após PUT, tente GET N vezes novamente até obter êxito. Falha se não houver sucesso após N tentar.
  • Dormir entre PUT e GET

Infelizmente, você precisa escolher valores mágicos (N ou duração do sono) para ambas as técnicas.


1
Você poderia esclarecer: essas alternativas são complementares? Eu acho que você quer dizer que são alternativas - e é assim que penso nelas. Mas talvez eu esteja errado.
Robin Green

1
Correto, eu quis que eles fossem alternativas.
Doug Richardson

2

Pelo que entendi, o armazenamento de dados do Google Cloud permite consultas fortemente consistentes e eventualmente consistentes .

O problema é que as consultas fortemente consistentes são bastante limitadas à taxa (algo com o qual você pode conviver durante o teste).

Uma possibilidade pode ser colocar suas consultas no armazenamento de dados em um wrapper que permita uma consistência forte para fins de teste.

Por exemplo, você poderia ter métodos chamados start_debug_strong_consistency()e end_debug_strong_consistency().

O método start criaria uma chave que pode ser usada como chave ancestral para todas as consultas subseqüentes, e o método final excluiria a chave.

A única alteração nas consultas reais que você está testando seria chamar setAncestor(your_debug_key)se essa chave existir.


1

Uma abordagem, que é legal em teoria, mas pode nem sempre ser prática, é tornar todas as operações de gravação no sistema em teste idempotentes . Isso significa que, supondo que seu código de teste teste as coisas em uma ordem seqüencial fixa, você pode tentar novamente todas as leituras e gravações individualmente até obter o resultado esperado, tentando novamente até que o tempo limite definido no código de teste seja excedido. Ou seja, faça a ação A1, tentando novamente se necessário até que o resultado seja B1, faça a ação A2, tentando novamente se necessário até que o resultado seja B2 e assim por diante.

Então você não precisa se preocupar em verificar as condições prévias das operações de gravação, porque as operações de gravação já as estão verificando e você apenas as tenta novamente até que sejam bem-sucedidas!

Use os mesmos tempos limite "padrão", tanto quanto possível, que podem ser aumentados se todo o sistema ficar mais lento e substituir os padrões individualmente ao tentar novamente operações particularmente lentas.


1

Um serviço como o Google App Engine Datastore é baseado na replicação de dados em vários pontos de presença espalhados globalmente (POP). Qualquer teste de integração para um serviço eventualmente consistente é realmente um teste da taxa de replicação desse serviço em seu conjunto de POPs. A taxa na qual o conteúdo é espalhado para cada POP em um determinado serviço não será a mesma para todos os POP dentro do serviço, dependendo de vários fatores, como o método de replicação e vários problemas de transporte da Internet - estes são dois exemplos que representam a maioria dos relatórios em qualquer serviço de armazenamento de dados eventualmente consistente (pelo menos essa foi a minha experiência enquanto eu trabalhava para uma CDN importante).

Para testar efetivamente a replicação de um objeto em uma determinada plataforma, você precisa definir o teste para solicitar o mesmo objeto recentemente colocado de cada um dos POPs do serviço. Estou sugerindo testar a lista de POPs uma a cinco vezes ou até que todos os POPs na sua lista de POPs relatem o objeto. Aqui está um conjunto de intervalos nos quais você pode ajustar o teste que você pode ajustar: 1, 5, 60 minutos, 12 horas, 25 horas depois de colocá-lo no armazenamento de dados. A chave é registrar os resultados em cada intervalo para posterior análise e análise, a fim de ter uma idéia da capacidade de um determinado serviço de replicar objetos globalmente. Frequentemente, os serviços de armazenamento de dados apenas puxam uma cópia local para um POP depois que ela é solicitada localmente [o roteamento é feito via protocolo BGP e é por isso que seu teste precisa solicitar o objeto de cada POP específico para que seja globalmente válido para uma determinada plataforma] . No caso do armazenamento de dados do Google, você deve configurar o teste para consultar um determinado objeto em "mais de 70 pontos de presença em 33 países"; você provavelmente precisará obter a lista de URLs de endereços específicos de POP no Suporte do Google [ref:https://cloud.google.com/about/locations/ ] ou se o Google estiver usando o Fastly para replicação, suporte rapidamente [ https://www.fastly.com/resources ].

Algumas vantagens deste método: 1) Você terá uma ideia da plataforma de replicação de um determinado serviço, conhecerá seus pontos fortes e fracos como um todo em uma escala global [como foi durante o teste de integração]. 2) Para qualquer objeto que você testar, você terá uma ferramenta disponível para aquecer o conteúdo [faça a primeira solicitação que cria a cópia em um determinado POP local] - fornecendo assim uma maneira de garantir que o conteúdo seja espalhado globalmente antes que seus clientes o solicitem. em qualquer lugar da terra.


0

Tenho experiência com o armazenamento de dados do Google App Engine. Rodando localmente, surpreendentemente, geralmente é mais "eventualmente" do que "consistente". O exemplo mais simples: crie uma nova entidade e, em seguida, recupere-a. Muitas vezes, nos últimos 5 anos, vi o SDK em execução local não encontrar a nova entidade imediatamente, mas depois de meio segundo.

No entanto, rodando contra os servidores reais do Google, eu não vi esse comportamento. Eles tentam fazer com que seu cliente do Datastore sempre seja executado no mesmo servidor do lado deles, portanto, geralmente quaisquer alterações são refletidas imediatamente nas consultas.

Meu conselho para os testes de integração é executá-los nos servidores reais e, provavelmente, você não precisará colocar nenhuma pesquisa ou atraso falso para obter seus resultados.


Embora isso seja conveniente, pode causar quebras sutis envolvendo vários servidores de aplicativos não sejam detectadas em seus testes de integração. Eu acho que eles tornaram o servidor local eventualmente consistente por um bom motivo!
Robin Green
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.