Resumo
Um programa que compila e escreve algo razoável não está, por isso, testado. Esta aula ensina a testar com método: a derivar os casos da especificação, por regiões e bordas; a reuni-los num programa de testes automáticos que conta as falhas e as informa; a verificar com assert o que não pode ocorrer se o programa é correto, e com static_assert, em tempo de compilação, o que uma função constexpr deve calcular; e a executar os testes com os detectores, que descobrem o comportamento indefinido que um resultado correto oculta. Tudo repousa sobre uma advertência: um teste pode mostrar a presença de um erro, nunca a sua ausência.
Objetivos de aprendizagem
- Derivar os casos de teste da especificação de uma função: as partições da sua entrada, as bordas entre elas e os casos especiais, com o resultado que a pós-condição exige em cada um.
- Escrever um programa de testes automáticos que compare os resultados obtidos com os esperados, informe cada falha sem parar e termine com um resumo e um código de saída que o reflita.
- Usar
assertpara verificar pré-condições e invariantes, e explicar por que desaparece ao compilar com-DNDEBUGe por que a sua condição não deve ter efeitos colaterais. - Usar
static_assertcom funçõesconstexprpara testar resultados em tempo de compilação, e executar os testes com os detectores para descobrir o comportamento indefinido que os resultados não revelam.
O que é testar
Testar um programa é executá-lo com entradas escolhidas e comparar o que ele faz com o que deveria fazer. Cada caso de teste consta de duas partes: uma entrada que cumpre a pré-condição, e o resultado que a pós-condição exige para ela, calculado de antemão e por outro caminho (à mão, com outra fórmula, com uma tabela conhecida). Sem o resultado esperado não há teste, mas uma execução: olhar a saída e achá-la “razoável” é uma impressão, não uma comparação.
Ora, uma função de um argumento int admite mais de quatro bilhões de entradas, e uma de dois argumentos, cerca de pares; nenhuma bateria de testes percorre todas. Se isso é assim, os testes examinam uma parte ínfima dos casos possíveis, e do fato de que todos passem não se segue que a função seja correta. Edsger Dijkstra formulou isso em 1970: os testes podem mostrar a presença de erros, nunca a sua ausência. A distinção é a que o curso de Lógica Matemática I estabelece entre verificar casos e demonstrar: verificar que uma fórmula é verdadeira para alguns valores não prova que o seja para todos, ao passo que uma demonstração (neste curso, o invariante da aula Projetar laços corretos) cobre todos os casos de uma vez. Os testes não substituem esse raciocínio: encontram os erros que ele deixou passar.
Disso se segue o critério para escolher os casos. Se os testes não podem confirmar a correção, o seu valor está em encontrar erros, e um bom caso de teste é o que tem mais probabilidade de falhar. Um caso que o autor escolheu porque sabia que funcionaria não informa nada. O programa seguinte o mostra com uma função que deve devolver o maior elemento de um vetor não vazio.
#include <print>#include <vector>// Maior elemento de uma lista de dados.// Pré-condição: values não está vazio.// Pós-condição: devolve o maior elemento de values.int largest(const std::vector<int>& values){ int best{0}; for (const int v : values) { if (v > best) { best = v; } } return best;}int main(){ std::println("{}", largest({3, 9, 4})); std::println("{}", largest({7})); std::println("{}", largest({1, 2, 3, 4})); std::println("{}", largest({-5, -2, -8}));}9740Os três primeiros casos são os que qualquer um escreve primeiro, e a função passa neles: 9, 7 e 4 estão corretos. O quarto dá 0, um valor que nem sequer está no vetor, quando deveria dar . O erro está na linha 9: best começa em 0 e não no primeiro elemento, de modo que a função é correta só se algum elemento for positivo ou zero. Os casos típicos compartilhavam essa propriedade sem que ninguém a escolhesse; o quarto a viola de propósito. A correção é int best{values[0]};, que a pré-condição torna legítima, porque garante que values[0] existe.
Carregando o conteúdo…