Você está na página 1de 41

See discussions, stats, and author profiles for this publication at: https://www.researchgate.

net/publication/306255146

INTRODUÇÃO AO TESTE DE SOFTWARE

Chapter · November 2000

CITATIONS READS

35 2,481

10 authors, including:

Simone Do Rocio Senger de Souza José Carlos Maldonado


University of São Paulo University of São Paulo
76 PUBLICATIONS   413 CITATIONS    313 PUBLICATIONS   3,373 CITATIONS   

SEE PROFILE SEE PROFILE

Auri Marcelo Rizzo Vincenzi Ellen Barbosa


Universidade Federal de São Carlos University of São Paulo
82 PUBLICATIONS   626 CITATIONS    148 PUBLICATIONS   761 CITATIONS   

SEE PROFILE SEE PROFILE

Some of the authors of this publication are also working on these related projects:

Avaliação de funções de similaridade em sistemas de CBIR View project

Metodologia de desenvolvimento de jogos sérios View project

All content following this page was uploaded by Simone Do Rocio Senger de Souza on 17 August 2016.

The user has requested enhancement of the downloaded file.


INTRODUÇÃO AO TESTE DE SOFTWARE
Simone do Rocio Senger de Souza1
José Carlos Maldonado2
Sandra Camargo Pinto Ferraz Fabbri3
Auri Marcelo Rizzo Vincenzi2
Ellen Francine Barbosa2
Márcio Eduardo Delamaro4
Mário Jino5
1Universidade Estadual de Ponta Grossa – UEPG
rocio@icmc.sc.usp.br
2Instituto de Ciências Matemáticas e de Computação – ICMC/USP
{jcmaldon, auri, francine}@icmc.sc.usp.br
3UniversidadeFederal de São Carlos – UFSCar
sfabbri@dc.ufscar.br
4UniversidadeEstadual de Maringá – DIN/UEM
delamaro@din.uem.br
5Universidade Estadual de Campinas – DCA/FEEC/UNICAMP
jino@dca.fee.unicamp.br

Resumo
As exigências por softwares com maior qualidade têm motivado a definição de
métodos e técnicas para o desenvolvimento de softwares que atinjam os padrões de
qualidade impostos. Com isso, o interesse pela atividade de teste de software vem
aumentando nos últimos anos. Vários pesquisadores têm investigado os diferentes
critérios de teste, buscando obter uma estratégia de teste com baixo custo de
aplicação, mas ao mesmo tempo, com grande capacidade em revelar erros. O objetivo
deste minicurso é apresentar os aspectos teóricos e práticos relacionados à atividade
de teste de software. Uma síntese das técnicas de teste funcional, estrutural e
baseada em erros, bem como de critérios de teste pertencentes a cada uma delas,
será apresentada. Fatores utilizados na comparação e avaliação de critérios de teste
de software (custo, eficácia e strength) também serão abordados, tanto do ponto de
vista teórico como empírico. A importância da automatização da atividade de teste
será destacada, caracterizando-se os esforços da comunidade científica nessa
direção. Dar-se-á ênfase ao critério de teste Análise de Mutantes apresentando uma
revisão histórica do seu surgimento e desenvolvimento. Aspectos teóricos e práticos
de sua utilização serão abordados e as estratégias que procuram minimizar o custo de
aplicação desse critério serão discutidas. A ferramenta Proteum – Program Testing
Using Mutants, que automatiza a aplicação do critério Análise de Mutantes, será
apresentada e ilustrada. Será apresentado o critério Mutação de Interface que estende
o critério Análise de Mutantes visando à atividade de teste no nível de integração.
Serão apresentadas também as extensões do critério Análise de Mutantes para
aplicação no contexto de especificações, discutindo sua definição para validação de
especificações baseadas em Statecharts, Máquinas de Estados Finitos, Redes de
Petri e Estelle. Perspectivas e trabalhos de pesquisa sendo realizados nessa área
serão discutidos.

1. Introdução
O processo de desenvolvimento de software envolve uma série de
atividades em que, apesar dos métodos, técnicas e ferramentas empregados,
erros no produto ainda podem ocorrer. Erros podem ocorrer desde o início do
desenvolvimento, por exemplo, especificando erroneamente os requisitos do
sistema, como também nos estágios finais através da inserção de defeitos no
projeto ou na implementação do sistema. Com isso, a etapa de teste é de
grande importância para a identificação e eliminação de erros que persistem,
representando a última revisão da especificação, projeto e codificação
(Pressman, 1997).
Segundo o padrão IEEE (1990), defeito (fault) é um passo, processo ou
definição de dados incorreto, por exemplo, uma instrução ou comando
incorreto no programa; engano (mistake) é uma ação humana que produz um
resultado incorreto, por exemplo, uma ação incorreta feita pelo programador;
erro (error) ocorre quando o valor esperado e o valor obtido não são os
mesmos, ou seja, qualquer resultado inesperado na execução do programa
constitui um erro e falha (failure) é a produção de uma saída incorreta em
relação à especificação. Neste texto, são utilizados os termos erro (causa) e
falha (conseqüência), sendo que o termo erro engloba os termos defeito,
engano e erro.
A atividade de teste, cujo objetivo principal é executar um programa com a
intenção de encontrar erros, é considerada como uma atividade de validação e
verificação. Atividades de verificação procuram garantir que a implementação
e/ou especificação do sistema estejam corretos, ou seja, referem-se ao
conjunto de atividades que procuram garantir que o sistema implementa
corretamente uma determinada função. Validação refere-se ao conjunto de
atividades que procuram garantir que o sistema esteja de acordo com os
requisitos do usuário (Pressman, 1997).
Em algumas aplicações, uma falha no software pode trazer conseqüências
desastrosas. Exemplos disso seriam softwares para controle de tráfego, de
aeronaves, controle bancário, monitoramento de pacientes, dentre outros, que
envolvem seres humanos. Esses softwares, chamados de sistemas críticos,
necessitam de metodologias mais rigorosas para o seu desenvolvimento, de
forma a garantir que o software seja desenvolvido corretamente e que funcione
de acordo com o esperado. As técnicas de especificação formal contribuem
nesse sentido fornecendo uma linguagem gráfica e descritiva para
especificação do sistema, com sintaxe e semântica formalmente definidas. A
utilização de técnicas formais permite o desenvolvimento de especificações
consistentes, completas e não ambíguas e são empregadas na descrição,
análise e projeto de sistemas, principalmente, de sistemas críticos.
A utilização de técnicas formais não garante que a especificação definida
esteja livre de erros. Sendo assim, testar a especificação é tão importante
quanto testar o programa. Duas atividades de teste são realizadas no escopo
de especificações: teste de especificações e teste de conformidade (ou teste
baseado na especificação). O teste de especificações é análogo ao teste de
programas e visa a encontrar erros na especificação e a garantir que a
especificação esteja de acordo com os requisitos do usuário utilizando, sempre
que possível, técnicas e critérios definidos com base nas técnicas e critérios de
teste existentes no nível de programas. O teste de conformidade é utilizado
principalmente no teste de protocolos de comunicação e visa a garantir que a
implementação do protocolo está em conformidade com a sua especificação.
Para o teste de conformidade é essencial que a especificação esteja correta e
retrate os requisitos do usuário.
Este texto está organizado da seguinte forma: na Seção 2 são
apresentados os principais critérios de teste das técnicas funcional, estrutural e
baseada em erros, dando-se ênfase ao critério Análise de Mutantes. Os
aspectos de automatização são ilustrados através da ferramenta Proteum –
Program Testing Using Mutants, que apóia a aplicação do critério Análise de
Mutantes. Na Seção 3 são sintetizados os principais resultados de estudos
empíricos relacionados aos critérios de teste apresentados neste texto. Na
Seção 4 são apresentados alguns trabalhos que aplicam critérios de teste para
validação de especificações formais em Redes de Petri, Máquinas de Estados
Finitos, Statecharts e Estelle. Na Seção 5 são apresentadas as conclusões.

2. Técnicas e Critérios de Teste


Segundo Myers (1979), teste é processo de executar um programa com o
objetivo de encontrar erros e compreende os seguintes passos: 1) construção
do conjunto de casos de teste, 2) execução do programa P com esse conjunto
de casos de teste e 3) análise do comportamento de P para determinar se o
mesmo está correto ou não. Esses passos se repetem até que se tenha
confiança de que P realiza o esperado com o mínimo de erros possível.
Portanto, não existe um procedimento de teste de propósito geral que
possa ser usado para provar que um programa está correto, ou seja, se erros
não são encontrados isso não significa que eles não existam. Apesar disso, os
testes conduzidos de forma sistemática e criteriosa podem contribuir para
aumentar a confiança de que o software desempenha as funções
especificadas, podendo também evidenciar algumas características mínimas
do ponto de vista de qualidade do produto.
A atividade de teste pode ser caracterizada em 3 níveis (ou fases),
usualmente presentes em uma estratégia: teste de unidade – que concentra
esforços nos módulos do programa (menores unidades do projeto de
software), procurando identificar erros de lógica e de implementação, teste de
integração – que visa a identificar erros na interação entre os módulos, sendo
uma técnica sistemática para integrar os módulos que compõe a estrutura do
software, e teste de sistema – que é realizado após a integração do sistema e
visa a identificar erros de funções e características de desempenho, que não
estejam de acordo com a especificação.
O sucesso das atividades de teste e validação está relacionado com a
qualidade do conjunto de casos de teste. Idealmente, o software deveria ser
exercitado com todos os valores possíveis do domínio de entrada. Sabe-se,
porém, que esse tipo de teste, conhecido como teste exaustivo é impraticável,
em geral, devido a restrições de tempo e custo para sua realização. Nesta
perspectiva, duas questões importantes são:
 Como selecionar os casos de teste?
 Como garantir que um programa foi suficientemente testado?
Para responder a essas questões definem-se: 1) método de seleção de
dados de teste, como um procedimento para escolher casos de teste, e 2)
critério de adequação dos dados de teste como um procedimento usado para
avaliar um conjunto de casos de teste. Existe uma forte correspondência entre
métodos de seleção e critérios de adequação, pois dado um critério de
adequação C, existe um método de seleção Mc que estabelece: “Selecione um
conjunto de teste que satisfaça o critério C”. De forma análoga, dado um
método de seleção M, existe um critério de adequação Cm que determina: “Um
conjunto de dados de teste é adequado se ele foi gerado pelo método M”.
Desse modo, costuma-se utilizar o nome de critério de teste para designar as
duas atividades (Maldonado, 1991).
Critérios de teste têm sido elaborados com o objetivo de fornecer uma
maneira sistemática e rigorosa para selecionar um conjunto de teste e ainda
assim ser eficiente para apresentar os erros existentes, respeitando as
restrições de tempo e custo associadas a um projeto de software. Esses
critérios são classificados em três técnicas de teste: Funcional, Estrutural e
Baseada em Erros. A diferença entre essas três técnicas é a origem da
informação usada para avaliar ou para construir os conjuntos de teste, de
forma que cada técnica possui um conjunto de critérios de teste utilizados para
esse fim. Além disso, nenhuma das técnicas de teste é completa, no sentido
que nenhuma delas é, em geral, suficiente para garantir a qualidade da
atividade de teste. Na verdade, essas diferentes técnicas se complementam e
devem ser aplicadas em conjunto para assegurar-se um teste de boa qualidade
(Maldonado, 1991). Essas três técnicas são descritas a seguir, dando-se
ênfase ao critério Análise de Mutantes.
Para ilustrar os aspectos de cada técnica de teste, o exemplo apresentado
na Figura 1 será utilizado. A aplicação dos critérios de teste para esse
programa exemplo foi extraído de Maldonado et al. (1998).

2.1. Técnica Funcional


