Existem teorias formalizadas / matemáticas de teste de software?


12

A "teoria dos testes de software" no Google parece apenas dar teorias no sentido suave da palavra; Não consegui encontrar nada que classificasse como teoria no sentido matemático, da informação teórica ou de algum outro campo científico.

O que estou procurando é algo que formalize o que é testar, as noções usadas, o que é um caso de teste, a viabilidade de testar algo, a praticidade de testar algo, a extensão em que algo deve ser testado, definição formal / explicação de cobertura de código etc.

UPDATE: Além disso, não tenho certeza, intuitivamente, sobre a conexão entre a verificação formal e o que pedi, mas há claramente algum tipo de conexão.


1
O teste de software é muito valioso (por exemplo, colocar o teste de unidade em prática), mas sua teoria sempre terá alguns buracos. Considere este exemplo clássico: double pihole(double value) { return (value - Math.PI) / (value - Math.PI); }que aprendi com meu professor de matemática . Esse código tem exatamente um buraco , que não pode ser descoberto automaticamente apenas a partir de testes em caixas pretas. Em Math, não existe esse buraco. No cálculo, você pode fechar o buraco se os limites unilaterais forem iguais.
rwong 25/05

4
Isso pode fazer parte do que você está procurando - en.wikipedia.org/wiki/Formal_verification
enderland

1
Eu segundo a sugestão de enderland. Não importa o quão rigorosa seja sua abordagem aos testes; alguns bugs ainda escapam às falhas e, à medida que você cobre mais código com seus testes, o custo de encontrar novos bugs aumenta. Provavelmente é por isso que ninguém se deu ao trabalho de formalizar a noção de teste - uma abordagem "heurística" funciona quase tão bem com menos treinamento.
Doval

Desde então, me familiarizei com a terra da verificação formal por meio de tipos dependentes e posso concordar completamente com @Doval e enderland.
precisa saber é o seguinte

1
@rwong Suponho que você alude à possibilidade de numerador e denominador serem iguais a zero. Parcialmente, o problema se deve ao design inadequado dessa função. Ao falar sobre verificação formal matemática, você precisa compor suas funções não arbitrariamente, mas seguindo regras formais, com base nos tipos de dados corretos. Neste exemplo, você precisaria usar a função de divisão (a,b)=>a/b, que precisa ser estendida com um valor de estouro, para ser corretamente composta.
Dmitri Zaitsev

Respostas:



5

Não posso apontar um bom recurso on-line (os artigos da Wikipedia em inglês sobre esses tópicos tendem a ser improváveis), mas posso resumir uma palestra que ouvi que também abordou a teoria básica dos testes.

Modos de teste

Existem diferentes classes de testes, como testes de unidade ou testes de integração . Um teste de unidade afirma que um pedaço de código coerente (função, classe, módulo) obtido por conta própria funciona como esperado, enquanto um teste de integração afirma que vários desses pedaços funcionam corretamente juntos.

Um caso de teste é um ambiente conhecido no qual uma parte do código é executada, por exemplo, usando entrada de teste específica ou zombando de outras classes. O comportamento do código é então comparado ao comportamento esperado, por exemplo, um valor de retorno específico.

Um teste pode apenas provar a presença de um erro, nunca a ausência de todos os erros. Os testes colocam um limite superior na correção do programa.

Cobertura de código

Para definir métricas de cobertura de código, o código-fonte pode ser convertido em um gráfico de fluxo de controle em que cada nó contém um segmento linear do código. O controle flui entre esses nós apenas no final de cada bloco e é sempre condicional (se condição, então vá para o nó A, depois vá para o nó B). O gráfico possui um nó inicial e um nó final.

  • Com este gráfico, a cobertura de instrução é a proporção de todos os nós visitados para todos os nós. A cobertura completa da declaração não é suficiente para testes completos.
  • A cobertura da filial é a proporção de todas as arestas visitadas entre os nós no CFG e todas as arestas. Isso insuficientemente testa loops.
  • A cobertura do caminho é a proporção de todos os caminhos visitados para todos os caminhos, onde um caminho é qualquer sequência de arestas do nó inicial ao final. O problema é que, com loops, pode haver um número infinito de caminhos; portanto, a cobertura completa do caminho não pode ser praticamente testada.

