Design de testes
Os casos decidem o que a suíte pode capturar. Tudo o que vem depois — o harness, o executor, a contagem — apenas executa essa decisão.
Os momentos que isso substitui
| Prestes a… | Em vez disso… |
|---|---|
| escrever o caminho feliz e parar | percorrer o espaço de entrada: para cada parâmetro, suas classes e seus limites |
| derivar casos da implementação | derivá-los da especificação, e deixar a implementação surpreender você |
| testar com um valor confortável | testar o último valor dentro e o primeiro valor fora de cada limite |
| testar o limite que a especificação nomeia | todo intervalo tem duas pontas; a que a frase deixou de fora ainda é um limite |
| enumerar os papéis que a especificação lista | adicionar o chamador que ela esqueceu: sem papel, não autenticado, não atribuído |
| variar uma condição do caminho feliz | construir a tabela de decisão e cobrir as regras onde duas condições falham juntas |
| desenhar uma tabela de estado e seguir em frente | colocar um id de teste em cada linha; a linha que diz "não permitido" mais do que todas |
| escrever "rejeitado, ou aceito desde que…" como resultado esperado | um resultado por caso; uma especificação que não consegue decidir é uma questão em aberto, não um teste |
| pular caminhos de erro como improváveis | cada modo de falha é uma classe: entrada inválida, recurso ausente, negado, timeout, parcialmente concluído |
| deixar um caminho não implementado sem teste, um marcador de skip ou um stub que ninguém verifica | escrever um teste de que o caminho é recusado como não implementado hoje; ele passa agora e fica vermelho no dia em que alguém o construir (abaixo) |
| testar um recurso pelo seu fluxo principal | ler os fluxos alternativos e de exceção do caso de uso; cada um é uma classe |
| avaliar uma suíte apenas lendo-a | contá-la contra a especificação: classes, ambos os limites, regras, transições |
| adicionar um quinto teste a uma classe coberta | gastá-lo em uma classe descoberta — o quinto compra tempo de execução e nenhuma cobertura |
Atacar a especificação, não a implementação
Um teste escrito ao ler o código afirma o que o código faz. Ele passa na chegada, codifica os bugs atuais como expectativas, e suas falhas após uma mudança dizem "o código mudou", o que já era sabido. Isso é medido, não suspeitado: em 24 repositórios Java, oráculos gerados por LLM tenderam a capturar o comportamento real do programa em vez do seu comportamento esperado (Konstantinou, Degiovanni, Papadakis, arXiv:2410.21136).
Derive a partir do que a coisa deveria fazer — a especificação, o formato do fio, o contrato — e só então execute. A única exceção legítima é o teste de caracterização que deliberadamente fixa o comportamento atual, e é marcado como tal para que ninguém mais tarde confunda um bug fixado com um requisito.
Um cheiro útil: se todo teste estava verde na sua primeira execução, a suíte foi derivada da implementação, independentemente da intenção.
Classes de equivalência: conte classes, não testes
Entradas que o código não consegue distinguir formam uma classe, e um teste por classe é suficiente — o segundo ao quinto custam tempo de execução e não provam nada de novo. A disciplina funciona em ambos os sentidos: nenhuma classe deixada descoberta, nenhuma classe coberta duas vezes por acidente.
Uma suíte de dezessete testes parece completa. Se ela é depende de quantas classes esses dezessete cobrem, e esse número não aparece na saída de nenhum executor. Ele tem que ser contado contra o espaço de entrada manualmente — que é o que um plano de teste é.
As classes que a especificação lista são a metade fácil. Uma tabela de papéis com três linhas tem uma quarta classe, a requisição que não corresponde a nenhuma delas, e é a que uma suíte construída a partir da tabela nunca contém. Para cada parâmetro, escreva as classes inválidas ao lado das válidas antes de escrever qualquer caso: tipo errado, ausente, fora de todo valor listado, o chamador sem direitos nenhum.
O syllabus afirma isso como uma regra em vez de um hábito: na partição de equivalência os itens de cobertura são as partições, 100% significa toda partição identificada exercitada "incluindo partições inválidas", e as partições "não devem se sobrepor e devem ser conjuntos não vazios" (ISTQB CTFL v4.0.1 §4.2.1). Com vários parâmetros ele nomeia o critério mais fraco Each Choice — toda partição de cada parâmetro uma vez, combinações ignoradas — que é o nível que a maioria das suítes atinge sem saber seu nome, e onde a tabela de decisão abaixo começa.
Limites, porque é onde estão as falhas
Off-by-one é a falha aritmética mais comum,
e é invisível em todos os lugares exceto na borda.
O par que importa em cada limite é o valor do limite e seu vizinho mais próximo do outro lado da linha,
que é o BVA de 2 valores (CTFL v4.0.1 §4.2.2);
o BVA de 3 valores adiciona o vizinho do lado de dentro também,
e o syllabus mostra o que isso compra:
uma verificação pretendida como x ≤ 10 mas escrita como x = 10 passa tanto para 10 quanto para 11,
e apenas 9 a pega.
As arestas recorrentes: vazio, exatamente um, muitos; zero e negativo;
o máximo declarado e um além dele;
o valor que é exatamente o buffer, o limite, o timeout.
Um par concreto desta máquina: uma verificação de PATH que está ok em 8.190 caracteres e deve falhar em 8.192 tem que ser testada nesses dois valores. Passar em 4.000 e 12.000 estabelece quase nada sobre a linha entre eles.
Onde o limite é uma pré-condição declarada,
o valor além dele é um bug do chamador em vez de um caso com resultado esperado;
design-by-contract decide qual dos dois uma determinada aresta é,
e o teste então verifica o que se aplica.
O lado que a especificação não nomeou
"Até 10 MB" nomeia uma ponta de um intervalo. A outra ponta existe quer a frase a mencione ou não, e casos derivados do texto param onde o texto para: uma suíte com o limite superior testado em −1, 0 e +1 bytes e nada abaixo de 1.024 fez análise de limite em metade de um intervalo. A definição não tem lado: "os valores mínimo e máximo de uma partição são seus valores de limite" (CTFL v4.0.1 §4.2.2). Escreva ambos para cada partição primeiro, depois pegue cada um com seu vizinho mais próximo na partição adjacente. O lado não nomeado é geralmente zero, vazio ou um, e a especificação geralmente é silenciosa sobre ele porque ninguém decidiu.
Mova duas condições de uma vez
Uma suíte construída mudando uma condição do caminho feliz cobre todo valor de toda condição e quase nenhuma de suas combinações: formato errado, tamanho acima, contagem acima, cada um sozinho, e nenhum caso onde dois falham juntos. Qual erro vence quando ambos se aplicam raramente está na especificação também, então o caso ausente esconde uma decisão ausente.
Disponha as condições como uma tabela de decisão e conte as regras,
depois cubra as regras onde duas condições falham de uma vez e a regra onde uma restrição de papel e uma restrição de formato ambas se aplicam.
No teste de tabela de decisão os itens de cobertura são as colunas que contêm combinações viáveis,
e 100% significa toda coluna desse tipo exercitada (CTFL v4.0.1 §4.2.3);
o syllabus acrescenta que a tabela "ajuda a encontrar quaisquer lacunas ou contradições nos requisitos",
que é a decisão ausente acima surgindo por si só.
O mesmo documento pode ser lido como totalmente coberto sob Each Choice e um quarto coberto sob as colunas,
e apenas o segundo número diz algo sobre as combinações.
Quando a contagem de regras é o produto de um conjunto de flags,
a correção mais barata é a montante:
state-first colapsa as flags nos estados que podem ocorrer,
e a tabela encolhe para eles.
Uma tabela é uma afirmação sobre a suíte
Uma tabela de estado, uma tabela de decisão, a lista de fluxo de um caso de uso: cada linha afirma que um teste existe para ela. Coloque o id do teste na linha. Uma linha sem id é uma classe descoberta, e a linha que diz "não permitido" é a mais provável de estar vazia, porque escrever a proibição pareceu já ter lidado com ela. A transição proibida — o segundo envio enquanto um está em andamento — é o teste, e a guarda que deveria impedi-lo é a coisa sob teste.
A tabela de estado torna essa linha explícita: uma transição inválida é uma célula vazia, e o mais forte dos três critérios que o syllabus define, cobertura de todas as transições, exige tentar cada uma delas — um por caso de teste, para que um defeito não possa mascarar outro (CTFL v4.0.1 §4.2.4). Cobertura de transições válidas, o 0-switch onde a maioria das suítes para, nunca toca nessas células, é assim que uma linha "não permitido" não testada permanece verde.
Depois leia os dois documentos um contra o outro. Um teste cujo caminho discorda da tabela (a tabela diz falhou → enviando, o teste vai falhou → selecionado → enviando) significa que um deles está errado, e nada além da comparação descobre qual. Auto-transições e as transições para fora do estado de falha também contam como linhas.
Um resultado por caso
"Rejeitado, ou se aceito, nenhum dado corrompido permanece após o envio" não pode passar ou falhar. Aparece honestamente, quando a especificação nunca disse o que acontece com um arquivo de zero bytes e o autor se recusou a inventar uma resposta, e sobrevive à revisão porque o caso está visivelmente presente. A disjunção se esconde na coluna esperada. A forma correta é uma pergunta para quem possui a especificação, listada como tal, e nenhum teste para aquela classe até que seja respondida.
Caminhos de erro também são classes
Os caminhos infelizes são onde o código não testado se acumula, porque nada os exercita no uso normal — um detentor de lock morrendo no meio da execução, uma pasta mapeada que não existe, entrada com o campo esperado faltando. Cada um é uma classe de equivalência com a mesma reivindicação a um teste que qualquer intervalo de entrada. Uma suíte que apenas prova que a ferramenta funciona quando tudo coopera testou a demo, não a ferramenta.
Casos de uso enumeram as classes antes de haver código
Um caso de uso é uma especificação já cortada em classes: um fluxo principal, cada fluxo alternativo, cada fluxo de exceção, e a pré-condição sob a qual cada um pode ser iniciado. Quem o escreveu fez a enumeração que esta skill pede — os fluxos alternativos e de exceção são os caminhos de erro — então uma suíte que exercita o fluxo principal cobriu uma classe das N que o documento lista.
Leia os fluxos do caso de uso e dê a cada um seu teste, depois verifique as pré-condições, porque um fluxo só é acessível quando sua pré-condição pode ser verdadeira. Uma verificação de paridade para páginas traduzidas tem um fluxo principal (ambos os idiomas editados e commitados), um fluxo alternativo (uma edição unilateral declarada como exceção), e dois fluxos de exceção que só aparecem depois que as pré-condições são escritas: a árvore está suja, então a desatualização ainda não pode ser julgada; o histórico foi reescrito, então um commit declarado não resolve mais. Ambos foram atingidos em um site dentro de uma semana. O caminho feliz sozinho não teria mostrado nenhum.
Se um fluxo de exceção pode realmente ocorrer não é resolvido por quão improvável parece.
O envelope operacional diz o que está fora do escopo e a análise de perigos diz o que resta;
ambos vivem em assurance-case,
e esta skill pega a lista que eles produzem em vez de podá-la por intuição.
Cobertura é um número sob um critério nomeado
Um documento de teste medido de três maneiras: 100% das condições, 90,9% MC/DC,
25% das regras de decisão.
Todos os três são verdadeiros para o mesmo arquivo,
então um número de cobertura sem seu critério não diz nada,
e um número cujo critério é escolhido após a contagem diz menos ainda.
Nomeie-o antes de contar,
nos próprios termos do syllabus para que o número possa ser verificado:
cobertura de partição e Each Choice (§4.2.1),
BVA de 2 ou 3 valores (§4.2.2), colunas de tabela de decisão (§4.2.3),
todos os estados, transições válidas ou todas as transições (§4.2.4).
MC/DC não está no syllabus Foundation;
ele vem dos padrões de segurança (DO-178C, ISO 26262),
e listá-lo ao lado dos outros sem dizer isso é o tipo de citação mista que parece rigor.
O resto do que um número deve ao seu leitor está em measured-claims.
Verificando uma suíte: conte, não julgue
Perguntar se uma suíte é adequada retorna os pontos cegos que a produziram.
Uma segunda sessão no mesmo modelo os compartilha,
e o mesmo acontece com a mesma pessoa um dia depois.
O syllabus chama isso de "alguma independência",
o segundo de seus quatro níveis, e diz de onde vem o valor:
"diferenças entre os vieses cognitivos do autor e do testador" (CTFL v4.0.1 §1.5.3).
Uma segunda sessão no mesmo modelo compartilha os vieses e mantém apenas o rótulo.
O passo que não os compartilha é contar:
liste as classes da especificação e marque as que têm um teste,
liste ambos os limites de cada intervalo, liste as regras da tabela de decisão,
liste as linhas de cada tabela de estado,
e cite o arquivo e a linha de cada lacuna.
Se um valor esperado está certo continua sendo um julgamento e fica com um leitor que não o escreveu;
se uma classe tem um teste é aritmética.
Se a contagem foi amostrada em vez de exaustiva,
o relatório diz amostrado.
A forma refute-por-padrão para o veredito está em adversarial-verify;
esta skill fornece o que contar.
Quando a requisição remove uma classe
"O envio duplo não pode acontecer porque o botão está desabilitado, descarte essa linha" remove uma classe da suíte com base na força de uma guarda em uma camada. Faça o que foi pedido, e diga em uma frase qual classe está agora descoberta e o que a alcançaria: a máquina de estados ainda aceita um segundo envio de qualquer chamador que não seja o botão. Uma linha deletada com essa frase é uma decisão; uma linha deletada sem ela é uma lacuna que ninguém encontrará novamente.
Um caminho não implementado tem um teste que diz isso
Um caminho deixado de fora desta mudança de propósito,
um papel que esta release não suporta, um formato ainda não analisado,
um branch que lança NotImplementedException ou chama todo!(),
ainda é uma classe da entrada,
e a suíte registra o que acontece lá hoje.
Escreva o teste de que o caminho é recusado como não implementado:
a exceção ou o valor de erro que ele levanta,
o item de menu que está ausente, a requisição que responde "não suportado".
Esse teste passa agora e faz dois trabalhos. Ele coloca a lacuna na lista da suíte, onde um leitor contando classes a encontra, em vez de em um comentário ou ticket. E ele fica vermelho no dia em que alguém implementa o caminho, que é o momento em que seus casos reais têm que ser escritos; o teste com falha é o lembrete, e é substituído, não deletado.
Um marcador de skip ou um teste pendente não é um teste; é um espaço reservado que a suíte trata como sucesso e ninguém revê.