Essa técnica também é conhecida como teste caixa preta (Myers, 1979)
pelo fato de tratar o software como uma caixa em que o conteúdo é
desconhecido e da qual só é possível visualizar o lado externo, ou seja, os
dados de entrada fornecidos e as respostas produzidas como saída. As
funções do sistema, identificadas a partir de sua especificação, são verificadas
sem se preocupar com detalhes de implementação. Dessa forma, é essencial
que a especificação esteja correta e de acordo com os requisitos do usuário
(DeMillo, 1987).
Alguns critérios de teste funcional são:
 Particionamento em Classes de Equivalência: divide o domínio de
entrada de um programa em classes de equivalência válidas e inválidas, a
partir das condições de entrada identificadas no programa. Seleciona-se o
menor número possível de casos de teste, partindo-se do princípio que um
elemento de uma classe é representativo para toda a sua classe. O uso de
particionamento permite examinar os requisitos com mais detalhes e
restringir o número de casos de teste existentes.
 Análise do Valor Limite: complementa o critério Particionamento em
Classes de Equivalência, testando os limites de cada classe de
equivalência. Ao invés de serem selecionados quaisquer elementos para
cada classe, os casos de teste são selecionados das fronteiras de cada
classe, pois é nesses casos que os erros costumam ocorrer com mais
freqüência (Pressman, 1997).

/**********************************************************************************************
IDENTIFIER.C

ESPECIFICAÇÃO: O programa deve determinar se um identificador é ou não válido em 'Silly Pascal'


(uma estranha variante do Pascal). Um identificador válido deve começar com uma letra e conter
apenas letras ou dígitos. Além disso, deve ter no mínimo 1 e no máximo 6 caracteres de
comprimento. Fornecido um identificador o programa exibe uma mensagem dizendo se o mesmo é
válido ou inválido.

**********************************************************************************************/
#include <stdio.h>
main ()
/* 01 */ {
/* 01 */ char achar;
/* 01 */ int length, valid_id;
/* 01 */ length = 0;
/* 01 */ printf ("Digite um possível identificador\n");
/* 01 */ printf ("seguido por <ENTER>: "); int valid_starter (ch)
/* 01 */ achar = fgetc (stdin); char ch;
/* 01 */ valid_id = valid_starter (achar); {
/* 01 */ if (valid_id) if (((ch >= 'A') &&
/* 02 */ { (ch <= 'Z')) ||
/* 02 */ length = 1; ((ch >= 'a') &&
/* 02 */ } (ch <= 'z')))
/* 03 */ achar = fgetc (stdin); return (1);
/* 04 */ while (achar != '\n') else
/* 05 */ { return (0);
/* 05 */ if (!(valid_follower (achar))) }
/* 06 */ {
/* 06 */ valid_id = 0; int valid_follower (ch)
/* 06 */ } char ch;
/* 07 */ length++; {
/* 07 */ achar = fgetc (stdin); if (((ch >= 'A') &&
/* 07 */ } (ch <= 'Z')) ||
/* 08 */ if (valid_id && (length >= 1) && ((ch >= 'a') &&
(length < 6) ) (ch <= 'z')) ||
/* 09 */ { ((ch >= '0') &&
/* 09 */ printf ("Valido\n"); (ch <= '9')))
/* 09 */ } return (1);
/* 10 */ else else
/* 10 */ { return (0);
/* 10 */ printf ("Invalido\n"); }
/* 10 */ }
/* 11 */ }

Figura 1. Programa exemplo: identifier (contém ao menos um erro) (Maldonado et al., 1998).

 Grafos de Causa e Efeito: explora a combinação das condições de


entrada do programa. São identificadas as possíveis condições de entrada
(causas) e as possíveis ações (efeitos) as quais são combinadas dando
origem a um grafo. A partir desse grafo é criada uma tabela de decisão a
partir da qual são derivados os casos de teste.
 Error Guessing: nesse critério os possíveis erros são listados, com base
nas características e no domínio de entrada do software, e os casos de
teste são elaborados baseando-se nessa lista.
Em geral, o teste funcional é uma técnica sujeita às inconsistências que
podem ocorrer na especificação (DeMillo, 1987). Outro problema com essa
técnica é a dificuldade de quantificar a atividade de teste, visto que não se
pode garantir que partes essenciais ou críticas do programa sejam
executadas. Além disso, devido à dificuldade em automatizar a aplicação
desses critérios, eles ficam, em geral, restritos à aplicação manual.
Para ilustrar a técnica funcional, o critério Particionamento em Classes de
Equivalência foi aplicado ao programa identifier. Na Tabela 1 são apresentadas
as condições de entrada e classes de equivalência válidas e inválidas obtidas.
A partir dessas classes o seguinte conjunto de casos de teste poderia ser
definido:T0={(a1,Válido),(2B3,Inválido), (Z-12, Inválido), (A1b2C3d, Inválido)}.
De posse do conjunto T0, seria natural indagar se esse conjunto exercita todos
os comandos ou todos os desvios de fluxo de controle de uma dada
implementação. Usualmente, lança-se mão de critérios estruturais de teste,
apresentados a seguir, como critérios de adequação ou critérios de cobertura
para se analisar questões como essas, propiciando a quantificação e a
qualificação da atividade de teste de acordo com o critério escolhido. Quanto
mais rigoroso o critério utilizado e se erros não forem revelados maior a
confiança no produto em desenvolvimento.

Tabela 1. Classes de Equivalência para o Programa identifier.


Restrições de Entrada Classes Classes
Válidas Inválidas
Tamanho (t) do identificador 1t6 t>6
(1) (2)
Primeiro caracter (c) é uma letra Sim Não
(3) (4)
Contém somente caracteres Sim Não
válidos (5) (6)

2.2. Técnica Estrutural


A técnica estrutural, também conhecida como teste caixa branca (em
oposição à caixa preta), utiliza aspectos de implementação para derivar os
requisitos de teste, baseando-se no conhecimento da estrutura interna da
implementação. A maioria dos critérios dessa técnica utiliza uma
representação do programa conhecida como Grafo de Fluxo de Controle ou
Grafo de Programa. Um grafo de fluxo de controle é um grafo orientado sendo
que cada vértice (ou nó) representa um bloco indivisível de comandos e cada
aresta representa um desvio de um bloco para outro. Um bloco desse tipo tem
as seguintes características: não existem desvios para o meio do bloco e uma
vez que o primeiro comando do bloco seja executado, todos os demais
comandos do bloco são executados seqüencialmente. Através do grafo de
programa escolhem-se os componentes que devem ser executados,
caracterizando assim o teste estrutural. Na Figura 2 é apresentado o grafo de
fluxo de controle do programa identifier, gerado pela ferramenta ViewGraph
(Vilela et al., 1997). Os números à esquerda dos comandos da Figura 1
representam os blocos de comando desse programa.

Figura 2. Grafo de Fluxo de Controle do Programa identifier


Gerado pela ViewGraph (Vilela et al., 1997).

Os critérios estruturais baseiam-se em tipos de estruturas diferentes para


determinar quais partes do programa são requeridas na execução. Os critérios
de teste estrutural são classificados em:
 Critérios Baseados na Complexidade: utilizam informações sobre a
complexidade do programa para determinar os requisitos de teste. Um
critério bastante conhecido dessa classe é o critério de McCabe que
utiliza a complexidade ciclomática para derivar os requisitos de teste.
Essencialmente, esse critério requer que seja executado um conjunto
de caminhos linearmente independente do grafo de programa
(Pressman, 1997).
 Critérios Baseados em Fluxo de Controle: utilizam apenas
características de controle da execução do programa, como comandos
ou desvios, para derivar os requisitos de teste. Os critérios mais
conhecidos dessa classe são Todos-Arcos, Todos-Nós e Todos-
Caminhos. Esses critérios são descritos a seguir.
 Critérios Baseados em Fluxo de Dados: utilizam, como o próprio
nome já diz, informações sobre o fluxo de dados do programa para
derivar os requisitos de teste, ou seja, esses critérios requerem que
sejam testadas as interações que envolvem definições de variáveis e
referências a essas definições. Exemplos de critérios dessa classe são
os critérios de Rapps e Weyuker (1985), de Ural e Yang (1988) e os
critérios Potenciais-Usos (Maldonado, 1991). Dada a importância
desses critérios na atividade de teste de programas, esses critérios são
descritos com mais detalhes a seguir.
O conjunto de casos de teste obtido durante a aplicação dos critérios
funcionais pode corresponder ao conjunto inicial para os testes estruturais.
Como, em geral, o conjunto de casos de teste funcional não é suficiente para
satisfazer totalmente um critério de teste estrutural, novos casos de teste são
gerados e adicionados ao conjunto até que se consiga o grau de satisfação
desejado, explorando-se, dessa forma, os aspectos complementares das duas
técnicas de teste.
Um problema relacionado ao teste estrutural é a impossibilidade de se
determinar automaticamente se um caminho é ou não executável, ou seja, não
existe um algoritmo que dado um caminho completo qualquer decida se o
caminho é executável e forneça o conjunto de valores que causam a execução
desse caminho (Vergílio et al., 1993). Sendo assim, é necessária a intervenção
do testador para determinar quais são os caminhos não executáveis para o
programa sendo testado.

2.2.1. Critérios Baseados em Fluxo de Controle


Esses critérios podem ser considerados os primeiros critérios estruturais
definidos. Como descrito anteriormente, esses critérios utilizam informações de
controle do programa, obtidas a partir do Grafo de Programa, para derivar os
requisitos de teste.
Os critérios de fluxo de controle mais conhecidos são (Zhu et al., 1997):
 critério Todos-Nós – requer que todos os comandos do programa sejam
executados no mínimo uma vez pelo conjunto de casos de teste. Isso
significa executar todos os nós ou vértices do grafo de programa. Esse
critério é considerado o mais fraco dos critérios estruturais, pois os
requisitos dele não incluem, em geral, todas as transferências de controle
do programa.
 critério Todos-Arcos – requer que toda transferência de controle do
programa, ou todas as arestas do grafo de programa sejam exercitadas
pelo menos uma vez pelo conjunto de casos de teste. Esse critério é mais
forte que o critério Todos-Nós porque se todos os arcos do grafo de
programa são cobertos, todos os nós são necessariamente cobertos
também. Portanto, um conjunto de casos de teste que satisfaz o critério
Todos-Arcos também satisfaz o critério Todos-Nós. Entretanto, mesmo que
todos os arcos do programa sejam testados não significa que todas as
combinações de transferência de controle são testadas.
 critério Todos-Caminhos – requer que todos os caminhos possíveis do
programa sejam executados, o que significa que todas as combinações de
arcos, ou de transferência de controle sejam incluídas pelos requisitos de
teste desse critério. Embora esse critério seja mais forte que os critérios
Todos-Nós e Todos-Arcos, um número infinito de caminhos pode existir em
programas com comandos de repetição (loops), tornando esse critério
impraticável.
Algumas derivações do critério Todos-Caminhos surgiram com o intuito de
torná-lo mais prático, como por exemplo o critério Todos-Caminhos-Simples,
que requer que todos os caminhos simples sejam executados no mínimo uma
vez pelo conjunto de casos de teste. Um caminho simples é um caminho P tal
que todos os nós que compõem esse caminho, exceto possivelmente o
primeiro e o último, são distintos.
Comandos de repetição ou laços aumentam a complexidade do programa
e ocasionam um número infinito de caminhos para serem testados. Alguns
critérios de teste são propostos para testar programas com laços, por exemplo,
o critério Todos-k-Laços (loop count-k) que requer que todo laço do programa
em teste seja executado no máximo k (um número inteiro) vezes pelo conjunto
de casos de teste.
Segundo Howden (1987), mesmo que se limite o número de iterações de
um laço a duas ou três vezes, ainda assim, geralmente ter-se-á um número
bastante grande de caminhos a serem testados. Neste sentido, os critérios de
Fluxo de Dados são mais seletivos pois, além de promoverem a concatenação
de arcos, conduzem à seleção de caminhos com uma maior relação com os
aspectos funcionais do programa.

2.2.2. Critérios Baseados em Fluxo de Dados