Portanto, geralmente é útil verificar a cobertura da condição .

  • Na cobertura de condições simples , cada condição atômica é verdadeira e falsa - mas isso não garante a cobertura completa da declaração.
  • Na cobertura de várias condições , as condições atômicas assumiram todas as combinações de truee false. Isso implica cobertura total da agência, mas é bastante caro. O programa pode ter restrições adicionais que excluem determinadas combinações. Essa técnica é boa para obter cobertura de ramificação, pode encontrar código morto, mas não consegue encontrar erros decorrentes da condição incorreta .
  • Na cobertura mínima de múltiplas condições , cada condição atômica e composta é verdadeira e falsa. Ainda implica uma cobertura completa da agência. É um subconjunto de várias condições de cobertura, mas requer menos casos de teste.

Ao construir a entrada de teste usando a cobertura da condição, o curto-circuito deve ser levado em consideração. Por exemplo,

function foo(A, B) {
  if (A && B) x()
  else        y()
}

precisa ser testado com foo(false, whatever), foo(true, false)e foo(true, true)para a cobertura condição múltipla mínima completo.

Se você possui objetos que podem estar em vários estados, o teste de todas as transições de estado análogas ao controle de fluxos parece sensato.

Existem algumas métricas de cobertura mais complexas, mas geralmente são semelhantes às métricas apresentadas aqui.

Estes são métodos de teste de caixa branca e podem ser parcialmente automatizados. Observe que um conjunto de testes de unidade deve ter uma cobertura de código alta por qualquer métrica escolhida, mas 100% nem sempre é possível. É especialmente difícil testar o tratamento de exceções, onde as falhas precisam ser injetadas em locais específicos.

Testes funcionais

Depois, há testes funcionais que afirmam que o código adere às especificações, visualizando a implementação como uma caixa preta. Esses testes são úteis para testes de unidade e testes de integração. Como é impossível testar com todos os dados de entrada possíveis (por exemplo, testar o comprimento da string com todas as strings possíveis), é útil agrupar a entrada (e a saída) em classes equivalentes - se length("foo")estiver correto, foo("bar")provavelmente funcionará também. Para cada combinação possível entre as classes de equivalência de entrada e saída, pelo menos uma entrada representativa é escolhida e testada.

Deve-se testar adicionalmente

  • casos extremos length(""), foo("x"), length(longer_than_INT_MAX),
  • valores permitidos pelo idioma, mas não pelo contrato da função length(null), e
  • possíveis dados indesejados length("null byte in \x00 the middle")...

Com números, isso significa testar 0, ±1, ±x, MAX, MIN, ±∞, NaNe com comparações de ponto flutuante testando dois carros alegóricos vizinhos. Como outra adição, valores de teste aleatórios podem ser selecionados nas classes de equivalência. Para facilitar a depuração, vale a pena registrar a semente usada…

Testes não funcionais: testes de carga, testes de estresse

Um software possui requisitos não funcionais, que também precisam ser testados. Isso inclui testes nos limites definidos (testes de carga) e além deles (testes de estresse). Para um jogo de computador, isso pode estar afirmando um número mínimo de quadros por segundo em um teste de carga. Um site pode ser submetido a um teste de estresse para observar os tempos de resposta quando o dobro do número de visitantes antecipados está prejudicando os servidores. Esses testes não são relevantes apenas para sistemas inteiros, mas também para entidades únicas - como uma tabela de hash é degradada com um milhão de entradas?

