Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
O teste ajuda a garantir que o código seja executado conforme o esperado, mas o tempo e o esforço para criar testes levam tempo longe de outras tarefas, como o desenvolvimento de recursos. Com esse custo, é importante extrair o valor máximo do teste. Este artigo discute os princípios de teste do DevOps, com foco no valor do teste de unidade e em uma estratégia de teste shift-left.
Testadores dedicados costumavam escrever a maioria dos testes, e muitos desenvolvedores de produtos não aprenderam a escrever testes de unidade. Escrever testes pode parecer muito difícil ou muito trabalho. Pode haver ceticismo sobre se uma estratégia de teste de unidade funciona, experiências ruins com testes de unidade mal escritos ou medo de que os testes de unidade substituam testes funcionais.
Para implementar uma estratégia de teste do DevOps, seja pragmático e concentre-se na construção de impulso. Embora você possa insistir em testes de unidade para um novo código ou código existente que possa ser refatorado de forma limpa, pode fazer sentido que uma base de código herdada permita alguma dependência. Se partes significativas do código do produto usarem SQL, permitir que os testes de unidade dependam do provedor de recursos SQL em vez de simular essa camada pode ser uma abordagem de curto prazo para o progresso.
À medida que as organizações de DevOps amadurecem, fica mais fácil para a liderança melhorar os processos. Embora possa haver alguma resistência à mudança, as organizações Agile valorizam as mudanças que pagam claramente dividendos. Deve ser fácil vender a visão de execuções de teste mais rápidas com menos falhas, pois significa mais tempo para investir na geração de novo valor por meio do desenvolvimento de recursos.
Taxonomia de teste do DevOps
Definir uma taxonomia de teste é um aspecto importante do processo de teste do DevOps. Uma taxonomia de teste de DevOps classifica testes individuais por suas dependências e pelo tempo necessário para serem executados. Os desenvolvedores devem entender os tipos corretos de testes a serem usados em diferentes cenários e quais testes são necessários para diferentes partes do processo. A maioria das organizações categoriza testes em quatro níveis:
- Os testes L0 e L1 são testes de unidade ou testes que dependem do código no assembly em teste e nada mais. L0 é uma ampla classe de testes rápidos de unidade na memória.
- L2 são testes funcionais que podem exigir o assembly mais outras dependências, como SQL ou o sistema de arquivos.
- Os testes funcionais L3 são executados em implantações de serviço testáveis. Essa categoria de teste requer uma implantação de serviço, mas pode usar stubs para as dependências de serviços chave.
- Os testes L4 são uma classe restrita de testes de integração executados em relação à produção. Os testes L4 exigem uma implantação completa do produto.
Embora seja ideal que todos os testes sejam executados o tempo todo, não é viável. O Teams pode selecionar onde no processo de DevOps executar cada teste e usar estratégias shift-left ou shift-right para mover diferentes tipos de teste anterior ou posterior no processo.
Por exemplo, a expectativa pode ser que os desenvolvedores sempre executem testes L2 antes de confirmar, uma solicitação de pull falhará automaticamente se a execução de teste L3 falhar e a implantação poderá ser bloqueada se os testes L4 falharem. As regras específicas podem variar de organização para organização, mas impor as expectativas para todas as equipes em uma organização move todos para os mesmos objetivos de visão de qualidade.
Diretrizes de teste de unidade
Defina diretrizes estritas para testes de unidade L0 e L1. Esses testes precisam ser muito rápidos e confiáveis. Por exemplo, o tempo de execução médio por teste L0 em um conjunto deve, em média, ser inferior a 60 milissegundos. O tempo médio de execução por teste L1 em um assembly deve ser inferior a 400 milissegundos. Nenhum teste nesse nível deve exceder 2 segundos.
Uma equipe da Microsoft executa mais de 60.000 testes de unidade em paralelo em menos de seis minutos. Seu objetivo é reduzir esse tempo para menos de um minuto. A equipe controla o tempo de execução do teste de unidade com ferramentas como o gráfico a seguir e registra bugs em testes que excedem o tempo permitido.
Diretrizes de teste funcional
Os testes funcionais devem ser independentes. O conceito chave para testes L2 é isolamento. Testes isolados corretamente podem ser executados de forma confiável em qualquer sequência, pois têm controle total sobre o ambiente em que são executados. O estado deve ser conhecido no início do teste. Se um teste tiver criado dados e deixado no banco de dados, ele poderá corromper a execução de outro teste que depende de um estado de banco de dados diferente.
Testes legados que precisam de uma identidade de usuário podem ter chamado provedores externos de autenticação para obter a identidade de usuário. Essa prática apresenta vários desafios. A dependência externa poderia ser não confiável ou estar temporariamente indisponível, interrompendo o teste. Essa prática também viola o princípio de isolamento de teste, pois um teste pode alterar o estado de uma identidade, como permissão, resultando em um estado padrão inesperado para outros testes. Considere evitar esses problemas investindo em suporte de identidade dentro da estrutura de teste.
Princípios de teste do DevOps
Para ajudar a fazer a transição de um portfólio de testes para processos modernos do DevOps, articule uma visão de qualidade. O Teams deve seguir os princípios de teste a seguir ao definir e implementar uma estratégia de teste do DevOps.
Deslocar para a esquerda para testar mais cedo
Os testes podem levar muito tempo para serem executados. À medida que os projetos são dimensionados, os números de teste e os tipos crescem substancialmente. Quando os conjuntos de testes aumentam e demoram horas ou dias para serem concluídos, eles podem ser adiados até o último momento. Os benefícios que os testes trazem para a qualidade do código só se tornam aparentes muito tempo depois que o código é enviado.
Testes de execução longa também podem produzir falhas que são demoradas para investigar. Os times podem desenvolver uma tolerância a falhas, especialmente nas etapas iniciais dos sprints. Essa tolerância prejudica o valor do teste como insights sobre a qualidade da base de código. Testes de última hora de longa duração também adicionam imprevisibilidade às expectativas de fim de sprint, pois uma quantidade desconhecida de dívida técnica deve ser paga para tornar o código em condição de ser entregue.
A meta ao deslocar o teste para a esquerda é mover a garantia de qualidade para etapas anteriores executando tarefas de teste mais cedo no pipeline. Por meio de uma combinação de melhorias de teste e processo, a mudança para a esquerda reduz o tempo necessário para que os testes sejam executados e o impacto das falhas mais adiante no ciclo. O deslocamento para a esquerda garante que a maioria dos testes seja concluída antes que qualquer alteração seja integrada ao branch principal.
Além de transferir determinadas responsabilidades de teste para etapas anteriores para melhorar a qualidade do código, as equipes podem mudar outros aspectos de teste para etapas posteriores no ciclo de DevOps, para melhorar o produto final. Para obter mais informações, consulte Shift-right para testar em produção.
Escrever testes no nível mais baixo possível
Escreva mais testes de unidade. Favoreça testes com menos dependências externas e concentre-se na execução da maioria dos testes como parte do build. Considere um sistema de build paralelo que pode executar testes de unidade para um assembly assim que o assembly e os testes associados forem removidos. Não é viável testar todos os aspectos de um serviço nesse nível, mas o princípio é usar testes de unidade mais leves se eles puderem produzir os mesmos resultados que testes funcionais mais pesados.
Visar a confiabilidade do teste
Um teste não confiável é organizacionalmente caro de manter. Esse teste funciona diretamente contra a meta de eficiência de engenharia, tornando difícil fazer mudanças com confiança. Os desenvolvedores devem ser capazes de fazer alterações em qualquer lugar e rapidamente ganhar confiança de que nada foi quebrado. Mantenha um alto padrão de confiabilidade. Desestimule o uso de testes de interface do usuário, pois eles tendem a não ser confiáveis.
Gravar testes funcionais que podem ser executados em qualquer lugar
Os testes podem usar pontos de integração especializados projetados especificamente para habilitar o teste. Um dos motivos para essa prática é a falta de testabilidade no próprio produto. Infelizmente, testes como esses geralmente dependem do conhecimento interno e usam detalhes de implementação que não importam de uma perspectiva de teste funcional. Esses testes são limitados a ambientes que têm os segredos e a configuração necessários para executar os testes, o que geralmente exclui implantações de produção. Os testes funcionais devem usar apenas a API pública do produto.
Projetar produtos para testabilidade
As organizações em um processo de DevOps em amadurecimento têm uma visão completa do que significa entregar um produto de qualidade em uma cadência de entrega em nuvem. Mudar o equilíbrio fortemente em favor do teste de unidade em vez de testes funcionais requer que as equipes façam escolhas de design e implementação que dão suporte à testabilidade. Há ideias diferentes sobre o que constitui código bem projetado e bem implementado para testabilidade, assim como há diferentes estilos de codificação. O princípio é que o design para testabilidade deve tornar-se uma parte primária da discussão sobre o design e a qualidade do código.
Tratar o código de teste como código do produto
Afirmar explicitamente que o código de teste é o código do produto deixa claro que a qualidade do código de teste é tão importante para o envio quanto a do código do produto. As equipes devem tratar o código de teste da mesma forma que tratam o código do produto e aplicar o mesmo nível de cuidado ao design e à implementação de testes e estruturas de teste. Esse esforço é semelhante ao gerenciamento de configuração e infraestrutura como código. Para ser completa, uma revisão de código deve considerar o código de teste e exigir o mesmo nível de qualidade que o código do produto.
Usar a infraestrutura de teste compartilhado
Facilite o uso da infraestrutura de teste para gerar sinais confiáveis de qualidade. Exiba o teste como um serviço compartilhado para toda a equipe. Armazene o código de teste de unidade junto com o código do produto e compile-o com o produto. Os testes executados como parte do processo de build também devem ser executados em ferramentas de desenvolvimento, como o Azure DevOps. Se os testes puderem ser executados em todos os ambientes, desde o desenvolvimento local até a produção, eles terão a mesma confiabilidade que o código do produto.
Tornar os proprietários de código responsáveis pelo teste
O código de teste deve residir ao lado do código do produto em um repositório. Para que o código seja testado em um limite de componente, a responsabilidade pelo teste deve ser direcionada para a pessoa que escreve o código do componente. Não confie em outras pessoas para testar o componente.
Estudo de caso: Shift para a esquerda com testes de unidade
Uma equipe da Microsoft decidiu substituir seus conjuntos de testes herdados por testes de unidade modernos de DevOps e um processo de shift-left. A equipe acompanhou o progresso em sprints tri-semanais, conforme mostrado no grafo a seguir. O gráfico abrange os sprints 78-120, os quais representam 42 sprints ao longo de 126 semanas, o que totaliza cerca de dois anos e meio de esforço.
A equipe começou com 27 mil testes legados no sprint 78 e atingiu zero nos testes legados no S120. Um conjunto de testes de unidade L0 e L1 substituiu a maioria dos testes funcionais antigos. Novos testes L2 substituíram alguns dos testes e muitos dos testes antigos foram excluídos.
Em uma jornada de software que leva mais de dois anos para ser concluída, há muito a aprender com o processo em si. No geral, o esforço para refazer completamente o sistema de teste ao longo de dois anos foi um investimento maciço. Nem todas as equipes de funcionalidades fizeram o trabalho ao mesmo tempo. Muitas equipes em toda a organização investiram tempo em cada sprint, e em alguns sprints foi a maior parte do que a equipe fez. Embora seja difícil medir o custo da mudança, era um requisito inegociável para as metas de qualidade e desempenho da equipe.
Como começar
No início, a equipe deixou os testes funcionais antigos, chamados de testes TRA, sem alterações. A equipe queria que os desenvolvedores acreditassem na ideia de escrever testes de unidade, especialmente para novos recursos. O foco era tornar o mais fácil possível criar testes L0 e L1. A equipe precisava desenvolver essa funcionalidade primeiro e criar impulso.
O gráfico anterior mostra a contagem de testes de unidade começando a aumentar cedo, pois a equipe viu o benefício de elaborar testes de unidade. Os testes de unidade eram mais fáceis de manter, mais rápidos de serem executados e tinham menos falhas. Foi fácil conseguir suporte para executar todos os testes de unidade no fluxo de pull request.
A equipe não se concentrou em escrever novos testes L2 até o sprint 101. Enquanto isso, a contagem de testes do TRA diminuiu de 27.000 para 14.000 entre a Sprint 78 e a Sprint 101. Novos testes de unidade substituíram alguns dos testes de TRA, mas muitos foram simplesmente excluídos, com base na análise da equipe de sua utilidade.
Os testes de TRA saltaram de 2100 para 3800 no sprint 110 porque mais testes foram descobertos na árvore de origem e adicionados ao grafo. Aconteceu que os testes sempre estiveram sendo executados, mas não estavam sendo rastreados corretamente. Esta não foi uma crise, mas era importante ser honesto e reavaliar conforme necessário.
Aumentando a velocidade
Depois que a equipe tinha um sinal de CI (integração contínua) extremamente rápido e confiável, tornou-se um indicador confiável para a qualidade do produto. A captura de tela a seguir mostra o pull request e o pipeline de CI em ação, além do tempo necessário para passar por várias fases.
Leva cerca de 30 minutos desde a pull request até a mesclagem, incluindo a execução de 60.000 testes de unidade. Da mesclagem de código ao build de CI é de cerca de 22 minutos. O primeiro sinal de qualidade da CI, SelfTest, vem após cerca de uma hora. Em seguida, a maior parte do produto é testada com a alteração proposta. Dentro de duas horas após o processo de "Merge to SelfHost", todo o produto é testado e a alteração está pronta para entrar em produção.
Usando métricas
A equipe rastreia um scorecard como o exemplo a seguir. Em um nível geral, o scorecard acompanha dois tipos de métricas: saúde ou dívida e velocidade.
Para métricas de integridade do site ao vivo, a equipe controla o tempo para detectar, o tempo para atenuar e quantos itens de reparo uma equipe está carregando. Um item de reparo é o trabalho que a equipe identifica em uma retrospectiva de site em operação para evitar que incidentes semelhantes sejam recorrentes. O scorecard também monitora se as equipes estão concluindo os itens de reparo dentro de um período razoável.
Para métricas de saúde da engenharia, a equipe rastreia bugs ativos por desenvolvedor. Se uma equipe tiver mais de cinco bugs por desenvolvedor, a equipe deverá priorizar a correção desses bugs antes do novo desenvolvimento de recursos. A equipe também rastreia bugs antigos em categorias especiais, como segurança.
As métricas de agilidade de engenharia medem a velocidade em diferentes partes do pipeline de integração contínua e entrega contínua (CI/CD). A meta geral é aumentar a velocidade do pipeline de DevOps: começando por uma ideia, colocando o código em produção e recebendo dados de volta dos clientes.