Os critérios de fluxo de dados foram propostos por Rapps e Weyuker
(1985), com o objetivo de fornecer uma hierarquia de critérios entre os critérios
de fluxo de controle Todos-Arcos e Todos-Caminhos e tornar o teste estrutural
mais rigoroso. Ao invés de selecionar os caminhos somente baseando-se na
estrutura de controle, como ocorre com os critérios de fluxo de controle, os
critérios de fluxo de dados estabelecem associações entre definições de
variáveis no programa e subsequentes referências a essas definições para
derivar os caminhos a serem percorridos pelos casos de teste.
Uma variável é definida quando: i) ela está no lado esquerdo de um
comando de atribuição, ii) ela está em um comando de entrada, ou iii) ela está
em uma chamada de procedimentos como parâmetro de saída. Uma variável é
referenciada quando seu valor é utilizado, podendo ser um uso computacional
(c-uso), quando a variável é usada do lado direito de uma expressão, ou ser
um uso predicativo (p-uso), quando a variável é utilizada em um predicado ou
condição, afetando o fluxo de controle do programa.
As associações entre definições-usos de variáveis são obtidas a partir de
um grafo proposto por Rapps e Weyuker (1985), chamado Grafo Def-Uso, que
consiste de uma extensão do grafo de programa (descrito anteriormente). Para
cada nó i do grafo Def-Uso são estabelecidos os conjuntos c-use(i) – conjunto
de variáveis com c-uso no nó i, e def(i) – conjunto de variáveis com definições
no nó i. Para cada arco (i,j) do grafo é estabelecido o conjunto p-use(i,j) –
conjunto de variáveis com p-uso no arco (i,j). Na Figura 3 é ilustrado o Grafo
Def-Uso do programa identifier.
Os critérios mais conhecidos da Família de critérios definidos por Rapps e
Weyuker (1985) são:
 Critério Todas-Definições: requer que cada definição de variável seja
exercitada pelo menos uma vez, não importa se por um c-uso ou por um
p-uso.
 Critério Todos-Usos: requer que todas as associações entre uma
definição de variável e seus subseqüentes usos (c-usos e p-usos) sejam
exercitadas pelos casos de teste, através de pelo menos um caminho
livre de definição, ou seja, um caminho onde a variável não é redefinida.

up = {valid_id} d = {length, valid_id, achar}


1 uc = {achar}
d = {length} 2 up = {valid_id}
3 d = {achar}

4
up = {achar}

5 up = {achar}
up = {achar} 6 up = {achar}
d = {valid_id}
7
d = {achar, length}
uc = {length}

up = {valid_id, length} 8 up = {valid_id, length}


d = definição
9 10 up = uso predicativo
11 uc = uso computacional

Figura 3. Grafo Def-Uso do Programa identifier.

Por exemplo, para exercitar a definição da variável length definida no nó 1,


de acordo com o critério Todas-Definições, poderiam ser executados um dos
seguintes subcaminhos: (1,3,4,5,7); (1,3,4,8,9); (1,3,4,8,10); e (1,3,4,5,6,7). O
subcaminho (1,3,4,8,9) é não executável, e qualquer caminho completo que o
inclua também é não executável. Se qualquer um dos demais caminhos for
exercitado, o requisito de teste estaria sendo satisfeito, e para satisfazer o
critério Todas-Definições esta análise teria que ser feita para toda definição que
ocorre no programa. Em relação ao critério Todos-Usos, com respeito à mesma
definição, seriam requeridas as seguinte associações: (1,7,length);
(1,(8,9),length) e (1,(8,10),length). As notações (i,j,var) e (i,(j,k),var) indicam
que a variável var é definida no nó i e existe um uso computacional de var no
nó j ou um uso predicativo de var no arco (j, k), respectivamente, bem como
pelo menos um caminho livre de definição do nó i ao nó j ou ao arco (j,k).
Observe que a associação (1,(8,9), length) é não executável pois o único
caminho que livre de definição possível de exercitá-la seria um caminho que
incluísse o subcaminho (1,3,4,8,9). Já para a associação (1,7,length) qualquer
caminho completo executável incluindo um dos subcaminhos (1,3,4,5,6,7),
(1,3,4,5,7) seria suficiente para exercitá-la. Esta mesma análise deveria ser
feita para todas as demais variáveis e associações pertinentes, a fim de
satisfazer o critério Todos-Usos.
A maior parte dos critérios baseados em fluxo de dados, para requerer um
determinado elemento (associação, caminho, etc.) exige a ocorrência explícita
de um uso de variável e não garante, necessariamente, a inclusão do critérios
Todos-Arcos na presença de caminhos não executáveis, presentes na maioria
dos programas (Maldonado, 1991).
Com a introdução do conceito potencial-uso, Maldonado define uma
família de critérios de teste, denominada Família de Critérios Potenciais-Usos
(1991). Esses critérios requerem associações, independentemente da
ocorrência explícita de uma referência a uma determinada variável, ou seja,
requer que caminhos livres de definição a partir da definição de uma
determinada variável sejam executados, independentemente de ocorrer uso
dessa variável nesse caminho. Por exemplo, as potenciais associações
(1,6,length) e (7,6,length) são requeridas pelo critério Todos-Potenciais-Usos
mas não seriam requeridas pelos demais critérios de fluxo de dados que não
fazem uso do conceito potencial uso. Observe-se que, por definição, toda
associação é uma potencial associação. Dessa forma, as associações
requeridas pelo critério Todos-Usos são um subconjunto das potenciais
associações requeridas pelo critério Todos-Potenciais-Usos.
Da mesma forma que os demais critérios de fluxo de dados, os critérios
Potenciais-Usos podem utilizar o grafo Def-Uso para estabelecer os requisitos
de teste. Um dos critérios dessa família é o critério Todos-Potenciais-Usos:
 Todos-Potenciais-Usos: requer, basicamente, para todo nó i e para
toda variável x, para a qual existe uma definição em i, que pelo menos
um caminho livre de definição com relação à variável (c.r.a) x do nó i
para todo nó e para todo arco possível de ser alcançado a partir de i por
um caminho livre de definição c.r.a. x seja exercitado.
Os critérios Potencias-Usos satisfazem os requisitos básicos exigidos de
um bom critério de teste, sendo que, mesmo na presença de caminhos não
executáveis, esses estabelecem uma hierarquia de critérios entre os critérios
Todos-Arcos e Todos-Caminhos. Além disso, esses critérios incluem o critério
Todas-Definições e necessitam um número finito de casos de teste. Outra
característica é que nenhum outro critério baseado em fluxo de dados inclui os
critérios Potenciais-Usos (Maldonado, 1991).
Os critérios estruturais têm sido utilizados principalmente no teste de
unidade, uma vez que os requisitos de teste por eles exigidos limitam-se ao
escopo de unidade. Várias pesquisas que procuram estender o uso de critérios
estruturais para o teste de integração podem ser identificadas. Linnenkugel e
Müllerburg (1990) propõem uma série de critérios que estendem os critérios
baseados em fluxo de controle e em fluxo de dados para o teste de integração.
Harrold e Soffa (1991) apresentam uma técnica para determinar as estruturas
de definição-uso interprocedurais permitindo a aplicação dos critérios de fluxo
de dados no nível de integração. Vilela (1998) estende os critérios Potenciais-
Usos para o teste de integração.
A Poke-Tool – Potential Uses Criteria Tool for Program Testing,
desenvolvida na Faculdade de Engenharia Elétrica da Universidade Estadual
de Campinas, é uma ferramenta de teste que apóia a aplicação dos critérios
Potenciais-Usos, Todos-Nós e Todos-Arcos (Maldonado et al., 1989). Devido à
essa ferramenta ser multilinguagem, existem versões para o teste de unidade
de programas na linguagem C (Chaim, 1991), Fortran (Fonseca, 1993) e Cobol
(Leitão, 1992). A ferramenta Poke-Tool é orientada à sessão de trabalho,
sendo que o usuário entra com o programa a ser testado, com o conjunto de
dados de teste e seleciona todos ou alguns dos critérios suportados. Como
saída, a ferramenta fornece ao usuário os seguintes resultados: conjunto de
arcos primitivos, grafo def-uso do programa, programa instrumentado para
teste, conjunto de associações necessárias para satisfazer o critério
selecionado e conjunto de associações ainda não exercitadas. O conjunto de
arcos primitivos consiste de arcos que uma vez executados garantem a
execução de todos os demais arcos do grafo de programa. Atualmente, essa
ferramenta encontra-se disponível para os ambientes DOS e UNIX. A versão
para DOS possui interface simples, baseada em menus. A versão para UNIX
possui módulos funcionais cuja utilização se dá através de interface gráfica ou
linha de comando (shell scripts). Na Figura 4 é apresentada a tela principal
dessa ferramenta em que se pode visualizar as suas principais funcionalidades.

Figura 4. Opções Disponíveis na Ferramenta Poke-Tool.

Para ilustrar a técnica estrutural, os critérios Todos-Arcos e Todos-


Potenciais-Usos foram aplicados ao programa identifier. Na Tabela 2 e 3 são
apresentados os requisitos desses critérios, respectivamente. A notação <i,
(j,k), {v1, ..., vn}> representa o conjunto de associações <i, (j,k), v1>,...,<i,(j,k),
vn>; ou seja, <i, (j,k), {v1,..., vn}> indica que existe pelo menos um caminho livre
de definição c.r.a v1, ..., vn do nó i ao arco (j,k). Observe-se que podem existir
outros caminhos livres de definição c.r.a algumas das variáveis v1, ..., vn mas
que não sejam, simultaneamente, livres de definição para todas as variáveis v1,
..., vn.
Utilizando o conjunto de casos de teste T0 = {(a1, Válido), (2B3,
Inválido), (Z-12, Inválido), (A1b2C3d, Inválido)} gerado anteriormente
procurando satisfazer o critério Particionamento em Classes de Equivalência,
observa-se qual a cobertura (em %) obtida em relação aos critérios Todos-
Arcos e Todos-Potenciais-Usos (Figura 5(a) e Figura 5(b), respectivamente).
Ainda na Figura 5(b), são ilustrados para o critério Todos-Potenciais-Usos os
elementos requeridos e não executados quando a cobertura é inferior a 100%.
Tabela 2. Elementos Requeridos pelo Critério Todos-Arcos.
Arcos Primitivos
Arco (1,2) Arco (1,3) Arco (5,6) Arco (5,7) Arco (8,9) Arco (8,10)

Tabela 3. Elementos Requeridos pelo Critério Todos-Potenciais-Usos.


Associações Requeridas
1) <1,(6,7),{ length }> 17) <2,(6,7),{ length }>
2) <1,(1,3),{achar, length, valid_id }> 18) <2,(5,6),{ length }>
3) <1,(8,10),{ length, valid_id }> 19) <3,(8,10),{ achar }>
4) <1,(8,10),{ valid_id }> 20) <3,(8,9),{ achar }>
5) <1,(8,9),{ length, valid_id }> 21) <3,(5,7),{ achar }>
6) <1,(8,9),{ valid_id }> 22) <3,(6,7),{ achar }>
7) <1,(7,4),{ valid_id }> 23) <3,(5,6),{ achar }>
8) <1,(5,7),{ length, valid_id }> 24) <6,(8,10),{ valid_id }>
9) <1,(5,7),{ valid_id }> 25) <6,(8,9),{ valid_id }>
10) <1,(5,6),{ length, valid_id }> 26) <6,(5,7),{ valid_id }>
11) <1,(5,6),{ valid_id }> 27) <6,(5,6),{ valid_id }>
12) <1,(2,3),{ achar, valid_id }> 28) <7,(8,10),{ achar, length }>
13) <1,(1,2),{ achar, length, valid_id }> 29) <7,(8,9),{ achar, length }>
14) <2,(8,10),{ length }> 30) <7,(5,7),{ achar, length }>
15) <2,(8,9),{ length }> 31) <7,(6,7),{ achar, length }>
16) <2,(5,7),{ length }> 32) <7,(5,6),{ achar, length }>

(a) (b)
Figura 5. Relatórios Gerados pela Ferramenta Poke-Tool em Relação ao Programa identifier:
(a) Arcos Executados, (b) Associações não Executadas.

