O teste de software aumenta a qualidade e a confiabilidade do aplicativo. Ele permite que os desenvolvedores encontrem e consertem bugs de software, mitiguem problemas de segurança e simulem casos de uso de usuários reais.
É uma parte essencial do desenvolvimento de qualquer aplicativo.
Serverless é uma tecnologia incrível, quase mágica. Aplicativos serverless, como quaisquer outros aplicativos, exigem testes. No entanto, testar aplicativos serverless difere dos testes tradicionais e introduz novos desafios.
Nesta postagem do blog, você aprenderá por que os serviços sem servidor apresentam novos desafios de teste e minhas diretrizes práticas para testar serviços sem servidor e funções do AWS Lambda que atenuam esses desafios.
Parte dois ensinará como escrever testes para seu serviço Serverless. Vamos nos concentrar em funções Lambda e fornecer dicas e truques e exemplos de código escrevendo testes para um aplicativo Serverless real.
Na parte três , você aprenderá a testar fluxos assíncronos orientados a eventos que podem ou não conter funções Lambda e outros serviços Serverless não baseados em Lambda.
Um projeto de serviço Serverless complementar que utiliza as melhores práticas de teste Serverless pode ser encontrado aqui .
Índice
Os testes sem servidor são diferentes?
Bem, sim, e por bastante.
Antigamente, os desenvolvedores rodavam o código localmente, e ele simulava o comportamento do aplicativo muito bem. Na maioria dos casos, era fácil rodar o código no IDE, adicionar pontos de interrupção e depurar. Se funcionasse localmente, você tinha muita confiança de que ele rodaria muito bem depois de implantado na produção.
Em aplicações sem servidor, não é mais tão simples assim.
Perspectiva das funções Lambda
Vamos dar uma olhada nas funções do Lambda e nos desafios de teste que elas apresentam.
As funções do AWS Lambda são executadas na infraestrutura da AWS em um ambiente de contêiner efêmero e exigem diversas configurações de função.
Essas características apresentam vários desafios que exigem uma compreensão mais profunda do serviço Lambda:
Diferentes gatilhos de eventos - uma função Lambda pode ser disparada por três tipos de gatilhos de eventos (síncrono, assíncrono, baseado em pesquisa) e integra-se com muitos serviços da AWS. Cada evento tem um esquema e tipos de dados diferentes para o envelope de metadados e a carga útil de entrada do domínio de negócios. Mais sobre isso aqui .
Sem estado - Funções Lambda são executadas em contêineres efêmeros.
Variáveis de ambiente - variáveis de ambiente IDE locais são diferentes de funções Lambda configuradas. Essa discrepância pode causar bugs em tempo de execução e até mesmo travamentos.
Função - Lambdas são executados com uma função diferente das permissões do usuário do IAM do seu desenvolvedor. Essa discrepância faz com que o código seja executado ok localmente e falhe na nuvem devido a configurações incorretas de permissões da função.
Configurações de memória - A função Lambda é executada com uma RAM pré-configurada diferente da máquina do desenvolvedor. Além disso, quanto mais RAM você configurar, melhor será o desempenho da CPU que a AWS fornece à função .
Lambdas por trás de gateways de API são frequentemente protegidas por um Amazon WAF e autorizadores como o autorizador IAM, autorizador Cognito e autorizador personalizado. Essas funcionalidades são difíceis ou impossíveis de simular localmente.
Timeouts - As funções Lambda têm um valor padrão de timeout de 3 segundos , mas você pode defini-lo para até 15 minutos. Conforme seu aplicativo muda, o tempo total de execução da função pode aumentar também, e você pode precisar ajustar o valor do timeout.
Dependências externas - As funções Lambda geralmente são empacotadas como um arquivo ZIP e carregadas na AWS. Se um pacote estiver faltando, a função falhará durante a invocação imediatamente devido a erros de importação. As camadas Lambda são uma opção para empacotar dependências externas. No entanto, elas podem complicar ainda mais o caso de uso quando não usadas corretamente. Leia mais aqui .
Os tipos de arquitetura Lambda (X86 ou ARM64) afetam o desempenho e o custo e são, em muitos casos, diferentes do tipo de arquitetura de CPU da máquina de desenvolvimento. Essa discrepância pode causar problemas ao construir dependências externas para o arquivo ZIP ou camada da função Lambda.
Inicializações a frio/simultaneidade provisionada/simultaneidade reservada - são tópicos avançados que afetam o desempenho do aplicativo, não podem ser simulados localmente e são difíceis de simular em escala na AWS.
O ponto principal é que não há garantia de que o código do aplicativo que funciona localmente no seu IDE será executado no ambiente da AWS, muito menos que funcione corretamente e conforme o esperado.
Serverless é mais do que apenas funções Lambda
Allen Helton, AWS Serverless Hero, definiu o Serverless muito bem em sua postagem de blog:
"Quando digo serverless, estou geralmente me referindo aos serviços que os desenvolvedores usam para construir aplicativos. Exemplos são AWS Lambda, EventBridge, DynamoDB e Step Functions" - Allen Helton
Allen está certo. Você está obtendo serviços de caixa preta de primeira classe e de última geração que você coloca em seu quebra-cabeça de arquitetura. Da forma como vejo, Serverless é um sinônimo para arquitetura orientada a eventos construída sobre serviços gerenciados pela AWS e, normalmente, seu objetivo é passar um evento por uma cadeia de serviços até que ele atinja seu destino e forma finais.
Dessa forma, a arquitetura sem servidor introduz novos desafios de teste:
Você não pode testar cada peça do quebra-cabeça por si só, especialmente as partes gerenciadas pela AWS (SQS, SNS, funções Step, DynamoDB, etc.) e provavelmente não precisa. Você pode testar partes menores do quebra-cabeça, ou seja, as funções Lambda que você escreve, e pode testar o quebra-cabeça inteiro do evento inicial até o ponto final.
Configuração de segurança - você precisa entender como configurar corretamente os serviços AWS Serverless e manter as práticas recomendadas de segurança (criptografia em repouso e em trânsito etc.).
Configuração de infraestrutura e resiliência - os desenvolvedores podem alterar recursos e suas configurações; como sabemos que eles os configuraram corretamente? Como sabemos que os recursos ainda existem após a mudança?
Cotas de dimensionamento e recursos - Serviços sem servidor têm propriedades de dimensionamento integradas. No entanto, precisamos configurar seus limites em muitos casos para reduzir custos durante períodos de carga pesada e evitar limites de cota de recursos da AWS. Por exemplo, cada conta e região da AWS tem uma quantidade máxima de lambdas simultâneas , que são compartilhadas entre TODAS as funções lambda. Se duas funções lambda forem implantadas na mesma conta e região, uma função pode ser dimensionada drasticamente e causar inanição e limitação para a outra função (erro HTTP 429), pois a conta inteira atinge a cota máxima simultânea.
Caixa preta de funções intrínsecas - e se você usar pipes EventBridge ou Step Functions com funções intrínsecas? Você não pode simulá-los localmente. Como você pode ter certeza de que os configurou corretamente e que eles funcionarão conforme o esperado?
No entanto, apesar de todos esses desafios, há luz no fim do túnel.
Tudo tem uma solução, então não se preocupe e continue lendo.
Experiência do desenvolvedor e metas de garantia de qualidade
O objetivo principal é testar o máximo possível para aumentar a confiança na qualidade do aplicativo; no entanto, um objetivo secundário é que o desenvolvedor tenha a melhor experiência de desenvolvimento no domínio Serverless.
Quanto melhor a experiência, mais rápido o desenvolvimento.
Então, como desenvolvedor de aplicativos sem servidor, eu gostaria de:
Quero testar meu código localmente no meu IDE de escolha e poder adicionar pontos de interrupção. Quero ter a maior certeza possível de que minha função Lambda, que roda localmente perfeitamente, também rodará perfeitamente na minha conta AWS.
Quero definir minha infraestrutura como código (IaC) junto com meu código de aplicativo e testá-los juntos.
Quero ser independente durante meu desenvolvimento e não interferir no trabalho de outros desenvolvedores que também estejam trabalhando no mesmo aplicativo.
Quero automatizar todos os meus testes e eliminar a necessidade de testes manuais.
Diretrizes de teste sem servidor e Lambda da AWS
Estrutura do projeto de aplicativo sem servidor
Cada repositório de aplicativo conterá três pastas: código de serviço, pasta de infraestrutura como código (AWS CDK neste exemplo) e pasta de testes. Acredito que um desenvolvedor Serverless deve "possuir" a infraestrutura e ser capaz de defini-la e entender sua arquitetura.
app.py é o ponto de entrada do aplicativo CDK que implanta o código de infraestrutura e carrega a pasta de serviço com as funções Lambda.
Ambiente local das funções Lambda
Queremos simular o ambiente local das funções Lambda e dependências externas no IDE. Em Python, que é minha linguagem de escolha para funções Lamda, instalaremos todas as dependências em um ambiente virtual local.
Quanto ao gerenciador de dependências, você pode escolher entre poetry e pipenv. Eu prefiro poetry, pois é mais rápido, então todas as dependências das funções residirão juntas em um único arquivo .toml.
Todas as funções Lambda no projeto usam versões de biblioteca idênticas definidas no arquivo toml. Ao construir funções Lambda com dependências externas ou camadas lambda, construa-as de acordo com o arquivo .toml. Leia mais sobre isso aqui .
Independência de implantação do desenvolvedor
Queremos que nossos desenvolvedores possam trabalhar com contas e recursos reais da AWS sem interromper o trabalho de seus colegas.
Há duas soluções possíveis que conheço:
Conta AWS por desenvolvedor
Conta única para todos os desenvolvedores
Na primeira opção, cada desenvolvedor implanta seu aplicativo em sua conta. Cada desenvolvedor tem um sandbox para brincar, reduzindo a chance de atingir os limites de cota de recursos. No entanto, várias contas aumentam a sobrecarga de gerenciamento de contas.
A segunda opção é ter uma conta 'dev' da AWS compartilhada entre todos os desenvolvedores, mas cada desenvolvedor implanta sua pilha de aplicativos (pilha CloudFormation) com um prefixo de nome de usuário, removendo assim uma chance de conflitos e permitindo a liberdade completa dos desenvolvedores. Nesta opção, há mais chance de atingir cotas de recursos da AWS (em muitos casos, esses são limites "suaves" que você pode incrementar por um custo adicional), mas é mais fácil de gerenciar da perspectiva da empresa.
Escolha a opção que fizer mais sentido para você.
Depuração no IDE e entradas de eventos gerados
Acredito que a depuração no console da AWS deve ser sempre o último recurso.
Leva mais tempo porque você não tem pontos de interrupção, então você recorre à depuração com impressão de log, o que resulta em uma experiência ruim no geral.
Uma melhor experiência para o desenvolvedor seria escrever um teste que chamasse meu manipulador de função Lambda no meu IDE, enviasse a ele um evento predefinido (que correspondesse ao esquema da integração Lambda) e verificasse seus efeitos colaterais e resposta.
Tudo administrado localmente, simples e rápido.
Manteremos tudo simples; não usaremos simuladores Lambda nem a abordagem de depuração local do SAM, nem criaremos imagens docker locais, apenas testes IDE simples ('pytest' em Python) com eventos gerados e pontos de interrupção locais.
No entanto, isso levanta a questão, como você gera esses eventos? Vou deixar isso para a parte dois que cobrirá isso em detalhes. No entanto, se você quiser um spoiler, veja um exemplo aqui .
Não zombe dos serviços da AWS, a menos que seja necessário
A biblioteca Pythonic motto zomba dos serviços da AWS, removendo a necessidade de implantar seu aplicativo ou pagar por chamadas de API contra serviços da AWS. Outras linguagens de programação têm sua implementação de motto.
No entanto, todos eles têm uma coisa em comum: parecem ótimos no papel, mas, na minha experiência, você só deve usá-los se for necessário.
Deixe-me explicar o porquê: uma desvantagem de usar 'motto' é que quando você o usa para simular um serviço da AWS, ele força você a simular todos eles. Você não pode usar NENHUMA outra chamada de API de serviços reais da AWS. Outra desvantagem é que eu tropecei em instâncias em que a resposta do motto era diferente do negócio real. E sim, 'motto' pode ter bugs também.
Então, você deve usar 'motto' no seguinte caso de uso:
Não é fácil simular um caso de uso específico, ou você precisa saber como seria a resposta. Por exemplo, você quer usar a API de organizações da AWS para listar todas as contas na organização, e quer que a organização tenha 50 contas em 3 hierarquias. A menos que você mantenha essa organização real da AWS pronta para uso, um mock de 'motto' é a única maneira de simular esse caso de uso.
Depuração em IDE e API AWS
Chamar API de serviços AWS reais no IDE - continuando o último ponto, ao usar o AWS SDK ('boto' para Python) no Lambda, não faremos mock dele. Quero obter o máximo de certeza e usar serviços AWS reais quanto possível durante minhas sessões de depuração e testes locais.
É claro que esse método significa um aumento no custo geral e é considerado a principal desvantagem desse método (e da independência de implantação do desenvolvedor), pois toda a sua equipe de desenvolvimento implantará a infraestrutura e usará chamadas de API da AWS para interagir com ela.
O Serverless tem muitos benefícios de preço (na maioria dos casos, você paga apenas pelo tempo de execução real em milissegundos para Lambdas), e a AWS tem um nível gratuito, mas, eventualmente, isso pode aumentar.
Além disso, muitos recursos custam dinheiro para serem implantados; KMS CMK, VPCs e certificados vêm à mente.
No entanto, no grande esquema das coisas, usar APIs reais localmente com um ponto de interrupção e com facilidade aumenta a confiança da equipe em seu trabalho, cria menos conflitos e discrepâncias entre o código local e o código que roda em contas da AWS e acelera o desenvolvimento. O custo mínimo deve valer a pena se você usar serviços AWS Serverless puros.
Simular falhas de API
Aplique patch nas chamadas da AWS quando quiser simular exceções e erros para testar casos extremos no seu código e aumentara cobertura do código de linha .
Um modelo de serviço Serverless totalmente funcional que incorpora essas diretrizes pode ser encontrado aqui , e sua documentação aqui .
A segunda parte se concentrará nessa área com exemplos de código.
Acione um evento na sua conta da AWS
Além da depuração no IDE local, é fundamental também acionar o aplicativo implantado na AWS e verificar se ele funciona do início ao fim.
Use casos de uso reais de clientes e acione o início da arquitetura orientada a eventos, seja uma chamada de API REST, uma mensagem SNS ou qualquer outro evento.
As funções do Lambda e os recursos sem servidor que você implantou funcionarão juntos do início ao fim na sua conta da AWS.
Esses testes são os melhores, pois simulam casos de uso reais de clientes e entradas em suas contas da AWS.
Automatize tudo
Escreva testes para todos os casos de uso do cliente e casos extremos. Não deixe pedra sobre pedra. Não teste seu serviço manualmente. Você quer ganhar confiança em seus testes e capacitar seu desenvolvedor para ter mais responsabilidade, velocidade de desenvolvimento aprimorada e confiança em seu trabalho. Quando você tem uma boa cobertura, não tem medo de empurrar para a produção várias vezes por dia.
Continua - Escrevendo testes para um aplicativo serverless de exemplo
Agora que temos as diretrizes básicas, vamos colocá-las em prática na próxima parte dois da série, onde apresentarei a pirâmide de testes sem servidor junto com exemplos de código e muitas dicas e truques.
Comentarios