Outros tipos de testes são testes de todo o sistema em que cenários são simulados ou testes de aceitação para provar que o contrato de desenvolvimento foi cumprido.

Métodos sem teste

Avaliações

Existem técnicas que não são de teste que podem ser usadas para garantir a qualidade. Exemplos são orientações, revisões formais de código ou programação de pares. Embora algumas peças possam ser automatizadas (por exemplo, usando linters), elas geralmente demandam muito tempo. No entanto, as revisões de código por programadores experientes têm uma alta taxa de descoberta de bugs e são especialmente valiosas durante o design, onde nenhum teste automatizado é possível.

Quando as revisões de código são tão boas, por que ainda escrevemos testes? A grande vantagem dos conjuntos de testes é que eles podem ser executados (principalmente) automaticamente e, portanto, são muito úteis para testes de regressão .

Verificação formal

A verificação formal confirma e prova certas propriedades do código. A verificação manual é principalmente viável para partes críticas, menos para programas inteiros. As provas colocam um limite inferior na correção do programa. As provas podem ser automatizadas até certo ponto, por exemplo, através de um verificador de tipo estático.

Certos invariantes podem ser verificados explicitamente usando assertinstruções.


Todas essas técnicas têm seu lugar e são complementares. O TDD grava os testes funcionais antecipadamente, mas os testes podem ser julgados por suas métricas de cobertura assim que o código for implementado.

Escrever código testável significa escrever pequenas unidades de código que podem ser testadas separadamente (funções auxiliares com granularidade adequada, princípio de responsabilidade única). Quanto menos argumentos cada função receber, melhor. Esse código também se presta à inserção de objetos simulados, por exemplo, via injeção de dependência.


2
Agradeço a resposta elaborada, mas receio que não tenha quase nada a ver com o que pedi :) Mas, felizmente, programmers.stackexchange.com/questions/78675/… parece ser o mais próximo possível do que eu era. visando.
Erik Kaplun

Isso é ótimo. Você pode recomendar livros ou coisas?
Marcin

4

Talvez o "teste baseado em especificação" também responda à sua pergunta. Verifique estes módulos de teste (que ainda não usei). Eles exigem que você escreva uma expressão matemática para especificar conjuntos de valores de teste, em vez de escrever um teste de unidade usando valores de dados únicos selecionados.

Teste :: Lectrotest

Como o autor diz, este Módulo Perl foi inspirado no Quick-Check Module de Haskell . Existem mais links nesta página, alguns dos quais estão mortos.


2

Uma abordagem matematicamente baseada é o teste de todos os pares . A idéia é que a maioria dos bugs seja ativada por uma única opção de configuração e a maior parte do restante seja ativada por um determinado par de opções tomadas simultaneamente. Assim, a maioria pode ser capturada testando "todos os pares". Uma explicação matemática (com generalizações) está aqui:

O sistema AETG: uma abordagem para testes com base no design combinatório

(existem muitas outras referências)


2

Existem algumas equações matemáticas usadas, mas isso depende do tipo de teste de software que você está usando. Por exemplo, a suposição crítica de falhas pressupõe que as falhas dificilmente são o produto de 2 ou mais falhas simultâneas. A seguinte equação é: f = 4n + 1. f = função que calcula o número de casos de teste para um determinado número de variáveis ​​( n) + 1 é a adição da constante em que todas as variáveis ​​assumem o valor nominal.

Outro tipo de teste que requer equações matemáticas é o Teste de robustez, que está testando a robustez ou a correção dos casos de teste em um processo de teste. Nesse teste, você digitaria variáveis ​​dentro do intervalo de entrada legítimo (casos de teste limpos) e variáveis ​​de entrada fora do intervalo de entrada (casos de teste sujos). Você usaria a seguinte equação matemática: f = 6n + 1 . 6n indica que cada variável deve assumir 6 valores diferentes, enquanto os outros valores assumem o valor nominal. * + 1 * representa a adição da constante 1.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.