Observa-se que somente com os casos de teste funcionais foi possível


cobrir o critério Todos-Arcos ao passo que para se cobrir o critério Todos-
Potenciais-Usos ainda é necessário analisar as associações que não foram
executadas. Deve-se ressaltar que o conjunto T0 é Todos-Arcos-adequado, ou
seja, o critério Todos-Arcos foi satisfeito e o erro presente no programa
identifier não foi revelado. Certamente, um conjunto adequado ao critério
Todos-Arcos que revelasse o erro poderia ter sido gerado; o que se ilustra aqui
é que não necessariamente a presença do erro é revelada.
Desejando-se melhorar a cobertura em relação ao critério Todos-
Potenciais-Usos, novos casos de teste devem ser inseridos visando a cobrir as
associações que ainda não foram executadas. Primeiramente, deve-se
verificar, entre as associações não executadas, se existem associações não
executáveis. No caso, as associações <1,(8,9),{length, valid_id}>,
<2,(8,10),{length}> e <6,(8,9),{valid_id}> são não executáveis. Na Tabela 4
esse processo é ilustrado até que se atinja a cobertura de 100% para o critério
Todos-Potenciais-Usos.

Tabela 4. Ilustração da Evolução da Sessão de Teste para cobrir


o Critério Todos-Potenciais-Usos.

Associações Requeridas T0 T1 T2 Associações Requeridas T0 T1 T2


1) <1,(6,7),{ length }>  17) <2,(6,7),{ length }> 
2)<1,(1,3),{achar,length, valid_id }>  18) <2,(5,6),{ length }> 
3) <1,(8,10),{ length, valid_id }>  19) <3,(8,10),{ achar }> 
4) <1,(8,10),{ valid_id }>  20) <3,(8,9),{ achar }> 
5) <1,(8,9),{ length, valid_id }> * * * 21) <3,(5,7),{ achar }> 
6) <1,(8,9),{ valid_id }>  22) <3,(6,7),{ achar }> 
7) <1,(7,4),{ valid_id }>  23) <3,(5,6),{ achar }> 
8) <1,(5,7),{ length, valid_id }>  24) <6,(8,10),{ valid_id }> 
9) <1,(5,7),{ valid_id }>  25) <6,(8,9),{ valid_id }> * * *
10) <1,(5,6),{ length, valid_id }>  26) <6,(5,7),{ valid_id }> 
11) <1,(5,6),{ valid_id }>  27) <6,(5,6),{ valid_id }> 
12) <1,(2,3),{ achar, valid_id }>  28) <7,(8,10),{ achar, length }> 
13)<1,(1,2),{achar,length,valid_id }>  29) <7,(8,9),{ achar, length }> 
14) <2,(8,10),{ length }> * * * 30) <7,(5,7),{ achar, length }> 
15) <2,(8,9),{ length }>  31) <7,(6,7),{ achar, length }> 
16) <2,(5,7),{ length }>  32) <7,(5,6),{ achar, length }> 
T0 = { (a1, Válido), (2B3, Inválido), (Z-12, Inválido), (A1b2C3d, Inválido)}
T1 = T0  {(1#, Inválido), (%, Inválido), (c, Válido)}
T2 = T1  {(#-%, Inválido)}

Observa-se que mesmo tendo satisfeito um critério mais rigoroso como


o critério Todos-Potenciais-Usos, a presença do erro ainda não foi revelada.
Assim, motiva-se a pesquisa de critérios de teste que exercitem os elementos
requeridos com maior probabilidade de revelar erros. Outra perspectiva que se
coloca é utilizar uma estratégia de teste incremental, que informalmente
procura-se ilustrar neste texto. Em primeiro lugar foram exercitados os
requisitos de teste requeridos pelo critério Todos-Arcos, em seguida os
requeridos pelo critério Todos-Potenciais-Usos, e, posteriormente, poder-se-ia
considerar o critério Análise de Mutantes (descrito na próxima seção), que do
ponto de vista teórico é incomparável com os critérios baseados em Fluxo de
Dados, mas em geral de maior custo de aplicação. Esse aspecto será ilustrado
na próxima seção.
2.3. Técnica Baseada em Erros
A técnica de teste baseada em erros utiliza informações sobre os tipos de
erros mais comuns cometidos durante o processo de desenvolvimento de um
software (DeMillo, 1978).
A ênfase dessa técnica está nos erros que o programador ou o projetista
pode cometer durante o processo de desenvolvimento, e nas abordagens que
podem ser usadas para detectar a sua ocorrência.
Os critérios mais conhecidos dessa técnica são:
 Critério Semeadura de Erros: neste critério alguns erros são inseridos
“artificialmente” no programa. Então, dos erros encontrados durante o
teste verificam-se quais são naturais e quais são artificiais. A razão dos
erros artificiais pelos naturais representa, teoricamente, o número de
erros naturais ainda existentes no programa e serve para medir a
adequação do conjunto de casos de teste empregados. Entretanto, os
resultados são dependentes de como os defeitos são introduzidos, os
quais, normalmente, são introduzidos manualmente. Além disso, nem
sempre os defeitos artificias são equivalentes aos defeitos naturais
tornando difícil a identificação dos defeitos naturais. O critério Análise
de Mutantes, descrito a seguir, procura amenizar esses problemas
introduzindo defeitos no programa de uma maneira mais sistemática
(Zhu et al., 1997).
 Critério Análise de Mutantes: a partir de pequenas modificações
sintáticas, introduzidas sistematicamente no programa, são geradas
versões ligeiramente diferentes do programa chamadas de mutantes. O
objetivo é obter casos de teste que consigam revelar, através da
execução do programa, as diferenças de comportamento existentes
entre o programa original e seus mutantes (DeMillo, 1987). Este critério
é descrito com detalhes a seguir.

2.3.1. Critério Análise de Mutantes


O critério Análise de Mutantes surgiu na década de 70 na YALE University
e Georgia Institute of Technology, possuindo um forte relacionamento com um
método clássico para detecção de erros lógicos em circuitos digitais,
conhecido como modelo de teste de falha única (DeMillo, 1980).
Um dos primeiros artigos que descrevem a idéia de teste de mutantes foi
publicado em 1978 (DeMillo, 1978). Neste artigo, DeMillo apresenta a filosofia
básica da técnica, conhecida como Hipótese do Programador Competente:
“Programadores possuem uma grande vantagem que é pouco explorada: eles
costumam criar programas muito próximos do correto!”. Assumindo a validade
dessa hipótese, o autor afirma que erros são introduzidos nos programas
através de desvios sintáticos que, apesar de não causarem erros sintáticos,
alteram a semântica do programa e, como conseqüência, conduzem o
programa a um comportamento incorreto. Para revelar tais erros, a Análise de
Mutantes identifica os desvios sintáticos mais comuns e, aplicando pequenas
transformações sobre o programa em teste, encoraja o testador a construir
casos de teste que mostrem que tais transformações conduzem a um
programa incorreto (Agrawal, et al., 1989).
Outra hipótese explorada na aplicação do critério Análise de Mutantes é o
Efeito de Acoplamento, a qual assume que erros complexos são “acoplados” a
erros simples de modo que um conjunto de casos de teste capaz de detectar
erros simples irá também detectar uma alta porcentagem de erros complexos
(DeMillo, 1978). Nesse sentido, aplica-se uma mutação de cada vez no
programa em teste, ou seja, cada mutante contém apenas uma transformação
sintática. Estudos realizados por Offut (1992) indicam a validade dessa
hipótese reforçando a premissa de que a Análise de Mutantes é bem
fundamentada.
A aplicação do critério da Análise de Mutantes envolve as seguintes
atividades: execução do programa com um conjunto de casos de teste T,
geração dos mutantes, execução dos mutantes com T e análise dos mutantes.
Um programa P é testado com um conjunto de casos de teste T. Se o
programa funciona corretamente, então P sofre pequenas perturbações,
gerando mutantes que são executados com T. Caso o comportamento de P’
(um mutante de P) seja diferente de P, então esse mutante é dito “morto”.
Caso contrário, esse mutante está “vivo” devido a um dos dois motivos: 1) o
conjunto T não é suficiente (adequado) para distinguir o comportamento de P e
P’ e, com isso novos casos de teste devem ser incluídos ao conjunto; ou 2) P’
é dito equivalente a P, ou seja, para qualquer dado do domínio de entrada o
comportamento dos dois programas não difere.
Os mutantes são gerados a partir de operadores de mutação que são
específicos para uma linguagem de programação ou técnica de especificação
e procuram sintetizar os erros mais comuns nessa linguagem ou técnica.
Assim, operadores de mutação são as regras que definem as alterações que
devem ser aplicadas no programa em teste. Segundo Offut et al. (1996b), os
operadores de mutação são construídos para ou: i) induzir mudanças
sintáticas simples com base nos erros típicos cometidos pelos programadores;
ou 2) forçar determinados objetivos de teste, como por exemplo, executar cada
arco do programa.
Conseguindo-se obter casos de teste que resultem em apenas mutantes
mortos e equivalentes tem-se um conjunto de casos de teste adequado ao
programa em teste. Significa também que se esse programa possui erros, são
erros pouco prováveis de ocorrerem, de acordo com o efeito de acoplamento,
o que é fortemente dependente do tipo de mutante gerado para esse programa
(DeMillo, 1978).
É importante observar que a equivalência entre programas é uma questão
indecidível e requer a intervenção do testador. Alguns métodos e heurísticas
têm sido propostos para determinar a equivalência de programas em uma
grande porcentagem de casos de interesse (Budd, 1981; Offut e Pan, 1996).
O critério Análise de Mutantes fornece uma medida objetiva para avaliar a
cobertura de um conjunto de casos de teste em relação ao programa em teste.
Essa medida, chamada de escore de mutação, relaciona o número de
mutantes mortos com o número de mutantes gerados e é calculada da
seguinte maneira:
DM(P,T)
ms(P,T) =
M(P) - EM(P)

