Geração de Testes Eficiente com Sementes de Contradição
Um método mais rápido para gerar suítes de teste completas no desenvolvimento de software.
― 9 min ler
Índice
No desenvolvimento de software, garantir que um programa funcione corretamente depois de mudanças é fundamental. Um conjunto de testes chamado suíte de testes de regressão ajuda a garantir que funções antigas não quebrem quando novas alterações são feitas. Para ser realmente eficaz, esses testes devem cobrir todas as partes do programa. No entanto, criar esses testes manualmente é muito demorado, e as ferramentas automatizadas existentes podem ser lentas e ineficientes.
O método "Seeding Contradiction" oferece uma solução mais rápida para gerar um conjunto completo de testes. Esse método envolve a introdução de instruções falsas ou incorretas em cada parte básica do programa. Isso permite que um tipo especial de verificador de programa encontre erros em todos os ramos do código e crie testes a partir desses erros. O processo é rápido, não requer a execução do código e alcança uma excelente Cobertura.
Importância do Teste
Testar é uma parte crucial da engenharia de software. Todo programa que vai para o ar deve ter um conjunto de testes prontos. Esses testes servem para confirmar que, após cada mudança, o software ainda funciona como esperado. O objetivo é evitar regressões, onde as alterações criam novos problemas em áreas que antes funcionavam bem.
Criar uma suíte de testes de regressão com alta cobertura é desafiador. Embora existam várias ferramentas, como RANDOOP, Pex e AutoTest, muitas delas exigem a execução do código diversas vezes, o que pode ser lento. O método Seeding Contradiction, por outro lado, consegue uma cobertura total rapidamente e sem rodar o código, tornando-se uma escolha mais eficiente.
A Ideia Principal por trás do Seeding Contradiction
Seeding Contradiction aproveita os verificadores de programa modernos. Esses verificadores são projetados para verificar se um programa está correto. Normalmente, a função de um verificador é provar a correção, mostrando que não existem erros. No entanto, ao introduzir erros de propósito, podemos mudar o foco para gerar testes a partir dos erros encontrados.
Essa ideia pode ser comparada a encontrar falhas em uma prova para criar casos de teste. Em trabalhos anteriores, pesquisadores usaram com sucesso esse conceito de várias maneiras. Seeding Contradiction se baseia nessa abordagem, visando criar um conjunto completo de testes ao introduzir erros em ramos específicos do programa. Para cada versão alterada do programa, o verificador gera testes que exercitam cada ramo. O resultado é uma suíte de testes que geralmente cobre todos os ramos.
Como o Método Funciona
Para ilustrar esse método, vamos considerar um programa simples com uma condição:
simple (a: INTEGER)
if a > 0 then
x := 1
else
x := 2
end
Nesse programa, x
é definido com base no valor de a
. Para garantir que geramos testes para ambos os resultados possíveis (quando a
é maior que zero e quando não é), podemos usar o método Seeding Contradiction.
Tradicionalmente, alcançar isso exigiria a execução do código. No entanto, Seeding Contradiction funciona de forma diferente. Ele assume que há um conjunto de ferramentas disponíveis para verificar a correção do programa. O processo depende de uma estrutura de verificação específica projetada para garantir que o programa atenda ao seu comportamento pretendido.
Ao inserir uma instrução defeituosa, como uma verificação que o programa espera que seja verdadeira, mas que na verdade é falsa, podemos enganar o verificador de programa para gerar um caso de teste. Para o programa acima, podemos modificá-lo para ficar assim:
simple (a: INTEGER)
if a > 0 then
check False end
x := 1
else
x := 2
end
Com a instrução adicionada check False end
, o verificador falhará em provar a correção. O resultado é um contraexemplo que mostra como o primeiro ramo pode ser exercitado em um teste. Ao repetir esse processo para cada ramo, geramos testes que cobrem todo o programa.
Gerando Testes para Todos os Ramos
Para fazer o método funcionar para todos os ramos, um novo conceito chamado "Programas Múltiplos Semeados" (MSP) pode ser usado. Nessa abordagem, criamos uma versão do programa para cada bloco ou seção individual onde uma decisão é tomada. Depois de fazer as mudanças, o verificador é executado para cada versão modificada do programa, garantindo que cada ramo seja testado.
No entanto, gerar versões separadas do programa para cada ramo pode se tornar impraticável, levando a um grande número de variantes. Em vez disso, é usada a abordagem "Programa Único Repetidamente Semeado" (RSSP). Dessa forma, conseguimos criar uma única versão do programa que contém todas as falhas possíveis. Isso significa que ainda podemos produzir múltiplos contraexemplos a partir de apenas uma execução, o que é muito mais eficiente.
Por exemplo, se quisermos testar uma seção de código com várias condições, poderíamos configurá-lo assim:
simple (a: INTEGER)
if a > 0 then
check False end
x := 1
check False end
x := 2
if a > a then
check False end
x := 3
check False end
x := 4
Ao aplicar o método aqui, conseguimos garantir que todos os caminhos do programa original sejam testados sem gerar inúmeras versões separadas. O verificador produzirá diferentes contraexemplos para cada verificação defeituosa inserida, proporcionando uma cobertura abrangente de testes para todos os ramos.
Abordando a Inutilidade Semeada
Um problema na criação de testes vem da 'Inutilidade Semeada'. Esse termo se refere a situações em que alguns ramos podem não ser acessíveis devido à maneira como as checagens estão configuradas, levando a testes incompletos. Para resolver isso, podemos introduzir uma variável local que ajuda a determinar qual seção do código deve ser executada.
A lógica é que, antes de qualquer bloco, introduzimos uma verificação que depende do valor de uma variável. Nesse cenário, uma variável bn
pode receber um valor que indica qual parte do código checar. Isso significa que todos os ramos são acessíveis, e o verificador pode gerar testes que cobrem todos os caminhos possíveis.
Por exemplo, usando o exemplo anterior, podemos modificá-lo para incluir:
bn := "Valor escolhido de forma não determinística entre 0 e N"
if a > 0 then
if bn == 1 then
check False end
x := 1
if bn == 2 then
check False end
x := 2
if a > a then
if bn == 3 then
check False end
x := 3
if bn == 4 then
check False end
x := 4
Qualquer teste gerado então verifica caminhos válidos, mesmo que alguns blocos não fossem acessíveis no código original. Essa técnica é chamada de Semeadura Condicional. Ela facilita a geração de testes que cobrem de forma abrangente todos os ramos.
Provando Correção
O objetivo de gerar testes é garantir alta cobertura. A estratégia de Seeding Contradiction visa uma cobertura exaustiva, significando que cada parte acessível do programa é testada. Para garantir isso, várias suposições sobre o verificador de programa precisam estar em vigor:
- O verificador deve ser sólido em relação à acessibilidade: se uma seção pode ser acessada, ela será reconhecida como acessível.
- O verificador deve ser sólido em relação à correção: se ele diz que uma parte está correta, ela realmente está.
- O verificador deve produzir contraexemplos para todas as seções incorretas.
Dadas essas condições, pode-se mostrar que o método Seeding Contradiction produz uma suíte de testes correta e abrangente. Isso significa que, se o programa original for correto, os testes gerados também garantirão que todos os caminhos sejam testados.
Aplicações Práticas
Para verificar se a abordagem Seeding Contradiction funciona bem, ela foi implementada como uma opção em uma ferramenta de verificação de programas chamada AutoProof. Essa ferramenta pode gerar automaticamente testes a partir de falhas de prova, facilitando muito o processo de teste.
Em testes práticos, o método mostrou produzir resultados rápidos enquanto mantém alta cobertura. Foi implementado especificamente para a linguagem de programação Eiffel, mas poderia potencialmente se adaptar a outros ambientes de programação também.
Avaliação de Desempenho
Um estudo comparando o método Seeding Contradiction com ferramentas de geração de teste existentes, como IntelliTest e AutoTest, mostrou resultados promissores. Foi avaliado com base na cobertura, velocidade e no tamanho da suíte de testes gerada.
Os resultados destacaram que o Seeding Contradiction consistentemente alcançou cerca de 100% de cobertura em vários exemplos, enquanto usava muito menos tempo e gerava suítes de testes menores.
- O Seeding Contradiction completou a geração de testes em um tempo médio de menos de um segundo.
- O IntelliTest levou significativamente mais tempo, com uma média de cerca de 27 segundos.
- O AutoTest, com sua abordagem aleatória, exigiu ainda mais tempo, às vezes ultrapassando 259 segundos.
Em termos do número de testes gerados, o Seeding Contradiction produziu o menor número de testes, correspondendo de perto ao número de blocos passíveis de cobertura.
Limitações e Direções Futuras
Embora o método Seeding Contradiction mostre grande promessa, ele tem algumas limitações. Estas incluem:
- O método pode ter dificuldades com programas que exigem cálculos complexos, já que o verificador subjacente pode não lidar bem com eles.
- Ele não suporta atualmente recursos avançados da linguagem de programação, como generics.
Trabalhos futuros se concentrarão em superar essas limitações, explorando programas maiores, permitindo o processamento de classes inteiras e aprimorando a geração de testes para cobrir lacunas onde os testes existentes podem falhar.
Conclusão
O método Seeding Contradiction oferece uma maneira empolgante e eficiente de gerar suítes de testes abrangentes. Com foco em desempenho rápido e alta cobertura, representa um avanço significativo no campo dos testes de software. À medida que a pesquisa contínua aprimora o método e aborda limitações existentes, ele pode beneficiar desenvolvedores de software em todo lugar, garantindo que seus programas permaneçam robustos e livres de erros após cada alteração.
Título: Seeding Contradiction: a fast method for generating full-coverage test suites
Resumo: The regression test suite, a key resource for managing program evolution, needs to achieve 100% coverage, or very close, to be useful. Devising a test suite manually is unacceptably tedious, but existing automated methods are often inefficient. The method described in this article, ``Seeding Contradiction'', inserts incorrect instructions into every basic block of the program, enabling an SMT-based Hoare-style prover to generate a counterexample for every branch of the program and, from the collection of all such counterexamples, a test suite. The method is static, works fast, and achieves excellent coverage.
Autores: Li Huang, Bertrand Meyer, Manuel Oriol
Última atualização: 2023-09-08 00:00:00
Idioma: English
Fonte URL: https://arxiv.org/abs/2309.04232
Fonte PDF: https://arxiv.org/pdf/2309.04232
Licença: https://creativecommons.org/licenses/by/4.0/
Alterações: Este resumo foi elaborado com a assistência da AI e pode conter imprecisões. Para obter informações exactas, consulte os documentos originais ligados aqui.
Obrigado ao arxiv pela utilização da sua interoperabilidade de acesso aberto.