Testes e qualidade de software
Fundamentos de teste de software: níveis, técnicas e o vocabulário que gera discussão
Iniciante11 min de leituraAtualizado
Esta página foi traduzida do inglês com a ajuda de IA e conferida com o original. Ler a versão em inglês
Testar é uma atividade de design muito antes de ser uma questão de ferramentas. Este guia mostra o que os testes podem e não podem provar, os níveis pelos quais uma mudança passa, as técnicas formais que transformam um espaço de entradas infinito em um conjunto finito de casos e o vocabulário comum que impede que as discussões sobre defeitos virem brigas por palavras.
O que você vai aprender
- Explicar o que os testes conseguem demonstrar e o que nunca conseguirão provar
- Distinguir os níveis de unidade, integração, sistema e aceitação, e para que cada um serve melhor
- Aplicar particionamento de equivalência, valores-limite, tabelas de decisão e transição de estados para derivar casos
- Usar erro, defeito e falha, e verificação e validação, em seu sentido preciso
- Avaliar a pirâmide de testes pelos seus méritos e reconhecer onde seus críticos têm razão
Todo programa não trivial tem comportamentos que seus autores nunca consideraram. Testar é a forma como um time descobre parte desse comportamento antes dos clientes e, o que importa ainda mais para quem mantém o código, a forma de preservar a liberdade de mudar as coisas depois. Uma suíte que falha ruidosamente quando um refactoring quebra uma regra vale mais do que uma suíte que só prova que o código funcionou uma vez, porque a primeira continua rendendo por anos.
O lado das ferramentas de teste é fácil de aprender e fácil de supervalorizar. Saber escrever uma asserção do JUnit não diz nada sobre quais asserções vale a pena escrever. Este guia cobre o que vem antes: o que os testes conseguem estabelecer, como o trabalho se divide entre os níveis, como derivar casos de forma sistemática em vez de chutar, e o pequeno vocabulário que permite a um time discutir um defeito sem que três pessoas entendam três coisas diferentes.
O que um teste consegue estabelecer e o que não consegue
Um teste executa um programa com entradas escolhidas em um estado escolhido e compara o resultado observado com um esperado. Como o espaço de entradas até de um serviço modesto é astronomicamente grande — toda string, toda data, toda ordem possível de requisições concorrentes, toda falha parcial de uma dependência —, teste exaustivo é algo que simplesmente não existe. O que você constrói de fato é uma amostra, escolhida para maximizar a chance de expor falhas por unidade de esforço.
Daí decorrem duas consequências práticas. Primeiro, uma suíte verde não é prova de correção; significa que as falhas que você pensou em procurar não aconteceram nas condições que você criou. Segundo, o valor da amostra depende quase inteiramente de como ela foi escolhida. Mil testes sobre o mesmo caminho feliz valem menos do que vinte testes que exploram, cada um, um limite diferente, um ramo de erro ou uma suposição sobre um colaborador.
É também por isso que o momento de um teste importa tanto quanto seu conteúdo. O custo de um defeito cresce com a distância entre o momento em que ele é introduzido e o momento em que é encontrado. Um engano pego pelo compilador custa segundos. Pego por um teste de unidade na máquina do desenvolvedor, minutos. Pego em um pipeline, uma hora de troca de contexto. Pego em produção, custa um incidente, um hotfix, um plano de rollback, possivelmente reparo de dados e a atenção de pessoas que estavam fazendo outra coisa. Os multiplicadores exatos citados na literatura variam muito e devem ser vistos com desconfiança, mas o formato da curva não é controverso, e ele justifica levar a detecção para o mais cedo possível.
Palavras que precisam significar uma coisa só
A maioria das conversas acaloradas sobre testes é, na verdade, uma conversa sobre definições. Quatro distinções valem ser fixadas.
Erro, defeito, falha. Uma pessoa comete um erro (um engano de raciocínio). Esse erro pode deixar um defeito em um artefato: código, um arquivo de configuração, um requisito. Quando o código defeituoso executa em condições que o expõem, surge uma falha: um comportamento observável diferente do esperado. Nem todo defeito causa uma falha, e uma falha também pode vir do ambiente ou dos dados, sem defeito algum. Ser preciso aqui economiza tempo nas revisões de incidente: "tivemos uma falha" e "tivemos um defeito" pedem investigações diferentes.
Verificação e validação. A verificação confronta o produto com sua especificação. A validação confronta a especificação com a necessidade. Testes automatizados são, em sua esmagadora maioria, verificação; demos, sessões de aceitação e testes de usabilidade são onde a validação acontece.
Teste e garantia de qualidade. O teste mede um produto. A garantia de qualidade é o conjunto mais amplo de práticas — revisões, padrões, análise estática, definition of done — que torna os defeitos menos prováveis desde o início.
Caso de teste, suíte de testes, execução. Um caso é uma especificação única de entradas, pré-condições e resultado esperado. Uma suíte agrupa casos. Uma execução é uma rodada de uma suíte contra um build em um ambiente. Dizer "o teste falhou" sem informar qual execução, em qual build, é como os times acabam caçando fantasmas.
Os níveis pelos quais uma mudança passa
Os níveis descrevem o escopo do que está sob teste, não a ferramenta usada.
| Nível | Escopo | Normalmente escrito por | Responde |
|---|---|---|---|
| Componente (unidade) | Uma classe ou um pequeno grupo, isolado de colaboradores lentos | Desenvolvedores | Esta lógica se comporta como especificado? |
| Integração | Dois ou mais componentes, ou um componente e um sistema externo real | Desenvolvedores | Estas partes concordam sobre o contrato? |
| Sistema | A aplicação implantada como um todo | Desenvolvedores e testadores | O produto montado atende aos requisitos? |
| Aceitação | O produto no contexto de negócio | Pessoas de produto, usuários | É disso que precisávamos, e podemos lançar? |
As fronteiras entre eles são convenções, não leis, e os times discutem sem fim se um teste que sobe um banco de dados é "de unidade" ou "de integração". A discussão costuma ser inútil; o que importa é que cada teste tenha um alvo claramente identificado e que uma falha aponte para um lugar específico. Um teste cuja falha pode significar nove coisas diferentes está mal delimitado, seja qual for o nome que você dê a ele. O guia de testes de unidade e o guia de testes de integração mostram como cada um é construído em Java.
O teste de aceitação tem variações que vale nomear: o teste de aceitação do usuário (UAT), feito pelas pessoas que vão usar o sistema; a aceitação operacional, que cobre backup, restauração, monitoramento e runbooks; a aceitação contratual ou regulatória, quando é preciso demonstrar conformidade com uma norma externa; e os testes alfa/beta, em que usuários reais exercitam uma versão de pré-lançamento.
O funcional e todo o resto
O teste funcional pergunta se o sistema faz a coisa certa. O teste não funcional pergunta quão bem ele faz isso, e é daí que vem, na prática, a maioria dos incidentes em produção. As principais famílias:
- Eficiência de desempenho — tempo de resposta, throughput, uso de recursos sob carga. Tratada no guia de testes de performance.
- Confiabilidade — comportamento quando uma dependência falha, recuperação após um crash, durabilidade dos dados.
- Segurança — autenticação, autorização, injeção, gestão de segredos, vulnerabilidades em dependências.
- Usabilidade e acessibilidade — uma pessoa consegue concluir a tarefa, inclusive com leitor de tela ou só com o teclado?
- Compatibilidade — navegadores, dispositivos, versões de banco de dados, clientes de API antigos.
- Manutenibilidade e portabilidade — qualidades medidas no código e na implantação, e não em tempo de execução.
Requisitos não funcionais costumam ser escritos como adjetivos ("o sistema deve ser rápido"), o que não pode ser testado. Transformá-los em números com contexto — "latência no percentil 95 abaixo de 300 ms para o endpoint de busca com 200 usuários simultâneos no tamanho de instância padrão de produção" — é, de longe, a ação de maior alavancagem que um time pode tomar pela qualidade não funcional, porque converte uma opinião em uma verificação de passa/não passa.
Caixa-preta, caixa-branca e o que fica no meio
O teste caixa-preta (baseado em especificação) deriva os casos do que o sistema deve fazer, sem nenhuma referência a como ele foi escrito. É o único tipo possível para um componente de terceiros e sobrevive intacto a refactorings, e por isso a maioria dos testes valiosos é caixa-preta em relação ao seu alvo.
O teste caixa-branca (baseado em estrutura) deriva os casos do código: instruções, ramos, condições, caminhos. Sua medida é a cobertura, a fração de instruções ou ramos executados. É o raciocínio caixa-branca que revela que uma suíte nunca exercita o ramo else de uma checagem de validade.
As técnicas baseadas em experiência preenchem as lacunas que nenhuma das duas cobre: suposição de erros (sondar os lugares onde os defeitos historicamente moram — coleções vazias, zero, Unicode, transições de horário de verão), teste exploratório (aprendizado, projeto e execução simultâneos em sessões com tempo limitado) e checklists construídos a partir de incidentes passados.
Derivar casos em vez de chutar
As técnicas formais de projeto de testes existem para transformar um espaço de entradas infinito em um pequeno conjunto de casos com uma justificativa fundamentada. Quatro delas cobrem a maior parte das necessidades do dia a dia.
Particionamento de equivalência. Divida cada entrada em classes cujos membros devem ser tratados da mesma forma e teste um membro por classe. Para um campo de idade que aceita de 18 a 65, as partições são "abaixo de 18", "de 18 a 65" e "acima de 65", mais as classes inválidas: não numérico, vazio, ausente. Três ou quatro valores substituem milhões.
Análise de valor limite. Os defeitos se concentram nas bordas das partições, porque é ali que < e <= são confundidos. Para cada limite, teste o valor exato e os imediatamente vizinhos de cada lado: 17, 18, 19 e 64, 65, 66. É a técnica de maior retorno por teste escrito, e por isso aparece em quase toda especificação.
Tabelas de decisão. Quando o comportamento depende de uma combinação de condições, coloque-as em uma tabela. Uma regra de frete baseada no valor do pedido, na categoria do cliente e no destino tem um conjunto finito de combinações; escrevê-las expõe as que ninguém especificou.
| Pedido ≥ 50 | Membro premium | Nacional | Frete |
|---|---|---|---|
| sim | qualquer | sim | grátis |
| sim | sim | não | grátis |
| sim | não | não | taxa fixa |
| não | sim | sim | grátis |
| não | não | qualquer | tarifa padrão |
Uma tabela com quatro condições binárias independentes tem dezesseis linhas, e esse costuma ser o ponto em que alguém percebe que a regra é complicada demais. Quando o conjunto combinatório completo é inviável, o teste pairwise (por pares) cobre todos os pares de valores de parâmetros com muito menos casos e pega a maioria dos defeitos de interação.
Teste de transição de estados. Para tudo que tem um ciclo de vida — um pedido, um pagamento, uma assinatura —, desenhe os estados e os eventos permitidos. Depois teste cada transição válida e, mais importante, as inválidas: cancelar um pedido já despachado, pagar uma fatura já reembolsada. É nas transições inválidas que se escondem os defeitos interessantes, porque a especificação raramente fala delas.
A pirâmide e seus críticos
A pirâmide de testes é uma heurística sobre a composição da suíte: muitos testes rápidos e isolados na base, menos testes de integração no meio e um punhado de testes end-to-end no topo. Sua justificativa é econômica: os testes de baixo rodam em milissegundos, falham por um único motivo e raramente são instáveis, enquanto os de cima são lentos, frágeis e vagos quando falham.
Os críticos levantam dois pontos justos. Primeiro, a forma costuma ser aplicada no eixo errado: o que importa não é o framework usado, mas o isolamento e a velocidade do teste, e um teste "de unidade" que mocka todos os colaboradores pode passar enquanto o sistema montado está quebrado. Segundo, em um serviço que é basicamente cola entre um banco de dados e um cliente HTTP, há muito pouca lógica pura para testar em unidade, e uma forma mais larga no meio (às vezes desenhada como um troféu ou um favo de mel) reflete melhor a realidade.
As duas críticas tratam das proporções específicas, não do trade-off subjacente, que ninguém contesta: testes mais amplos encontram mais tipos de problema e custam mais para rodar e manter. O guia de estratégia de automação mostra como escolher uma forma para um sistema específico em vez de herdá-la de um diagrama.
Do defeito encontrado ao defeito fechado
Um relatório de defeito é um artefato de comunicação, e um relatório ruim desperdiça mais tempo do que o próprio defeito. O essencial: o que foi feito, o que aconteceu, o que era esperado, em qual build e ambiente, com logs ou uma captura de tela e a menor sequência que o reproduz. Em seguida, atribuem-se dois atributos frequentemente confundidos. A severidade é o impacto técnico: perda de dados é severidade alta, não importa quem percebeu. A prioridade é a urgência de negócio da correção. Um desalinhamento cosmético na página de cadastro pode ter severidade baixa e prioridade alta; um crash em uma funcionalidade usada por três pessoas pode ser o contrário.
O ciclo de vida é, então, uma pequena máquina de estados: novo, triado, atribuído, corrigido, pronto para reteste, verificado, fechado — com ramificações para rejeitado, duplicado e adiado. Duas regras o mantêm honesto: só quem reportou o defeito (ou o teste que o pegou) o fecha, e todo defeito corrigido ganha um teste de regressão, para que a mesma falha não possa voltar silenciosamente.
Saber quando começar e quando parar
Os critérios de entrada dizem o que precisa ser verdade antes de uma fase de teste começar: o build é implantado, os smoke tests passam, os dados de teste estão carregados, o ambiente corresponde à configuração-alvo. O objetivo deles é impedir que um time passe dias testando um build que nunca ia funcionar.
Os critérios de saída dizem o que precisa ser verdade para declará-la concluída. Os úteis são específicos e mensuráveis: todos os casos planejados executados, nenhum defeito de severidade alta em aberto, cobertura do código novo acima de um patamar combinado, todos os limites não funcionais atendidos, defeitos conhecidos em aberto documentados e aceitos. Os inúteis são "os testes estão completos" ou "o tempo acabou" disfarçados de critério. Em um pipeline de entrega contínua, esses critérios deixam de ser um portão entre fases e viram quality gates automatizados, a mesma ideia imposta por uma máquina em vez de uma reunião — o guia de integração contínua mostra como isso fica na prática.
Principais conclusões
- Os testes examinam o comportamento por amostragem; demonstram a presença de falhas e nunca podem provar sua ausência, por isso a forma de escolher os casos importa muito mais do que a quantidade.
- Defeitos ficam drasticamente mais caros quanto mais tarde são encontrados, e esse é todo o argumento econômico para testes automatizados rodando cedo e com frequência.
- Os níveis descrevem escopo — componente, integração, sistema, aceitação — e cada teste deve ter um alvo claramente identificado, para que uma falha aponte para um lugar específico.
- Requisitos não funcionais só se tornam testáveis quando escritos como números com contexto; adjetivos não conseguem quebrar um build.
- Particionamento de equivalência, valores-limite, tabelas de decisão e transições de estado transformam um espaço de entradas infinito em um conjunto de casos defensável; os limites são os que dão retorno mais rápido.
- Um vocabulário preciso — erro versus defeito versus falha, verificação versus validação, severidade versus prioridade — torna a triagem mais rápida e as discussões mais curtas.
Fontes e leituras complementares
Perguntas frequentes
- Os testes conseguem provar que um software não tem bugs?
- Não. Os testes examinam por amostragem o comportamento de um programa em entradas escolhidas, e o espaço de entradas, estados e sequências de tempo possíveis é praticamente infinito. Uma suíte que passa mostra que as falhas que você procurou não ocorreram nas condições que você criou. Isso é uma evidência valiosa, mas é evidência de presença, nunca prova de ausência; por isso números de cobertura e builds verdes são sinais, não garantias.
- Qual é a diferença entre verificação e validação?
- A verificação pergunta se o produto foi construído de acordo com a especificação: o código faz o que o design dizia que faria? A validação pergunta se essa especificação resolve o problema real do usuário. Uma funcionalidade pode passar em todas as verificações e ainda falhar na validação porque o requisito estava errado. Testes de unidade e de integração verificam, na maior parte; testes de aceitação do usuário, demos e sessões de usabilidade validam.
- Garantia de qualidade e teste são a mesma coisa?
- Teste é uma atividade dentro da garantia de qualidade. QA abrange tudo o que define como os defeitos são prevenidos e detectados: revisão de requisitos, padrões de código, pair programming, análise estática, definition of done, retrospectivas e melhoria de processos. O teste mede o produto; a QA atua sobre o processo que o produz. Um time pode testar muito e ainda ter uma garantia de qualidade ruim se nada muda quando os defeitos se repetem.
- Quantos casos de teste são suficientes?
- Não existe um número absoluto. Derive os casos da especificação com uma técnica como particionamento de equivalência e análise de valor limite, acrescente casos para áreas de risco conhecidas e defeitos passados, e pare quando novos casos deixarem de mudar sua confiança. Depois meça o risco residual em vez da contagem: quais requisitos não estão testados, quais caminhos de código nunca executam, quais modos de falha nunca foram simulados.