sendo que:
DM(P,T): número de mutantes mortos pelo conjunto de casos de teste T.
M(P): número de mutantes gerados para o programa P.
EM(P): número de mutantes equivalentes ao programa P.
O escore de mutação varia no intervalo de [0,1], sendo que quanto maior o
escore mais adequado é o conjunto de casos de teste para o programa em
teste. Percebe-se com essa fórmula que somente DM(P,T) é dependente do
conjunto de casos de teste utilizado e que EM(P) é obtido à medida que o
testador decide, manualmente ou com o apoio de heurísticas, que determinado
mutante vivo é equivalente.
Um dos maiores problemas para a aplicação do critério Análise de
Mutantes está relacionado ao seu alto custo, uma vez que o número de
mutantes gerados, mesmo para pequenos programas, pode ser muito grande,
exigindo um tempo de execução muito alto.
Algumas soluções têm sido propostas objetivando viabilizar a aplicação do
critério Análise de Mutantes. A utilização de arquiteturas de hardware
avançadas e o uso de análise estática de anomalias de fluxo de dados para
reduzir o número de mutantes gerados são algumas dessas soluções
(Krauser, et al., 1988; Mathur e Krauser, 1988; Marshall, et al., 1990; Choi e
Mathur, 1993).
Critérios de teste alternativos a partir da Análise de Mutantes também têm
sido definidos com o objetivo de reduzir os custos de aplicação desse critério.
Esses critérios alternativos procuram selecionar apenas um subconjunto do
total de mutantes gerados, reduzindo o custo, mas procurando não reduzir a
eficácia em revelar erros. Offut e Rothermel (1993) definem a mutação
seletiva, que seleciona alguns operadores de mutação para geração dos
mutantes, por exemplo, descartando aqueles que geram um número elevado
de mutantes em relação aos demais. Mathur e Wong (1993) apresentam dois
critérios alternativos: mutação aleatória – que examina uma pequena
porcentagem de mutantes selecionados aleatoriamente de cada operador de
mutação e ignora os demais, e mutação restrita – que seleciona alguns tipos
de operadores de mutação para geração dos mutantes. Outros trabalhos
nessa linha, são os trabalhos de Offut et al. (1996b) e Barbosa et al. (1998), os
quais definem um conjunto essencial de operadores de mutação para o teste
de programas Fortran e programas C, respectivamente.
Um aspecto importante para a aplicação da Análise de Mutantes é a
disponibilidade de ferramentas que automatizem esse critério. Nesse sentido,
algumas ferramentas podem ser citadas. Mothra é um ambiente de teste
baseado no critério Análise de Mutantes para programas na linguagem
Fortran-77, com 22 operadores de mutação implementados (DeMillo et al.,
1988). A interface utilizada é baseada em janelas o que facilita a realização
dos testes; permite incorporar outras ferramentas como gerador de casos de
teste, verificador de equivalência e oráculo.
A ferramenta Proteum/C – Program Test Using Mutants, desenvolvida pelo
Grupo de Engenharia de Software do Instituto de Ciências Matemáticas e de
Computação de São Carlos – USP, é outra ferramenta que apóia a aplicação
do critério Análise de Mutantes (Delamaro, 1993). Essa ferramenta foi
desenvolvida a partir dos operadores de mutação definidos por Agrawal et al.
(1989) para a linguagem C e dá suporte ao teste de unidade para programas
nessa linguagem. Devido ao seu aspecto de multilinguagem ela pode ser
configurada para o teste de programas escritos em diferentes linguagens. A
Proteum/C oferece os seguintes recursos para a condução da atividade de
teste: definição de casos de teste, execução do programa em teste, seleção
dos operadores de mutação que serão utilizados para gerar os mutantes,
geração dos mutantes, execução dos mutantes com os casos de teste
definidos, análise dos mutantes vivos e cálculo do escore de mutação. As
funções implementadas na Proteum possibilitam que alguns desses recursos
sejam executados automaticamente (como a execução dos mutantes),
enquanto que para outros são fornecidas facilidades para que o testador possa
realizá-los (como a análise de mutantes equivalentes) (Delamaro, 1993; 1997).
A utilização da ferramenta pode ser feita através de um ambiente gráfico ou na
forma de comandos (scripts de teste). Esta última forma é muito útil para a
realização de experimentos empíricos, pois reduz as interações do testador
com a ferramenta diminuindo, com isso, o tempo consumido na atividade de
teste. Na Figura 6 é apresentada a tela principal da ferramenta Proteum/C, em
que é possível visualizar os recursos fornecidos por ela.
A Proteum/C possui 71 operadores de mutação implementados, os
quais são divididos em 4 classes (Delamaro, 1993): mutação de comandos,
mutação de operadores, mutação de variáveis e mutação de constantes. É
possível escolher os operadores de acordo com a classe de erros que se
deseja enfatizar, permitindo que a geração de mutantes seja feita em etapas ou
até mesmo dividida entre vários testadores trabalhando independentemente.
Na Tabela 5 são ilustrados alguns operadores de mutação disponíveis.
Figura 6. Operações Disponíveis na Interface da Proteum/C.

Tabela 5. Exemplos de Operadores de Mutação para Programas C.


Operador Descrição
SSDL Retira um comando de cada vez do programa.
ORRN Substitui um operador relacional por outro operador relacional.
VTWD Substitui a referência escalar pelo seu valor sucessor e predecessor.
Ccsr Substitui referências escalares por constantes.
SWDD Substitui o comando while por do-while.
SMTC Interrompe a execução do laço após duas execuções.
OLBN Substitui operador lógico por operador bitwise.
Cccr Substitui uma constante por outra constante.
VDTR Força cada referência escalar a possuir cada um dos valores: negativo,
positivo e zero.

O critério Análise de Mutantes tem sido utilizado principalmente no teste de


unidade de programas. Delamaro (1997) define o critério Mutação de Interface
para o teste de integração, explorando erros de interface relacionados com a
conexão entre as unidades de programas implementados na linguagem C. A
idéia é caracterizar erros simples de integração que podem ocorrer na
passagem de dados por parâmetros ou por referência, no uso de variáveis
globais, ou no comando return do módulo chamado. Um modelo de falhas
causado por erros de integração é apresentado e a partir dele os operadores
de mutação para o teste de integração são definidos. A Mutação de Interface é
aplicada ponto-a-ponto, testando cada conexão existente. Dois grupos de
operadores de mutação são definidos: 1) operadores que são aplicados na
função chamada, e 2) operadores que são aplicados onde uma função é
chamada. Segundo Delamaro (1997), uma das vantagens da Mutação de
Interface sobre a Mutação nos Módulos é que ela tende, por princípio a reduzir
o número de mutantes a serem executados e analisados pois sua aplicação se
restringe aos pontos de conexão entre as unidades do programa. Vincenzi
(1998) realizou estudos empíricos com o objetivo de verificar qual o
relacionamento entre os critérios Análise de Mutantes e Mutação de Interface e
como utilizar tais critérios de forma complementar na atividade de teste. Com
base nos resultados obtidos, uma estratégia de teste incremental é proposta,
em que os operadores do conjunto essencial tanto no teste de unidade quanto
no teste de integração são utilizados de maneira que a maioria dos requisitos
de teste de ambos os critérios seja satisfeita a um baixo custo de aplicação. A
ferramenta Proteum/IM – que apóia o teste de mutação de interface foi
desenvolvida com as mesmas características da Proteum/C (Delamaro, 1997).
A seguir, será avaliada a adequação da atividade de teste do programa
identifier, realizada até este ponto, em relação ao critério Análise de Mutantes,
com o apoio da ferramenta Proteum/C; ou seja, será avaliada a adequação dos
conjuntos Todos-Arcos-adequado e Todos-Potenciais-Usos-adequado em
relação ao critério Análise de Mutantes. Na Figura 7(a) é apresentado o estado
da sessão de teste após a execução dos mutantes com o conjunto de casos de
teste T0 foram utilizados. Como o escore de mutação ainda não é satisfatório,
foram adicionados os casos de teste do conjunto T1 e T2 e o resultado é
apresentado na Figura 7(b). Observa-se que mesmo após a adição de todos os
casos de teste do conjunto Todos-Potenciais-Usos-adequado, 70 mutantes
ainda permaneceram vivos, desses, 63 eram equivalentes e 7 não foram
mortos pelos casos de teste utilizados até essa fase do teste. Isto significa que
qualquer um desses 7 mutantes poderiam ser considerados “corretos” em
relação à atividade de teste atual, uma vez que não existe um caso de teste
selecionado que seja capaz de distinguir entre o comportamento dos mutantes
e do programa original (Figura 7 (c)). Três novos casos de teste foram
adicionados ao conjunto procurando “matar” os 7 mutantes que ficaram vivos:
T3 = T2  {(zzz, Válido), (aA, Válido), (A1234,Válido)}. O resultado da execução
dos mutantes com esses novos casos de teste é ilustrado na Figura 7 (d).
Observa-se que dois mutantes ainda permaneceram vivos, ilustrados na Figura
8 (b e c).
(a) (b)

(c) (d)
Figura 7. Tela de Status da Ferramenta Proteum: (a) após a execução dos mutantes com T0,
(b) após a execução dos mutantes com T1 e T2, (c) após a determinação dos mutantes
equivalentes e (d) após a execução com T3.

Na Figura 8 são ilustrados 3 mutantes gerados pela ferramenta


Proteum/C para o programa identifier: Figura 8(a) – mutante equivalente,
Figura 8 (b) e (c) – mutantes error-revealing. Repare que ambos os mutantes
vivos, não equivalentes, são error-revealing e um deles é o programa correto:
Figura 8 (c). Um mutante é error-revealing se para qualquer caso de teste t tal
que P*(t)  M*(t) pudermos concluir que P*(t) não está de acordo com o
resultado esperado, ou seja, revela a presença de um erro.
Observe que os mutantes error-revealing (Figura 8 (b)(c)) foram gerados
pelos operadores de mutação ORRN e VTWD e que necessariamente o erro
presente na versão do programa identifier será revelado ao elaborar-se
qualquer caso de teste que seja capaz de distinguir o comportamento desses
mutantes e a versão do programa identifier em teste. Esses mutantes
“morrem”, por exemplo, com o caso de teste {(ABCDEF, Válido)}.
Para o programa identifier, utilizando-se todos os operadores de
mutação, foram gerados 357 mutantes. Aplicando-se somente os operadores
da Tabela 5 teriam sido gerados somente 108 mutantes, representando uma
economia de 69,74%. Os operadores de mutação ilustrados na Tabela 5
constituem um conjunto de operadores essenciais para a linguagem C
(Barbosa et al., 1998), ou seja, um conjunto de casos de teste que seja capaz
de distinguir os mutantes gerados por esses operadores, em geral, seria capaz
de distinguir os mutantes não equivalentes gerados pelos demais operadores
de mutação, determinando um escore de mutação bem próximo de 1. Observe-
se que os operadores de mutação ORRN e VTWD, que geraram os mutantes
error-revealing, estão entre os operadores essenciais, o que neste caso, não
comprometeria a eficácia da atividade de teste.

 
/* 08 */ if (valid_id * (length >= 1) && /* 08 */ if (valid_id && (length >= 1) &&
(length < 6) ) (PRED(length) < 6) )
/* 09 */ { /* 09 */ {
/* 09 */ printf ("Valido\n"); /* 09 */ printf ("Valido\n");
/* 09 */ } /* 09 */ }
/* 10 */ else /* 10 */ else
/* 10 */ { /* 10 */ {
/* 10 */ printf ("Invalido\n"); /* 10 */ printf ("Invalido\n");
/* 10 */ } /* 10 */ }
 
(a) (b)


/* 08 */ if (valid_id && (length >= 1) &&
(length <= 6) )
/* 09 */ {
/* 09 */ printf ("Valido\n");
/* 09 */ }
/* 10 */ else
/* 10 */ {
/* 10 */ printf ("Invalido\n");
/* 10 */ }

(c)

Figura 8. Três mutantes do programa identifier: (a) Mutante equivalente,


(b) Mutante não equivalente (error-revealing), (c) Mutante não equivalente (error-revealing).

3. Estudos Empíricos Relacionados à Atividade de Teste de Software


