Skip to main content

Microsserviço 9

· 9 min read
Leandro Andrade
Leandro Andrade
Software Developer

O mundo dos testes avançou significativamente nos últimos anos, mas testar funcionalidades em nosso código de modo eficaz e eficiente continua sendo um desafio em sistemas distribuídos.

Entender os diferentes tipos de testes que podemos executar é importante para ter o equilíbrio entre forças ocasionalmente opostas entre entregar o software em produção o mais rápido possível e garantir que tenha uma qualidade suficiente.

Podemos classificar os testes como:

  • testes de aceitação: construímos o software correto?
  • testes exploratórios: como posso causar falhas no sistema?
  • testes de unidade: construímos o software corretamente?
  • testes de propriedades: tempo de resposta, escalabilidade, desempenho, segurança.

A grande maioria desses testes possuem o foco de validar o software antes de entrar em produção. A quantidade exata de cada teste dependerá muito do contexto, complexidade e natureza do sistema. Mas, caso o testes estejam sendo feitos manualmente, é melhor mudar esse cenário antes de se direcionar aos microsserviços, para que seja possível validar o software com rapidez e eficiência. Logo, utilizar um conjunto de ferramentas que permitam automatizar processos manuais é de extrema importância.

Obvio que não deve ser encarado como não ter testes manuais, mas sim eliminar tarefas repetitivas. Testes manuais estão mais relacionados à descobertas e podem ser utilizados quando o custo de escrever um teste automatizado se torna impraticável.

escopo dos testes

O modelo original de Cohn separa os testes em:

  • testes de UI;
  • testes de serviço;
  • testes de unidade;

Olhando de baixo para cima, à medida que subimos, o escopo do teste aumenta. Olhando de cima para baixo, à medida que descemos, temos testes sendo executados mais rápidos com um ciclo de feedback mais curto.

teste de unidade

Testa uma unidade do sistema, por exemplo, uma chamada de função. O TDD é o modelo que se enquadra nessa categoria. Geralmente, a maior quantidade de testes estão nessa categoria, já que são extremamente rápidos, possuem feedback curto, permite a refatoração com mais segurança e conseguimos capturar a maioria dos bugs.

testes de serviço

Ignora a interface do usuário e testa diretamente o microsserviço. A falha no teste ficará limitada somente ao microsserviço em teste e, para isso, isolamos componentes externos com stubs para que somente o microsserviço em questão esteja no escopo.

A ideia de stubs é ter um serviço que envia respostas prontas para as requisições conhecidas feitas pelo microsserviço em teste, não importando a quantidade de requisições feitas. Um stub pode ser um serviço simples iniciado pela linha de comando.

Existe também os mocks, que diferente dos stub que retornam respostas prontas, os mocks simulam o comportamento de um item. Logo, possuem um escopo maior que o teste de unidade.

teste fim a fim

São os testes executados em todo o sistema, simulando o comportamento do usuário. O ponto de atenção com esse tipo de teste é o quão custoso pode ser, já que podemos precisar implantar vários microsserviços juntos, e então executar um teste que envolva todos eles.

Devemos encontrar o equilíbrio entre fazer ou não fazer esse tipo de teste já que, como são testes mais demorados, afetam diretamente o feedback e a produtividade dos desenvolvedores, a correção de qualquer falha será mais lenta e diminui a capacidade de lançar alterações pequenas. Nesse cenário, podem acabar trazendo mais danos do que vantagens.


O que estamos procurando com os diferentes tipos de testes é chegar em um equilíbrio razoável já que queremos feedback rápido e confiança de que nosso sistema funciona como esperado.

Uma abordagem com o escopo de teste seria, caso um teste de escopo maior falhe, escreva um teste com escopo menor para encontrar mais rápido o problema. Vale a pena substituir testes de escopo maior por testes de escopo menor.

Agora, à medida que o escopo dos testes aumenta, também aumenta a quantidade de partes envolvidas. Essas partes envolvidas podem introduzir falhas nos testes que não mostram um erro na funcionalidade em si, mas sinalizam que algum outro problema ocorreu, por exemplo, executamos os testes mas algum dos microsserviços envolvidos está inativo. Iremos receber uma falha que não possui relação com a natureza do teste em questão.

Quando mais partes envolvidas, mais frágeis e menos determinísticos poderão ser os nossos testes. Testes que falham de vez em quando são testes frágeis. Devemos nos esforçar ao máximo para eliminar esse tipo de comportamento, caso contrário perderemos a confiança na suite de teste e chegaremos na normalização do desvio já que com o tempo, podemos ficar acostumados com algo errado a ponto de começarmos a aceitá-lo como sendo normal, e não como um problema.

Assim, caso não possa corrigi-los, melhor remover da suíte de testes para poder lidar com eles.

info

Ambientes de teste não deveriam ser compartilhados. O ideal é que cada contexto tenha seu próprio ambiente de teste.

Metaversão

Como temos vários serviços relacionados e que funcionam juntos, pode sugir a ideia de versionar todos os sistemas com o mesmo número.

O problema é que, com isso, aceitamos a ideia de que alterar e implantar vários serviços de um só vez é aceitável. Assim, aumentaremos o acoplamento dos serviços que antes estavam separados, tornando-os cada vez mais entrelaçados.

