Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
O teste ajuda a garantir que o código tenha o desempenho 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 máximo valor dos testes. Este artigo discute os princípios de teste de DevOps, concentrando-se 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. Os testes de escrita podem parecer demasiado difíceis ou dar demasiado 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 os testes funcionais.
Para implementar uma estratégia de teste de DevOps, seja pragmático e concentre-se na criação de impulso. Embora você possa insistir em testes de unidade para código novo ou código existente que pode ser refatorado corretamente, pode fazer sentido para uma base de código herdada permitir alguma dependência. Se partes significativas do código do produto usam 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, torna-se mais fácil para a liderança melhorar os processos. Embora possa haver alguma resistência à mudança, as organizações ágeis valorizam mudanças que claramente pagam dividendos. Deve ser fácil vender a visão de testes mais rápidos com menos falhas, porque isso significa mais tempo para investir na geração de novo valor através do desenvolvimento de recursos.
Taxonomia de testes DevOps
Definir uma taxonomia de teste é um aspeto importante do processo de teste de DevOps. Uma taxonomia de teste de DevOps classifica testes individuais por suas dependências e pelo tempo que levam para serem executados. Os desenvolvedores devem entender os tipos certos de testes para usar em diferentes cenários e quais testes diferentes partes do processo exigem. A maioria das organizações categoriza os testes em quatro níveis:
- Os testes L0 e L1 são testes de unidade, ou testes que dependem do código no conjunto em teste e nada mais. L0 é uma ampla classe de testes de unidade rápidos 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. Esta categoria de teste requer uma implantação de serviço, mas pode usar stubs para dependências de serviços chave.
- Os testes L4 são uma classe restrita de testes de integração que sã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 em todos os momentos, não é viável. As equipes podem selecionar onde no processo de DevOps executar cada teste e usar estratégias shift-left ou shift-right para mover diferentes tipos de teste mais cedo ou mais tarde no processo.
Por exemplo, a expectativa pode ser que os desenvolvedores sempre executem testes L2 antes de confirmar, uma solicitação pull falhe automaticamente se a execução do teste L3 falhar e a implantação possa 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 dentro de uma organização move todos para os mesmos objetivos de visão de qualidade.
Diretrizes de testes unitários
Defina diretrizes rígidas para testes de unidade L0 e L1. Esses testes precisam ser muito rápidos e confiáveis. Por exemplo, o tempo médio de execução por teste L0 em uma montagem deve ser inferior a 60 milissegundos. O tempo médio de execução por teste L1 em uma montagem deve ser inferior a 400 milissegundos. Nenhum teste a este 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. O objetivo é reduzir esse tempo para menos de um minuto. A equipe rastreia o tempo de execução do teste de unidade com ferramentas como o gráfico a seguir e arquiva bugs em testes que excedem o tempo permitido.
Diretrizes de teste funcional
Os testes funcionais devem ser independentes. O conceito-chave para os testes L2 é o isolamento. Testes adequadamente isolados podem ser executados de forma confiável em qualquer sequência, porque eles 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 criou dados e os deixou no banco de dados, ele pode corromper a execução de outro teste que depende de um estado de banco de dados diferente.
Os testes herdados que precisam de uma identidade de usuário podem ter chamado provedores de autenticação externos para obter a identidade. Esta prática introduz vários desafios. A dependência externa pode não ser fiável ou estar indisponível momentaneamente, interrompendo o teste. Essa prática também viola o princípio de isolamento de teste, porque um teste pode alterar o estado de uma identidade, como permissão, resultando em um estado padrão inesperado para outros testes. Considere a prevenção desses problemas investindo em suporte de identidade dentro da estrutura de teste.
Princípios de teste de DevOps
Para ajudar na transição de um portfólio de testes para processos modernos de DevOps, articule uma visão de qualidade. As equipes devem aderir aos seguintes princípios de teste ao definir e implementar uma estratégia de teste de DevOps.
Vire 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 e tipos de testes crescem substancialmente. Quando os conjuntos de testes levam horas ou dias para serem concluídos, eles podem ir mais longe até serem executados no último momento. Os benefícios da qualidade do código através dos testes não são percebidos até muito depois de o código ser cometido.
Testes de longa duração também podem produzir falhas que são demoradas para investigar. As equipes podem criar uma tolerância para falhas, especialmente no início dos sprints. Essa tolerância prejudica o valor dos testes como informações sobre a qualidade da base de código. Testes de última hora e de longa duração também adicionam imprevisibilidade às expectativas de fim de sprint, porque uma quantidade desconhecida de dívida técnica deve ser paga para tornar o código pronto para envio.
O objetivo para mudar o teste para a esquerda é mover a qualidade para cima, executando tarefas de teste mais cedo no pipeline. Através de uma combinação de melhorias de teste e processo, a mudança para a esquerda reduz o tempo necessário para a execução dos testes e o impacto de falhas mais tarde no ciclo. Deslocar para a esquerda garante que a maioria dos testes seja concluída antes que uma alteração se funda na ramificação principal.
Além de antecipar certas responsabilidades de teste para melhorar a qualidade do código, as equipas podem realocar outros elementos de teste para fases posteriores no ciclo de DevOps, para melhorar o produto final. Para obter mais informações, consulte Shift right to test in production.
Escreva testes no nível mais baixo possível
Escreva mais testes de unidade. Favoreça testes com o menor número de dependências externas e concentre-se em executar a maioria dos testes como parte da compilação. Considere um sistema de construção paralelo que possa executar testes de unidade para um conjunto assim que o conjunto e os testes associados estiverem disponíveis. Não é viável testar todos os aspetos de um serviço neste 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 fiabilidade dos testes
Um teste não confiável é organizacionalmente caro de manter. Esse teste funciona diretamente contra o objetivo de eficiência de engenharia, dificultando a realização de alterações 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. Desencoraje o uso de testes de interface do usuário, porque eles tendem a não ser confiáveis.
Escreva testes funcionais que podem ser executados em qualquer lugar
Os testes podem usar pontos de integração especializados projetados especificamente para permitir o teste. Uma razão para esta prática é a falta de testabilidade no próprio produto. Infelizmente, testes como esses geralmente dependem de conhecimento interno e usam detalhes de implementação que não importam de uma perspetiva 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.
Projete produtos para testabilidade
As organizações num processo de DevOps em amadurecimento têm uma visão completa do que significa fornecer um produto de qualidade numa cadência de serviços em nuvem. Mudar o equilíbrio fortemente em favor do teste de unidade em detrimento do teste funcional exige que as equipes façam escolhas de projeto e implementação que suportem a estabilidade. Existem diferentes ideias sobre o que constitui um código bem concebido e bem implementado para a estabilidade, assim como existem diferentes estilos de codificação. O princípio é que projetar para testabilidade deve se tornar uma parte primária da discussão sobre design e qualidade de código.
Tratar o código de teste como código do produto
Declarar 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 mantê-lo na mesma barra de qualidade que o código do produto.
Usar infraestrutura de teste compartilhada
Reduza as exigências para utilizar a infraestrutura de teste e gerar sinais de qualidade confiáveis. Veja 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 crie-o com o produto. Os testes executados como parte do processo de compilação 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 pelos testes
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, envie a responsabilidade pelo teste para a pessoa que está escrevendo o código do componente. Não confie em outras pessoas para testar o componente.
Estudo de caso: Vire para a esquerda com testes unitários
Uma equipe da Microsoft decidiu substituir seus pacotes de testes legados por modernos testes de unidade de DevOps e um processo shift-left. A equipe acompanhou o progresso em sprints trisemanais, como mostra o gráfico a seguir. O gráfico abrange os sprints 78-120, o que representa 42 sprints ao longo de 126 semanas, ou cerca de dois anos e meio de esforço.
A equipa começou com 27K testes legados na sprint 78 e chegou a zero testes legados na sprint 120. 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 enorme. Nem todas as equipas 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 os objetivos de qualidade e desempenho da equipe.
Como Começar
No início, a equipe deixou os antigos testes funcionais, chamados de testes TRA, sozinhos. A equipa queria que os desenvolvedores adotassem a ideia de escrever testes de unidade, especialmente para novas funcionalidades. O foco foi tornar o mais fácil possível a criação de testes L0 e L1. A equipe precisava desenvolver essa capacidade primeiro e criar impulso.
O gráfico anterior mostra que a contagem de testes de unidade começou a aumentar desde cedo, quando a equipe reconheceu os benefícios de criar testes de unidade. Os testes unitários eram mais fáceis de manter, mais rápidos de executar e tinham menos falhas. Foi fácil obter suporte para executar todos os testes de unidade no fluxo de solicitação pull.
A equipe não se concentrou em escrever novos testes L2 até o sprint 101. Entretanto, a contagem dos testes 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 TRA, mas muitos foram simplesmente excluídos, com base na análise da equipe de sua utilidade.
Os testes TRA saltaram de 2100 para 3800 no sprint 110 porque mais testes foram descobertos na árvore de origem e adicionados ao gráfico. Descobriu-se que os testes sempre estiveram em execução, mas não estavam sendo rastreados corretamente. Esta não foi uma crise, mas era importante ser honesto e reavaliar conforme necessário.
Tornar-se mais rápido
Uma vez que a equipe tinha um sinal de integração contínua (CI) que era 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 pedido de pull e o pipeline de integração contínua (CI) em ação, e o tempo necessário para passar por várias fases.
Leva cerca de 30 minutos para processar um pull request até ao merge, o que inclui a execução de 60.000 testes unitários. Da mesclagem de código para a compilação de CI são cerca de 22 minutos. O primeiro sinal de qualidade do CI, SelfTest, vem depois de cerca de uma hora. Em seguida, a maior parte do produto é testada com a mudança proposta. Dentro de duas horas do Merge para o SelfHost, todo o produto é testado e a alteração está pronta para entrar em produção.
Usando métricas
A equipe acompanha um quadro de pontuações como o exemplo a seguir. Em um nível alto, o scorecard rastreia dois tipos de métricas: Saúde ou dívida e velocidade.
Para métricas de integridade do site em tempo real, a equipe rastreia o tempo para detetar, o tempo para mitigar e quantos itens de reparo uma equipe está carregando. Um item de reparo é um trabalho que a equipe identifica em uma retrospetiva de site ao vivo para evitar que incidentes semelhantes se repitam. O scorecard também rastreia se as equipes estão fechando os itens de reparo dentro de um prazo razoável.
Para métricas de saúde em engenharia, a equipe acompanha bugs ativos por desenvolvedor. Se uma equipe tiver mais de cinco bugs por desenvolvedor, a equipe deve priorizar a correção desses bugs antes do desenvolvimento de novos recursos. A equipe também rastreia bugs antigos em categorias especiais, como segurança.
Os indicadores de velocidade de engenharia medem a rapidez em diferentes partes do pipeline de integração contínua e entrega contínua (CI/CD). O objetivo geral é aumentar a velocidade do pipeline de DevOps: começar a partir de uma ideia, colocar o código em produção e receber dados de volta dos clientes.