Devido à diversidade de critérios de teste existentes, decidir qual deles
deva ser utilizado ou como utilizá-los de maneira complementar para obter
melhores resultados com baixo custo não é tarefa fácil. A realização de
estudos empíricos e teóricos permite comparar os diversos critérios de teste
existentes, procurando fornecer uma estratégia viável para realização dos
testes.
Do ponto de vista de estudos teóricos, os critérios de teste são avaliados
segundo dois aspectos: a relação de inclusão, e a complexidade dos critérios.
A relação de inclusão estabelece uma ordem parcial entre os critérios
caracterizando uma hierarquia entre eles. Um critério C1 inclui um critério C2 se
para qualquer programa P e qualquer conjunto de casos de teste T1 C1-
adequado, T1 for também C2-adequado e para um conjunto de casos de teste
T2 C2-adequado, T2 não é C1-adequado. A complexidade de um critério C1 é
definida como o número máximo de casos de testes ou de elementos (ou
requisitos) requeridos no pior caso (Rapps e Weyuker, 1985; Weyuker, 1984,
Ntafos, 1988).
Do ponto de vista de estudos empíricos, os seguintes fatores são utilizados
para avaliar os critérios de teste: custo, eficácia e dificuldade de satisfação
(strength). Entende-se por custo o esforço necessário para que o critério seja
usado, o qual pode ser medido pelo número de casos de teste necessários
para satisfazer o critério, ou por outras métricas dependentes do critério, tais
como: o tempo necessário para executar todos os mutantes gerados, ou o
tempo gasto para identificar mutantes equivalentes, caminhos e associações
não executáveis, construir manualmente os casos de teste e aprender a utilizar
as ferramentas de teste. Eficácia refere-se à capacidade que um critério possui
em detectar um maior número de erros em relação a outro. Dificuldade de
satisfação refere-se à probabilidade de satisfazer-se um critério tendo satisfeito
outro critério. Seu objetivo é verificar o quanto consegue-se satisfazer um
critério C1 tendo satisfeito um critério C2 (C1 inclui C2, ou C1 e C2 são
incomparáveis, ou seja, não possuem uma ordem de inclusão) (Wong, 1993).
Os critérios baseados em Fluxo de Dados têm complexidade exponencial
(Maldonado, 1991), o que motiva a condução de estudos empíricos para
determinar o custo de aplicação desses critérios do ponto de vista prático.
Alguns autores têm abordado do ponto de vista teórico a questão de eficácia
dos critérios, definindo outras relações de inclusão que captam a capacidade
de revelar erros dos critérios (Frankl e Weyuker, 1993) ou demonstrando que a
relação de inclusão proposta por Rapps e Weyuker (1985) tem uma estreita
relação com a capacidade de revelar erros (Zhu, 1996).
O critério Análise de Mutantes tem sido bastante investigado
empiricamente, em função de sua eficácia em revelar erros (Mathur e Wong,
1993; 1994; Wong et al., 1994a; 1994b; Offut et al., 1996a; 1996b; Souza,
1996, Delamaro, 1997; Barbosa et al., 1998; Vincenzi, 1998). Esses estudos,
além de indicarem como o critério Análise de Mutantes se relaciona com
outros critérios de teste, buscam novas estratégias a fim de reduzir os custos
associados ao critério.
Mathur e Wong (1993) comparam dois critérios de mutação alternativos: a
Mutação Aleatória (no caso, foi selecionado 10% de cada operador de
mutação) e a Mutação Restrita. Esse experimento foi conduzido para comparar
qual dessas estratégias apresenta melhor relação custo/eficácia. Segundo os
autores, mutação restrita e mutação aleatória mostraram-se igualmente
eficazes, obtendo-se significativa redução no número de mutantes a serem
analisados sem sensível perda na eficácia em revelar erros.
Em outro trabalho realizado por Mathur e Wong (1994) foi investigada a
adequação de conjuntos de casos de teste em relação aos critérios Análise de
Mutantes e Todos-Usos. O objetivo do experimento é verificar a dificuldade de
satisfação dos dois critérios, bem como seus custos, uma vez que esses
critérios são incomparáveis do ponto de vista teórico. Neste estudo, os
conjuntos de casos de teste Análise de Mutantes-adequados também se
mostraram Todos-Usos-adequados. No entanto, os conjuntos de casos de
teste Todos-Usos-adequados não se mostraram, em muitos casos, Análise de
Mutantes-adequados. Esses resultados indicam que é mais difícil de satisfazer
o critério Análise de Mutantes do que o critério Todos-Usos, podendo-se dizer
que, na prática, o critério Análise de Mutantes inclui o critério Todos-Usos
(Mathur e Wong, 1993).
Wong et al. (1994a) utilizaram a Mutação Aleatória (10%) e a Mutação
Restrita para comparar o critério Análise de Mutantes com o critério Todos-
Usos. O objetivo foi verificar o custo, eficácia e dificuldade de satisfação
desses critérios. Os autores observaram que o critério Todos-Usos, Mutação
Aleatória (10%) e Mutação Restrita representam, nesta ordem, o decréscimo
do custo necessário para a aplicação do critério (número de casos de teste
requeridos), ou seja, o critério Todos-Usos requer mais casos de teste para ser
satisfeito do que a Mutação Restrita. Em relação à eficácia para detectar erros
a ordem (do mais eficaz para o menos) é Mutação Restrita, Todos-Usos e
Mutação Aleatória. Observou-se, com isso, que examinar somente uma
pequena porcentagem de mutantes pode ser uma abordagem útil na avaliação
e construção de conjuntos de casos de teste na prática. Quando o testador
possui pouco tempo para efetuar os testes (devido ao prazo de entrega do
produto) pode usar o critério Análise de Mutantes para testar partes críticas do
software utilizando alternativas mais econômicas, tais como Mutação Restrita,
sem comprometer significativamente a qualidade da atividade de teste.
Offut et al. (1996a) também realizaram um experimento comparando o
critério Análise de Mutantes com o critério Todos-Usos. Os resultados foram
semelhantes àqueles obtidos por Wong et al. (1994a), ou seja, o critério
Análise de Mutantes revelou um maior número de erros do que o critério
Todos-Usos e mais casos de testes foram necessários para satisfazer o
critério Análise de Mutantes. Além disso, os conjuntos de casos de teste
Análise de Mutantes-adequados foram adequados ao critério Todos-Usos;
entretanto, o inverso não foi verdadeiro, gerando um resultado semelhante ao
obtido por Mathur e Wong (1994).
Nos trabalhos de Wong et al. (1994b) e Souza (1996) foram comparadas 6
diferentes classes de mutação restrita quanto à eficácia em revelar erros.
Desse experimento pode-se observar quais classes de mutação eram mais
econômicas (baixo custo de aplicação) e eficazes. Com isso, foi possível o
estabelecimento de uma ordem incremental para o emprego dessas classes
de mutação, com base no custo e eficácia de cada uma. Desse modo, os
conjuntos de casos de testes podem ser construídos, inicialmente, de forma a
serem adequados à classe com menor relação custo/eficácia. Na seqüência,
quando as restrições de custo permitirem, esse conjunto pode ser melhorado
de modo a satisfazer as classes de mutação com maior relação custo/eficácia.
Souza (1996) realizou um experimento para avaliar a dificuldade de
satisfação e o custo do critério Análise de Mutantes empregando, para efeito
comparativo, os critérios Potenciais-Usos (Maldonado, 1991), os quais incluem
o critério Todos-Usos. Os resultados indicaram que o custo de aplicação do
critério Análise de Mutantes, estimado pelo número de casos de teste
necessário para satisfazer o critério, apresentou-se maior do que o custo dos
critérios Potenciais-Usos. Em relação à dificuldade de satisfação observou-se
que, de uma maneira geral, os critérios Análise de Mutantes e Todos-
Potenciais-Usos são incomparáveis mesmo do ponto de vista empírico. Os
critérios Todos-Potencias-Usos/Du e Todos-Potenciais-Du-Caminhos
apresentaram maior dificuldade de satisfação que o critério Todos-Potenciais-
Usos em relação ao critério Análise de Mutantes, o que motiva a investigar o
aspecto complementar desses critérios quanto à eficácia.
Offut et al. (1996b) realizaram um experimento para determinar um
conjunto essencial de operadores de mutação para o teste de programas em
Fortran. Os resultados obtidos indicam que, dos 22 operadores de mutação
definidos, apenas cinco eram suficientes para aplicar eficientemente o teste de
mutação. Baseado nesse estudo, Barbosa et al. (1998) conduziu uma série de
experimentos buscando determinar um conjunto essencial de operadores de
mutação para a linguagem C. Como resultado, dos 71 operadores de mutação,
8 mostraram-se suficientes para garantir um escore de mutação bem próximo
de 1. Esses estudos contribuem fortemente para a viabilização da aplicação do
critério Análise de Mutantes em ambientes comerciais de desenvolvimento de
software.

4. Aplicação do Critério Análise de Mutantes no Contexto de


Especificações Formais
Apesar de todo rigor das técnicas formais, não se garante que a
especificação formal esteja livre de erros e de acordo com os requisitos do
usuário. Observa-se também que quanto mais cedo os erros são detectados no
processo de desenvolvimento, menos difícil torna-se a tarefa de removê-los.
Algumas iniciativas de definição de critérios de teste para a validação de
especificações formais são identificadas. Técnicas para seleção de seqüências
de teste para especificações baseadas em Máquinas de Estados Finitos e
Máquinas de Estados Finitos Estendidas são propostas, principalmente para o
teste de conformidade de protocolos de comunicação. Os critérios de teste
propostos por Ural (1987), Probert e Guo (1991), Fabbri (1996), Souza et al.
(2000a) e Souza et al. (2000b) procuram utilizar o conhecimento adquirido no
teste de programas, mapeando critérios de teste empregados no nível de
programa para o nível de especificação. Essa abordagem tem se mostrado
promissora e pode complementar as técnicas de simulação e análise de
alcançabilidade, normalmente empregadas para a validação de especificações
baseadas em Máquinas de Estados, Redes de Petri, Estelle, entre outras.
Nesta seção, são apresentados os resultados da definição do critério
Análise de Mutantes no contexto de especificações formais, em particular,
especificações baseadas em Redes de Petri, Máquinas de Estados Finitos,
Statecharts e Estelle. Essas técnicas formais possuem apoio gráfico e por isso
são bastante empregadas, pois facilitam a visualização e a descrição do
sistema.
As Redes de Petri foram criadas para modelar sistemas que apresentam
concorrência, paralelismo, comunicação síncrona e assíncrona (Peterson,
1977). Sua execução gera uma seqüência de eventos, obtidos a partir do
disparo das transições, podendo ocorrer não determinismo no disparo das
transições. Outro fator importante é que as Redes de Petri podem ser utilizadas
para análise de alcançabilidade do sistema e para detecção de impasses
(deadlock), sendo que a árvore de alcançabilidade é uma das principais
técnicas para análise de Redes de Petri (Fabbri, 1996).
A técnica Máquinas de Estados Finitos (MEFs) é muito utilizada para
especificação do aspecto comportamental de sistemas reativos,
particularmente, na área de protocolos de comunicação (Gill, 1962). Sistemas
Reativos são sistemas baseados em eventos que reagem a estímulos internos
ou do ambiente, interagindo com o mesmo de forma a produzir resultados
corretos dentro de intervalos de tempo previamente especificados (Harel,
1987). O sistema modelado em uma MEF é descrito por uma máquina,
composto de estados e transições, estando em somente um de seus estados
num dado momento. A partir de uma entrada, a máquina gera uma saída e
muda de estado. A máquina pode ser representada por um diagrama de
transição de estados ou por uma tabela de transição, esta última seguindo o
modelo de Mealy – interseção da linha com a coluna especifica o próximo
estado e a saída gerada; ou o modelo de Moore – interseção da linha com a
coluna contém apenas o próximo estado e existe uma coluna separada para
indicar a saída associada com cada estado (Davis, 1988). Com o objetivo de
aumentar o poder de representação das MEFs, extensões dessa técnica são
propostas, podendo-se citar: Máquinas de Estados Finitos Estendidas (MEFEs)
– que representa o uso e definição de variáveis associadas às transições; e
Máquinas de Estados Finitos com Comunicação (MEFCs) – que representam
os aspectos de comunicação entre MEFs através de canais de comunicação
com filas associadas.
Statecharts é uma técnica de especificação formal proposta por Harel
(1987) para descrever o aspecto comportamental de sistemas reativos, através
de uma hierarquia de MEFEs. As características dessa técnica a tornam mais
atrativa que outras técnicas formais, como MEF, MEFE e Redes de Petri. Tais
características são: apresentação do modelo por meio de uma hierarquia de
MEFEs, que torna o modelo mais claro sendo possível visualizar os aspectos
de concorrência; ortogonalidade, que possibilita descrever o paralelismo entre
os componentes (MEFEs) do modelo especificado; broadcasting ou reação em
cadeia, que permite descrever a sincronização entre os componentes
ortogonais do modelo; e história, que possibilita lembrar estados que foram
visitados previamente. Essa técnica permite descrever a dinâmica dos sistemas
reativos, de forma clara e realística, e ao mesmo tempo formal e rigorosa, de
maneira a possibilitar também uma simulação detalhada do sistema (Harel,
1987).
Estelle – Extended State Transition Language é uma técnica de descrição
formal desenvolvida pela ISO (International Standards Organization), para
especificação de sistemas distribuídos, protocolos de comunicação e serviços.
O padrão ISO 9074 (1987) descreve a semântica e sintaxe formal dessa
técnica. Um sistema especificado em Estelle é estruturado em uma hierarquia
de módulos que se comunicam através de troca de mensagens. O
comportamento de cada módulo é descrito através de uma MEFE e utiliza, com
algumas restrições, a linguagem Pascal, o que torna Estelle uma técnica de
fácil aprendizagem. As mensagens recebidas pelos módulos são armazenadas
em filas de tamanho infinito (tipo FIFO – first in first out) e são processadas de
acordo com as condições, prioridades e atrasos associados às transições da
MEFE. Estelle permite a descrição de paralelismo síncrono e assíncrono entre
as MEFEs do sistema, permitindo evolução dinâmica da configuração do
sistema. A especificação pode ser descrita em diferentes níveis de abstração,
vindo da forma mais abstrata até aproximar-se da implementação (Budkowski e
Dembinski, 1987).
Para a definição do critério Análise de Mutantes no contexto de
especificações é feito um mapeamento da hipótese do programador
competente, definindo a hipótese do especificador ou projetista competente: se
a especificação em teste foi construída por um projetista ou especificador
competente ou está correta ou está muito próxima do correto. Fabbri (1996) faz
uma definição formal: dada uma especificação S, gera-se um conjunto de
mutantes de S, (S), com base em um conjunto de operadores de mutação e
diz-se que um conjunto de teste T é adequado para S em relação a (S) se
para cada especificação Z  (S), ou Z é equivalente a S, ou Z difere de S em
pelo menos um ponto de T.
Como apresentado anteriormente, um aspecto fundamental do critério
Análise de Mutantes é a definição do conjunto de operadores de mutação. É
necessário que esse conjunto consiga representar os tipos de erros mais
comuns que podem ser cometidos, no caso, pela técnica de especificação.
Desse modo, Fabbri (1996) define o critério Análise de Mutantes para
validação de especificações baseadas em Redes de Petri, em Máquinas de
Estado Finito e em Statecharts, considerando, para definição do conjunto de
operadores de mutação de cada técnica, a classificação de erros sugerida por
Chow (1978). Chow classifica os erros de Máquinas de Estados Finitos em 3
tipos: erros de transferência (erros de transição), erros de operação (erros de
saída) e erros de estados extras ou ausentes (erros de estados).
Para a técnica Statecharts, o conjunto de operadores de mutação é
formado pelos operadores definidos para MEF; pelos operadores de mutação
para MEFEs, os quais foram definidos com base nos operadores de mutação
para a linguagem C (Agrawal et al., 1989) e que são relativos ao uso de
variáveis e condições e por alguns operadores de mutação relativos aos
aspectos intrínsecos de Statecharts, como história, broadcasting e paralelismo
(Tabela 6). Três estratégias de abstração para aplicação do critério Análise de
Mutantes para Statecharts são propostas: Básica, Baseada em Ortogonalidade
e Baseada em História. Essas estratégias permitem selecionar os
componentes do Statecharts em diferentes níveis de hierarquia, possibilitando
que a condução do teste seja realizada através das abordagens top-down ou
bottom-up. A partir dos componentes, o teste da especificação pode partir do
nível mais alto de abstração, que corresponde ao próprio Statecharts, ou partir
do nível mais baixo de abstração, composto pelos componentes cujos estados
são átomos ou básicos (Fabbri, 1996).
Tabela 6. Operadores de Mutação para Statecharts.
Operadores de Mutação para MEF
1. alteração do estado default 6. destino trocado
2. arco faltando 7. ação faltando
3. evento faltando 8. ação trocada
4. evento extra 9. estado faltando
5. evento trocado
Operadores de Mutação para MEFE
1. exclusão de expressão 7. negação lógica
2. negação de expressão booleana 8. troca variável por variável
3. troca associativa de termos 9. troca variável por constante
4. troca operador aritmético por 10. troca constante por
operador aritmético constante requeridas
5. troca operador relacional por 11. troca constante por variável escalar
operador relacional
6. troca operador lógico por operador lógico
Operadores de Mutação para Statecharts
1. desassocia história da transição 9. troca estado da condição in(s)
2. troca transição associada ao símbolo história 10. exclui condição not-yet(e)
3. exclui história do estado 11. troca evento da condição not-yet(e)
4. troca h por h* 12. exclui evento exit(s)
5. troca h* por h 13. troca estado do evento exit(s)
6. associa h ao estado 14. exclui evento entered(s)
7. associa h* ao estado 15. troca estado do evento entered(s)
8. exclui condição in(s)