A implantação se torna um caos, pois teremos de coordenar a implantação de vários microsserviços de uma só vez. Exatamente o oposto do que queremos ao adotar microsserviços, que é implantações independentes, autonomia para as equipes e lançamentos de softwares com mais eficácia.

No final, o que estamos procurando é garantir que, quando implantarmos um novo serviço em produção, nossas mudanças não causarão falhas aos consumidores. Os schemas podem até ajudar a identificar imcompatibilidades estruturais, mas não ajudam a identificar as imcompatibilidades semânticas, isto é, alterações no comportamento que causem falhas. Para esse cenário, precisamos de testes de contrato e os contratos orientados a consumidores.

Testes de contrato e CDC

O propósito dos testes de contrato é que uma equipe cujo microsserviço consuma um serviço externo escreve os testes que descrevem como ela espera que um serviço externo vá sem comportar.

Essa estratégia também pode ser usada pelo provedor do serviço externo, criando um cliente que consuma o próprio serviço. Assim, no momento do build, caso aconteça alguma falha no serviço cliente, ficará evidente que os consumidores externos serão impactados. Com isso, duas abordagens podem ser realizadas: ou corrige o problema, ou inicia-se uma discussão sobre a introdução de uma alteração que causará incompatibilidade.

Uma exemplo de ferramenta que pode ser usada para CDC (Consumer-Driven Contracts) é o pact, uma ferramenta de testes orientada a consumidores.

Excesso de microsserviços

À medida que o número de microsserviços aumentar, os desenvolvedores precisarão trabalhar com uma quantidade cada vez maior de microsserviços e a experiência pode começar a sofrer, pelo simples fato haver a necessidade de executar cada vez mais microsserviços localmente.

O ponto é que alguns conjuntos de tecnologia exigem mais recursos já de partida - microsserviços baseados em JVM. Entretanto, outros conjuntos de tecnologia podem resultar em microsserviços com menos uso de recursos ou com usos mais rápidos, talvez permitindo executar muito mais microsserviços localmente.

O ideal é executar localmente somente os microsserviços que realmente forem necessários para trabalhar. Caso o time que o desenvolvedor participe seja responsável por 6 microsserviços, o desenvolvedor deve ser capaz de executar os 6 microsserviços do modo mais eficaz possível. Assim, caso algum desses microsserviços faça chamadas externas que estão fora do escopo do time, stubs precisam ser criados. Os únicos microsserviços reais que devem ser executados localmente são aqueles nos quais o desenvolvedor trabalha.

Testes em produção

O foco principal dos testes é executar uma série de modelos para nos dar a segurança e garantir que o sistema funciona e se comporta como esperado, tanto do ponto de vista funcional como não funcional. Gostaríamos de saber se há algum problema com nosso software antes que um usuário final experimente o problema.

Mesmo com esses testes antes da implantação, não é possível reduzir as chances de falha a zero.

Podemos e devemos aplicar testes em um ambiente de produção. Isso pode ser feito de forma segura e fornecerá um feedback de melhor qualidade do que os testes pré-produção. Os tipos de teste que podemos executar em produção são:

  • ping: verificar se o serviço está ativo;
  • smoke test: executado como parte das atividades de implantação e garante que o software implantado funciona corretamente;
  • canary release: lança um nova versão do software para uma pequena parcela dos usuários com o intuito de "testar" se ele funciona corretamente;

Claro que, decidindo fazer testes em produção, é importante que os testes não causem problemas no ambiente, seja gerando instabilidade, seja corrompendo os dados.

Evidente que também devem existir meios de realizar um rollback rápido caso algum problema seja evidenciado no ambiente de produção a fim de reduzir o impacto para os clientes.

Testes multifuncionais

Além dos testes funcionais, que diz respeito a funcionalidades no sistema, também temos que realizar os testes não funcionais, que são características do microsserviço que não podem ser implementadas simplemente como uma funcionalidade, por exemplo:

  • latência aceitável;
  • número de usuários;
  • nível de acessibilidade;
  • nível de proteção dos dados os clientes;

Requisitos não funcionais devem ser analisados o mais cedo possível e revisados com regularidade.

Muitos deles serão atendidos somente no ambiente de produção, mas podemos definir estratégias de teste que ajudem a verificar se estamos na direção correta a fim de atender esses requisitos não funcionais.

testes de desempenho

Identificar as causas de latência e extermamente importante. Em uma cadeia de chamadas, se uma parte dessa cadeia ficar lenta, tudo ficará lento.

Verifica as principais jornadas do microsserviço é extremamente importante. Para obter resultados relevante, cenários de testes com usuários simulados é uma abordagem que pode ser utilizada, observando o desempenho à medida que o número de usuários aumentam. A partir dos resultados, gargalos podem ser identificados e resultados observados.

testes de robustez

Robustez está relacionado à aumentar a confiabilidade do sistema, identificar o elo mais fraco. Podemos atingir os resultados, por exemplo, executando o microsserviço atrás de uma balanceador de carga para tolerar falha de uma instância, circuit breaks, além de recriar determinadas falhas para garantir que o microsserviço continuará funcionando como um todo.