Para a definição do critério Análise de Mutantes para Estelle, Souza et al.


(2000a) utilizaram como base os operadores de mutação definidos por Fabbri
(1996) para MEF e MEFE; o trabalho de Probert e Guo (1991), que define
operadores de mutação para o aspecto comportamental (MEFE) de Estelle; e o
critério Mutação de Interface definido por Delamaro (1997). Os operadores de
mutação para Estelle são divididos em 3 classes de acordo com os aspectos
da especificação que são abordados: mutação nos módulos, que consiste,
basicamente, na aplicação de operadores de mutação para as MEFEs dos
módulos; mutação de interface, que procura validar os aspectos de
comunicação entre os módulos do sistema; e mutação na estrutura, que aplica
os operadores de mutação nos aspectos arquiteturais da especificação, como
por exemplo, nas conexões entre os módulos (Tabela 7). Uma estratégia
incremental para aplicação dos operadores de mutação também é
apresentada.
Critérios de mutação alternativo também foram definidos para as técnicas
de especificação, visando a reduzir o número de mutantes gerados e,
consequentemente, o custo do critério Análise de Mutantes. São utilizados os
critérios mutação aleatória (seleção de 10% dos mutantes de cada operador) e
mutação restrita, visto que esses critérios apresentam bons resultados para o
nível de programas, conforme apresentado na Seção 3.

Tabela 7. Operadores de Mutação para Estelle.


Operadores de Mutação nos Módulos
1. Substituição do estado inicial 11. Substituição de atribuição booleana
2. Substituição do estado origem 12. Substituição de variável por variável
3. Substituição do estado destino 13. Substituição de variável por constante
4. Remoção de estados da MEFE 14. Incremento/ decremento de variáveis e constantes
5. Remoção de transições da MEFE 15. Inclusão de operadores unários nas variáveis
6. Remoção da condição das transições 16. Substituição da ação das transições
7. Substituição do evento de entrada 17. Cobertura de código (bloco begin-end)
8. Remoção do evento de entrada 18. Remoção da cláusula priority das transições
9. Negação da condição das transições 19. Remoção da cláusula delay das transições
10. Troca de operadores matemáticos 20. Substituição da política de fila
da condição
Operadores de Mutação na Interface dos Módulos
Grupo I: Ponto de chamada Grupo II: Módulo chamado
1. Substituição dos parâmetros 7. Substituição de variáveis de interface
2. Incremento e decremento dos parâmetros 8. Substituição de variáveis de não interface
3. Troca na ordem dos parâmetros 9. Incremento e decremento de variáveis
4. Inclusão de operadores unários 10. Inclusão de operadores unários nas variáveis
nos parâmetros
5. Substituição do comando output 11. Substituição de atribuição booleana
6. Remoção do comando output
Operadores de Mutação na Estrutura
1. Remoção das conexões 9. Substituição de release por disconnect
2. Inserção de desconexão 10. Substituição de terminate por detach
3. Remoção de desconexão 11. Substituição de terminate por disconnect
4. Remoção do comando release 12. Paralelismo síncrono por execução seqüencial
5. Remoção do comando terminate 13. Paralelismo síncrono por paralelismo assíncrono
6. Substituição de release por terminate 14. Execução seqüencial por paralelismo síncrono
7. Substituição de terminate por release 15. Execução seqüencial por paralelismo assíncrono
8. Substituição de release por detach

Mecanismos e ferramentas para automatizar a aplicação do critério Análise


de Mutantes no contexto dessas especificações formais também têm sido
explorados. Utilizando a ferramenta Proteum/C como base, foram definidas as
ferramentas: Proteum-RS/FSM – que apóia o teste de especificações baseadas
em MEF (Fabbri, 1996); Proteum-RS/ST – que apóia o teste de especificações
baseadas em Statecharts (Sugeta, 1999) e Proteum-RS/PN – que apóia o teste
de especificações baseadas em Redes de Petri (Simão, 2000). A
disponibilidade dessas ferramentas permite a realização de experimentos para
avaliar o custo e também eficácia do critério Análise de Mutantes no contexto
de especificações. Esses aspectos estão sendo investigados.
5. Conclusões
Neste texto foram apresentados os principais conceitos relacionados à
atividade de teste de software e critérios de teste pertencentes às técnicas de
teste funcional, estrutural e baseada em erros. Através de um exemplo foi
possível demonstrar o aspecto complementar dos critérios de teste e também
como utilizar incrementalmente esses critérios para identificar erros no
programa. Destacou-se o critério Análise de Mutantes e os critérios de Fluxo de
Dados, considerados os mais promissores a curto e médio prazo. Foram
apresentadas também as ferramentas Proteum/C e Poke-Tool, que apoiam a
aplicação dos critérios Análise de Mutantes e Potenciais-Usos,
respectivamente. Ressaltou-se a relevância de se conduzir estudos empíricos
para a avaliação dos critérios de teste. O desenvolvimento de estudos
empíricos contribui para o estabelecimento de estratégias de teste incrementais
para a condução da atividade de teste de software. Nessas estratégias
poderiam ser aplicados, inicialmente, critérios “mais fracos” e talvez menos
eficazes para revelar erros e, em função da disponibilidade de recursos e
tempo, incrementalmente, poderiam ser utilizados critérios mais “fortes” e
eventualmente mais eficazes, porém, em geral, mais caros.
Por algum tempo, o teste de software foi considerado uma atividade a ser
realizada somente na implementação do software e com isso, a maioria das
pesquisas nessa área concentrou-se em estabelecer técnicas, critérios e
ferramentas para validação de programas. Isso é demonstrado pelo número de
critérios de teste existentes no nível de programas. Recentemente,
pesquisadores têm direcionado a atenção também para a validação das
demais fases do ciclo de desenvolvimento de software. Se um erro for inserido
nas fases iniciais do desenvolvimento, por exemplo, na fase de especificação,
não é preciso esperar a implementação do programa para identificar e corrigir
esse erro. Adiar a atividade de teste somente contribui para aumentar a
dificuldade em se detectar o erro, o qual pode se propagar por diversas partes
do programa, aumentando também o custo para a realização dos testes.
Nesse sentido, a utilização de critérios de teste, em particular o critério
Análise de Mutantes, para validação de especificações formais foi apresentada.
Discutiu-se a definição desse critério para a aplicação no contexto de
especificações baseadas em Redes de Petri, Máquinas de Estado Finito,
Statecharts e Estelle. Ferramentas e mecanismos para apoiar a aplicação
desse critério no contexto de especificações têm sido desenvolvidos. É
importante observar que o conjunto de casos de teste obtido para testar e
validar a especificação pode ser utilizado durante o teste de conformidade da
implementação em teste. A relação existente entre esses níveis de abstração:
especificação e implementação estão sendo investigados.
De uma maneira geral, pode-se dizer que a atividade de teste tem forte
relação com a atividade de Qualidade do Software, sendo que testes bem
conduzidos procuram aumentar a confiabilidade do software. Além disso, o
conjunto de informações obtidos na atividade de teste é significativo para as
atividades de depuração, estimativa de confiabilidade e de manutenção.

Referências
AGRAWAL, H.; DEMILLO, R.; HATHAWAY, R.; HSU, Wm., HSU, W.; KRAUSER, E.;
MARTIN, R.J.; MATHUR, A.; SPAFFORD, E. Design of Mutant Operators for the C
Programming Language. Technical Report SERC-TR-41-P, Software Engineering
Research Center, Purdue University, Março, 1989.
BARBOSA, E. F.; VINCENZI, A. M. R; MALDONADO, J.C. Uma Contribuição para a
Determinação de um Conjunto de Operadores de Mutação no Teste de
Programas C. in Proc. XII SBES - Simpósio Brasileiro de Engenharia de Software,
Maringá, PR, Brasil, Outubro, 1998.
BUDD, T.A. Mutation Analysis: Ideas, Examples, Problems and Prospects. Computer
Program Testing, North-Holand Publishing Company, 1981.
BUDKOWSKI, S.; DEMBINSKI, P. An Introduction to Estelle: a specification language
for distributed systems. Computer Network and ISDN Systems, Vol.14(01), pp.3-
23, 1987.
CHAIM, M.J. POKE-TOOL - Uma Ferramenta para Suporte ao Teste Estrutural de
Programas Baseados em Análise de Fluxo de Dados. Dissertação de Mestrado,
DCA/FEE/UNICAMP - Campinas, SP, Brasil, Abril, 1991.
CHOI, B.; MATHUR, A.P. High Performance Mutation Testing. Journal Systems
Software, Vol. 20(02), pp. 135-152, Fevereiro, 1993.
CHOW, T.S. Testing Software Design Modeled by Finite-State Machines. IEEE
Transaction on Software Engineering, SE(4(3)), pp. 178-187, 1978.
DAVIS, A.M. A Comparison of Techniques for the Specification of External System
Behavior. Communications of the ACM, Vol. 31(09), Setembro, 1988.
DELAMARO, M.E. Proteum - Um Ambiente de Teste Baseado na Análise de Mutantes.
Dissertação de Mestrado, ICMSC/USP, São Carlos, SP, Brasil, Outubro, 1993.
DELAMARO, M.E. Mutação de Interface: Um Critério de Adequação Inter-Procedural
para o Teste de Integração. Tese de Doutorado, IFSC/USP, São Carlos, SP,
1997.
DEMILLO, R.A.; LIPTON, R.J.; SAYWARD, F.G. Hints on Test Data Selection: Help for
the Practicing Programmer. IEEE Computer, Abril, 1978.
DEMILLO, R.A. Mutation Analysis as a Tool for Software Quality Assurance. in Proc. of
COMPSAC80, Chicago - IL, Outubro, 1980.
DEMILLO, R.A. Software Testing and Evaluation. The Benjamin/Cummings Publishing
Company, Inc, 1987.
DEMILLO,R.A., et al. An Extended Overview of the Mothra Testing Environment. in
Proc, of the Second Workshop on Software Testing, Verification and Analysis,
Banff - Canadá, 1988.
FABBRI, S.C.P.F. A Análise de Mutantes no Contexto de Sistemas Reativos: Uma
Contribuição para o Estabelecimento de Estratégias de Teste e Validação. Tese
de Doutorado, IFSC/USP, São Carlos, SP, Outubro, 1996.
FONSECA, R.P. Suporte ao Teste Estrutural de Programas Fortran no Ambiente
POKE-TOOL. Dissertação de Mestrado, DCA/FEE/UNICAMP - Campinas, SP,
Brasil, Janeiro, 1993.
FRANKL, P.G.; WEYUKER, E.J. A Formal Analysis of the Fault-detecting Ability of
Testing Methods. IEEE Transactions on Software Engineering, Vol. 19(03), pp.
202-213, Março, 1993.
GILL, A. Introduction to the Theory of Finite-State Machine. New Jork, McGraw-Hill,
1962.
HAREL, D.; PINNEL, A.; SCHMIDT, J.P.; SHERMAN, R. On the Formal Semantics of
Statecharts. in Proc. 2nd IEEE Symposium on Logic in Computer Science, Ithaca,
New York, 1987.
HARROLD, M.J.; SOFFA, M.L. Selecting and Using Data for Integration Testing. IEEE
Software, vol. 8(02), pp. 58-65, Março, 1991.
HOWDEN, W.E. Functional Program Testing and Analysis. McGraw-Hill, USA, 1987.
IEEE. IEEE Standard Glossary of Software Engineering Terminology. Padrão 610.12,
1990.
ISO/TC97/SC21/WG1/DIS9074, Estelle – A formal description technique based on an
extended state transition model. 1987.
KRAUSER, E.W.; MATHUR, A.P.; REGO, V. High Performance Testing on SIMD
Machines. in Proc. of the Second Workshop on Software Testing, Verification and
Analysis, Banff - Canadá, 1988.
LEITÃO, P.S.J. Suporte ao Teste Estrutural de Programas Cobol no Ambiente POKE-
TOOL. Dissertação de Mestrado, DCA/FEE/UNICAMP - Campinas, SP, Brasil,
Agosto, 1992.
LINNENKUGEL, U.; MÜLLERBURG, M. Test Data Selection Criteria for Software
Integration Testing. First International Conference on Systems Integration,
Morristown, Nova Jersey, pp.709-717, Abril, 1990.
MALDONADO, J.C.; CHAIM, M.L.; JINO, M. Arquitetura de uma Ferramenta de Teste
de Apoio aos Critérios Potenciais Usos. in Proc. XXII Congresso Nacional de
Informática, São Paulo, SP, Brasil, Setembro, 1989.
MALDONADO, J.C. Critérios Potenciais Usos: Uma Contribuição ao Teste Estrutural
de Software. Tese de Doutorado, DCA/FEE/UNICAMP, Campinas, SP, Brasil,
Julho,1991.
MALDONADO, J.C.; VINCENZI, A. M.; BARBOSA. E.F.; SOUZA, S.R.S.; DELAMARO,
M.E. Aspectos Teóricos e Empíricos de Teste de Cobertura de Software. VI
Escola de Informática da SBC- Regional Sul, Maio, 1998.
MARSHALL, A.C. et al. Static Dataflow-aided Weak Mutation Analysis (SDAWM).
Information and Software Technology, Vol. 32(01), Janeiro/Fevereiro, 1990.
MATHUR, A.P.; KRAUSER, E.W. Modeling Mutation on Vector Processor. in Proc. of
the Second Workshop on Software Testing, Verification and Analysis, Banff,
Canadá, 1988.
MATHUR, A.P.; WONG, W.E. Evaluation of The Cost Alternate Mutation Strategies. in
Proc. VII SBES - Simpósio Brasileiro de Engenharia de Software, Rio de Janeiro,
RJ, Outubro, 1993.
MATHUR, A.P.; WONG, W.E. An Empirical Comparison of Data Flow and Mutation-
Based Test Adequacy Criteria. Software Testing, Verification and Reliability, Vol.
4, 1994.
MYERS, G.J. The Art of Software Testing. John Wiley & Sons, New York, 1979.
NTAFOS, S.C. A Comparison of Some Structural Testing Strategies. IEEE
Transactions on Software Engineering, Vol.14(06), pp. 868-873, Junho, 1988.
OFFUTT, A.J. Investigations of the Software Testing Coupling Effect. ACM
Transactions on Software Engineering Methodology, Vol. 1(01), pp. 3-18, Janeiro,
1992.
OFFUTT, A.J. ; ROTHERMEL, A.J. An Experimental Evaluation of Selective Mutation.
15th International Conference on Software Engineering, Baltimore, Maryland, Maio,
1993.
OFFUTT(a), A.J. et al. An Experimental Evaluation of Data Flow and Mutation Testing.
Software Practice and Experience, Vol. 26(02), pp. 165-176, Fevereiro, 1996.
OFFUTT(b), A.J. et al. An Experimental Determination of Sufficient Mutant Operators.
ACM Transactions on Software Engineering Methodology, Vol. 5(02), pp. 99-118,
Abril, 1996.
OFFUTT, A.J.; PAN, J. Detecting Equivalent Mutants and the Feasible Path Problem.
in Proc. COMPASS’96 – Conference on Computer Assurance, IEEE Computer
Society Press, Gaithersburg, MD, pp.224-236, Junho, 1996.
PETERSON, J.L. Petri Nets. Computing Surveys, Vol. 9(03), Setembro, 1977.
PRESSMAN, R. S. Software Engineering - A practitioner's approach. 4ª Edição,
McGraw-Hill, 1997.
PROBERT, R.L.; GUO, F. Mutation Testing of Protocols: principles and preliminary
experimental results. in Proc. III IFIP TC6, Third International Workshop, pp. 57-
76, 1991.
RAPPS, S.; WEYUKER, E.J. Selecting Software Test Data Using Data Flow
Information. IEEE Transaction on Software Engineering, Vol.11(04), pp. 367-375,
Abril, 1985.
SIMÃO, A. S. Proteum-RS/PN: Uma Ferramenta para a Validação de Redes de Petri
Baseada na Análise de Mutantes, Dissertação de Mestrado, ICMSC/USP,
Fevereiro, 2000.
SOUZA, S.R.S. Avaliação do Custo e Eficácia do Critério Análise de Mutantes na
Atividade de Teste de Software. Dissertação de Mestrado, ICMSC/USP, São
Carlos, SP, Junho, 1996.
SOUZA(a), S.R.S.; MALDONADO, J.C.; FABBRI, S.C.P.F.; LOPES De SOUZA, W.
Mutation Testing Applied to Estelle Specifications. Software Quality Journal, Vol.
8(04), to appear, artigo selecionado do 33rd Hawaii International Conference on
System Sciences, Mini-track: Distributed Systems Testing, Maui, Hawaii, Janeiro
4-7, 2000.
SOUZA(b), S.R.S.; MALDONADO, J.C.; FABBRI, S.C.P.F.; MASIERO, PC.
Statecharts Specifications: A Family of Coverage Testing Criteria, in Proc.
CLEI200 – Conferência Latino Americana de Informática, Cidade de México,
México, Setembro 18-22, 2000.
SUGETA, T. Proteum-RS/ST: Uma Ferramenta para Apoiar a Validação de
Especificações Statecharts Baseada na Análise de Mutantes, Dissertação de
Mestrado, ICMSC/USP, Dezembro, 1999.
URAL, H. Test Sequence Selection Based on Static Data Flow Analysis. Computer
Communications, Vol.10(05), pp. 234-242, Outubro, 1987
URAL, H.; YANG, B. A. Structural Test Selection Criterion. Information Processing
Letters, vol.28, pp.157-163, 1988.
VERGÍLIO, S.R.; MALDONADO, J.C.; JINO, M. Uma Estratégia para Geração de
Dados de Teste. in Proc. VII SBES – Simpósio Brasileiro de Engenharia de
Software, Rio de Janeiro, RJ, pp. 307-319, 1993.
VINCENZI, A.M.R. Subsídios para o Estabelecimento de Estratégias de Teste
Baseadas na Técnica de Mutação. Dissertação de Mestrado, ICMC/USP, São
Carlos, SP, Novembro, 1998.
VILELA, P.R.S.; MALDONADO, J.C; JINO, M. Program Graph Visualization, Software
Pratice and Experience¸ Vol. 27(11), pp.1245-1262, Novembro, 1997.
VILELA, P.R.S. Critérios Potenciais Usos de Integração: Definição e Análise, Tese de
Doutorado, DCA/FEE/UNICAMP, Campinas, SP, Brasil, Abril,1998.
WEYUKER, E.J. The Complexity of Data Flow Criteria for Test Data Selection.
Information Processing Letters, Vol.19(02), pp. 103-109, Agosto, 1984.
WONG, W.E. On Mutation and Data Flow. Tese de Doutorado, Software Engineering
Research Center - Purdue University, West Lafayette, Indiana, Dezembro,1993.
WONG(a), W.E. et al. Mutation versus All-Uses: An Empirical Evaluation of Cost,
Strength, and Effectiveness. Software Quality and Productivity - Theory, Practice,
Education and Training, Hong Kong, Dezembro, 1994.
WONG(b), W.E. et al. Constrained Mutation in C programs. VIII SBES - Simpósio
Brasileiro de Engenharia de Software, Curitiba, PR, pp. 439-452, Outubro, 1994.
ZHU, H., A Formal Analysis of the Subsume Relation Between Software Test
Adequacy Criteria. IEEE Transaction Software Engineering, Vol.22(04), pp. 248-
255, Abril, 1996.
ZHU, H.; HALL, P.A.V.; MAY, J.H.R. Software Unit Test Coverage and Adequacy.
ACM Computing Surveys, vol.29(04), Dezembro, 1997.

View publication stats

Você também pode gostar