Gerência - Qualidade e Testes

Técnicas de VV&T - Validação, Verificação e Teste

Este artigo tem como objetivo apresentar os principais conceitos relacionados à atividade de VV&T – Validação, Verificação e Teste – enfatizando as técnicas de Leitura de Código, Teste Funcional, Teste Estrutural e Baseada em Erros.

por Katia Romero Felizardo



1 INTRODUÇÃO

Engenheiros de software buscam qualidade (e desenvolvem atividades de garantia de qualidade e de controle de qualidade) aplicando métodos e medidas técnicas sólidas, conduzindo revisões técnicas formais e efetuando teste de software bem planejado [Pressman, 2002].

A definição de VV&T - Validação, Verificação e Teste - abrange muitas das atividades relacionadas com a Garantia da Qualidade de Software, ou GQS. Validação é uma atividade que tem como objetivo assegurar que o produto final corresponda aos requisitos do software (Estamos construindo o produto certo?). Verificação é uma atividade que tem como objetivo assegurar consistência, completitude e corretitude do produto em cada fase e entre fases consecutivas do ciclo de vida do software (Estamos construindo corretamente o produto?). Teste é uma atividade que tem como objetivo examinar o comportamento do produto através de sua execução.

As atividades de VV&T compõem atividades caracterizadas como estáticas e dinâmicas, cujo objetivo é avaliar os diversos artefatos de software, ou seja, os produtos intermediários de software que são liberados ao longo do processo de desenvolvimento.

As atividades estáticas estão associadas a uma avaliação das propriedades de qualidade do produto e não implicam na execução do mesmo. Um exemplo de atividade estática é a revisão formal (inspeção, walktroughs, entre outros), pois esse tipo de atividade não implica na execução do produto que está sendo validado [Coward, 1988], como é o caso das atividades dinâmicas.

2 INSPEÇÃO

O processo de inspeção foi descrito primeiramente por Michael Fagan e é composto por seis fases, que são: Planejamento, Apresentação, Preparação, Reunião de Inspeção, Retrabalho e Acompanhamento [Fagan, 1986].

No Planejamento os inspetores são selecionados e os materiais a serem revisados são preparados; na Apresentação, o grupo recebe instruções essenciais sobre o material a ser inspecionado, especialmente sobre o que deve ser inspecionado; na Preparação, integrantes do time de inspeção se preparam para desempenhar o papel designado a cada um; no momento da Reunião de Inspeção os defeitos são encontrados, discutidos e categorizados; no Retrabalho o autor do documento corrige os defeitos encontrados pelo time de inspeção e na etapa de Acompanhamento, o time de inspeção é responsável por assegurar que todos os defeitos encontrados foram corrigidos e nenhum outro tipo de defeito foi introduzido na fase de Retrabalho. O Acompanhamento também pode ser realizado somente pelo moderador [MacDonald et al, 1995] [Fagan, 1986].

Um grupo de inspeção envolve desenvolvedores de software, entre outros participantes, em um processo formal de investigação. Consiste de três a oito participantes e inclui os seguintes papéis: o Autor que é o desenvolvedor do produto a ser inspecionado; o Moderador que é o membro da equipe que lidera a inspeção, programa e controla as reuniões, o Redator que é aquele que tem como função relatar os defeitos (passo, processo ou definição de dados incorretos, como por exemplo, uma instrução ou comando incorreto) e as questões surgidas durante a inspeção e o Inspetor que é o papel assumido por cada integrante do time de inspeção que tenta encontrar erros no produto, sendo que todos os participantes podem agir como inspetores além de executarem outras atribuições.

Segundo Fagan [Fagan, 1986], o processo de inspeção detecta mais defeitos no produto com um custo menor do que os outros mecanismos de teste existentes. Exemplos de técnicas que podem ser utilizadas numa atividade de inspeção são as técnicas de leitura. Estas técnicas são formadas por um conjunto de instruções passadas para o leitor com o objetivo de informá-lo de como se deve ler e o que olhar em um produto de software. As técnicas de leitura objetivam realizar uma tarefa particular como, por exemplo, detectar defeitos através de uma análise individual de um produto de software, o qual pode ser tanto uma especificação de requisitos, como um projeto, um código ou mesmo um plano de teste [Basili et al, 1996].

A inspeção é um processo de revisão formal de software e corresponde a uma das mais importantes atividades de GQS - Garantia de Qualidade de Software, sendo que o principal objetivo é a descoberta antecipada de falhas (produção de uma saída incorreta em relação à especificação), de modo que eles não se propaguem para o passo seguinte do processo de software.

Do ponto de vista financeiro, um estudo realizado pela IBM [IBM, 1981] mostra que enquanto um erro encontrado e corrigido durante a fase de projeto custa uma unidade monetária, se o mesmo for encontrado durante a atividade de teste do código, custará 15 unidades. Após o lançamento do produto no mercado, a correção do mesmo erro custará entre 60 e 100 unidades monetárias. Os resultados da pesquisa levam à conclusão de que, quanto antes um erro for detectado, melhor, pois o custo para reparo é menor. Outras pesquisas, como de MacDonald [MacDonald et al, 1995], reafirmam que existe um grande benefício em validar o produto logo no começo do ciclo de desenvolvimento.

Na seqüência será apresentado um resumo da técnica de leitura denominada Abstração Passo a Passo [Linger, 1979].

3 TÉCNICA DE LEITURA ABSTRAÇÃO PASSO A PASSO

A técnica Abstração Passo a Passo [Linger, 1979] é uma técnica de leitura utilizada para detectar defeitos em código fonte. Segundo o autor, a funcionalidade do programa é determinada pelas abstrações funcionais que são geradas a partir do código fonte.


Figura 1 -Exemplo de aplicação da técnica Abstração Passo a Passo
(Adaptada de [Dória, 2001])

O procedimento consiste em leituras realizadas no código fonte, a partir das quais os revisores escrevem suas próprias especificações para o programa. Para a escrita das próprias especificações, os revisores identificam trechos no código fonte, como pode ser observado na Figura 1, nível 1 de abstração, delineados por um tracejado, e realizam abstrações de funcionalidade para os mesmos.

As abstrações construídas para cada bloco são combinadas gerando uma abstração mais geral, que identifica uma função mais completa do programa. Sendo assim, o nível 4 é mais abstrato que o nível 3, que possui um nível maior de abstração em relação ao nível 2. O processo de combinar as abstrações é repetido sucessivamente e, ao final, obtém-se a Função do Programa, que então deve ser comparada com a Especificação para identificar se existem inconsistências.4 TESTE DE SOFTWARE

As atividades dinâmicas requerem que o artefato seja executado e um exemplo é a atividade de teste, na qual o programa é executado com alguns casos de teste. Um caso de teste é composto pela entrada (conjunto de dados necessários para uma execução do programa) e a correspondente saída esperada (resultado de uma execução do programa). Os resultados da execução são examinados para verificar se o programa operou de acordo com o esperado. As atividades dinâmicas são usadas, principalmente, na validação do código no nível de módulos e na integração geral do sistema.

O Teste de Software é um elemento crítico para a qualidade do produto, pois representa a revisão final da especificação, projeto e geração de código. Embora durante todo o processo de desenvolvimento de software sejam utilizadas técnicas, métodos e ferramentas a fim de evitar que erros sejam introduzidos no produto, a atividade de teste continua sendo de fundamental importância para a eliminação dos erros que persistem [Maldonado, 1991].

A atividade de teste é conduzida, em geral, em três etapas aplicadas seqüencialmente: o Teste de Unidade, o Teste de Integração e o Teste de Sistema, sendo que variações são identificadas no contexto de software Orientado a Objetos (OO).

No Teste de Unidade o objetivo é identificar erros de lógica e de implementação em cada unidade do software, separadamente.

No Teste de Integração o objetivo é verificar se as unidades, que já foram testadas isoladamente, quando colocadas em conjunto, continuam funcionando de forma adequada. Ou seja, a partir dos módulos testados no nível de unidade, constrói-se a estrutura dos programas, que foram determinadas na fase de projeto, para verificar se não há erros de interface entre os módulos.

No Teste de Sistema o objetivo é verificar se todos os elementos do sistema foram adequadamente integrados e realizam as funções esperadas.

Pequenas variações quanto à divisão das fases de teste para programas OO são identificadas na literatura. Por exemplo, Colanzi [Colanzi, 1999] caracteriza a fase do Teste de Classe, que tem por objetivos descobrir erros de integração entre os métodos dentro do escopo da classe em teste e a fase do Teste de Integração para software orientado a objetos que tem o objetivo de encontrar erros na integração de classes do sistema. Assim, segundo Colanzi, o teste orientado a objetos é organizado em quatro fases [Colanzi, 1999]:

  • Teste de unidade: testa os métodos individualmente;
  • Teste de classe: testa a interação entre métodos de uma classe;
  • Teste de integração: testa a interação entre classes do sistema e
  • Teste de sistema: testa a funcionalidade do sistema como um todo.

Ressalta-se que tanto para os sistemas procedimentais como OO, após a atividade de teste, não se pode, em geral, afirmar que o produto esteja correto. Essa afirmação só seria possível no caso de se poder aplicar o chamado Teste Exaustivo, que corresponde a exercitar o programa com todos os valores possíveis do domínio de entrada. No entanto, na maior parte dos casos, isso não é viável, pois o domínio da aplicação (conjunto de dados de entrada possíveis) é infinito ou, quando finito, grande o bastante para tornar a atividade inviável.

Assim, para contornar esse problema, surgiram as técnicas e critérios de teste. A atividade de teste é apoiada por três grandes famílias de técnicas que são: A Funcional ou Teste Caixa Preta, Estrutural ou Teste Caixa Branca e a Baseada em Erros. Na técnica de Teste Funcional o testador utiliza essencialmente a especificação do software para derivar os requisitos de teste; já na técnica Estrutural os requisitos de teste são baseados em uma implementação particular. A técnica Baseada em Erros utiliza informações sobre os erros mais freqüentemente cometidos no processo de desenvolvimento de software para estabelecer os requisitos de teste [Howden, 1987].

Cada uma dessas técnicas possui critérios que têm como finalidade obter, de maneira sistemática, um conjunto de casos de teste que seja efetivo quanto à meta principal do teste, ou seja, revelar a presença de erros no programa.

Segundo Myers [Myers, 1979], um bom caso de teste é projetado levando em conta os requisitos do projeto, tem alta probabilidade de revelar a presença de erros e de encontrar falhas no sistema até então não descobertas.

Quando um conjunto de casos de teste é utilizado na aplicação de um determinado critério e a atividade de teste é executada, pode-se calcular a cobertura obtida em relação ao critério. Esta cobertura é uma medida que indica o quanto um critério de teste foi satisfeito.

Na seção seguinte, serão apresentados os conceitos gerais das técnicas de Teste Funcional, Estrutural e Baseada em Erros.

4.1 TÉCNICA DE TESTE FUNCIONAL

O Teste Funcional também é conhecido como Teste Caixa Preta devido ao fato do software ser tratado como uma caixa, na qual é visível e disponível apenas a interface, ou seja, o lado externo [Beizer, 1990]. O conteúdo (implementação) não é utilizado pelo testador que, ao invés disso, utiliza-se da especificação para derivar os requisitos de teste. Por este motivo, pode-se dizer que o Teste Funcional analisa o produto sob o ponto de vista macroscópico.

Segundo Pressman [Pressman, 2002] o Teste Funcional procura, entre outras coisas, mostrar que os requisitos funcionais do software são satisfeitos, que a entrada é adequadamente aceita, que a saída esperada é produzida e que a integridade das informações externas é mantida; por isso, não existe preocupação com a estrutura lógica interna do sistema.

Para a técnica de Teste Funcional, exemplos de critérios são o Particionamento em Classes de Equivalência, Análise do Valor Limite e Grafo de Causa-Efeito [Beizer, 1990]. Dentre eles os dois primeiros serão comentados a seguir.

  • Particionamento em Classes de Equivalência: Este critério divide o domínio de entrada do programa em classes ou partições de equivalência, baseando-se em informações tanto da entrada como da saída do programa. Essas partições são classificadas em classes válidas (valores válidos do domínio de entrada) ou inválidas (valores inválidos do domínio de entrada). Esse critério pode simplificar e reduzir os casos de teste, pois seleciona elementos representativos de cada classe válida e inválida, testando através de um elemento, um conjunto ou classe de dados de entrada diminuindo, dessa forma, o domínio de entrada e, conseqüentemente, o tempo associado à atividade. A idéia é que se um caso de teste de determinada classe revela um erro, todos os demais elementos daquela mesma classe também revelam o mesmo erro, reduzindo assim o número total de casos de teste que devem ser desenvolvidos.

  • Análise do Valor Limite: Este critério deposita sua atenção nos valores limites do domínio de entrada do programa, pois de acordo com a literatura, é aí que se concentra o maior número de erros. Ao invés de selecionar qualquer valor de uma dada classe de equivalência, os casos de testes selecionados são os valores das fronteiras. O critério Análise do Valor Limite pode complementar o critério Particionamento em Classes de Equivalência, uma vez que a seleção de casos de teste é feita a partir das extremidades das Classes de Equivalência [Pressman, 2002]. 4.1 TÉCNICA DE TESTE ESTRUTURAL

    O Teste Estrutural, em oposição ao nome Caixa Preta, recebe o nome de Caixa Branca, como mencionado anteriormente. O motivo deve-se ao fato do teste considerar os aspectos de implementação na escolha dos casos de teste, ou seja, o testador utiliza a estrutura interna (implementação) do produto a ser testado.


    Figura 2 - Programa Identifier.c e o Grafo de Fluxo de Controle
    (Adaptada de [Maldonado et al, 2001])

    A maioria dos critérios dessa técnica utiliza uma representação do programa denominada Grafo de Fluxo de Controle, que é um grafo orientado, com um único nó de entrada, o nó 1, destaque da Figura 2(b), e um único nó de saída, o nó 11, também destacado na Figura 2(b).

    Como pode ser observado, na Figura 2(a), à esquerda das instruções do programa estão representados os números dos nós do grafo, por exemplo, tem-se que o nó 1 representa o bloco 1, que é composto pelos comandos correspondendo ao agrupamento que foi feito para identificar cada nó, representado pelos símbolos /*01*/. Cada arco representa uma transferência de controle entre esses blocos. Com base no grafo de Fluxo de Controle são identificados os componentes (nós, arcos ou caminhos) que devem ser executados para satisfazer determinado critério, caracterizando assim o Teste Estrutural.

    Os critérios de Teste Estrutural baseiam-se em diferentes tipos de componentes para determinar quais partes do programa devem ser executadas. Eles estão classificados em critérios baseados em fluxo de controle e critérios baseados em fluxo de dados [Myers, 1979]. Dentre eles, comentam-se a seguir alguns critérios referentes às duas classes.

  • Critérios Baseados em Fluxo de Controle: Esses critérios usam informações do controle da execução do programa para derivar os requisitos de teste. Dessa classe, são exemplos os critérios: Todos-Nós e Todos-Arcos.

    O critério Todos-Nós exige que cada nó do grafo seja exercitado ao menos uma vez, o que corresponde a fazer com que cada comando do programa seja executado.

    Já o critério Todos-Arcos requer que cada aresta do grafo, ou seja, cada desvio do programa, seja exercitado pelo menos uma vez.

    Os critérios Todos-Caminhos, Cobertura de Decisão, Cobertura de Condição Múltiplas, Boundary-Interior, são outros exemplos de critérios dessa categoria [Myers, 1979].

  • Critérios Baseados em Fluxo de Dados: Esses critérios usam informações do fluxo de dados do programa para derivar os requisitos de teste. Por isso é necessário adicionar ao grafo do programa informações sobre o fluxo de dados, gerando o chamado grafo Def-Uso, definido por Rapps & Weyuker [Rapps & Weyuker, 1982] [Rapps & Weyuker, 1985]. O grafo mostrado na Figura 3 apresenta os pontos que exibem definição e uso de variáveis. Com base nesse grafo são determinados quais caminhos devem ou não ser exercitados para atender os critérios baseados em fluxo de dados. O uso de uma variável se dá por um uso computacional ou uso predicativo, sendo que o uso computacional determina o uso de uma variável de forma computacional e o uso predicativo de forma predicativa, ou seja, quando uma variável é usada em uma condição de teste do programa. Dessa classe, são exemplos os critérios: Todos-Usos [Rapps & Weyuker, 1982] [Rapps & Weyuker, 1985] e Todos-Potenciais-Usos [Maldonado, 1991].


    Figura 3 - Grafo Def-Uso do programa Identifier [Maldonado et al, 2001]

    O critério Todos-Usos requer que todas as associações entre uma definição de variável e seus subseqüentes usos sejam exercitadas pelos casos de teste, sendo que uma associação é estabelecida entre uma atribuição de valor a uma variável (sua definição) e um subseqüente uso dessa variável através de um caminho livre de definição, ou seja, um caminho em que a variável não seja redefinida. Seja um grafo de fluxo de controle G = (N,E, s) onde N representa o conjunto de nós, E o conjunto de arcos, e s o nó de entrada. Um caminho pode ser definido informalmente como sendo uma seqüência finita de nós (n1,n2,..,nk), k 2, tal que exista um arco de ni para ni +1 para i = 1,2,.. k ­1 [Barbosa et al, 2000]. Um caminho pode ser chamado de não executável quando não existe um dado de entrada que leve à sua execução.

    O critério Todos-Potenciais-Usos [Maldonado, 1991] requer a execução das Potenciais-Associações. Uma Potencial-Associação, como informalmente definido por Barbosa [Barbosa et al, 2000], é uma associação na qual elementos são caracterizados independentemente da ocorrência explícita de uma referência (um uso) a uma determinada definição. Se um uso desta definição pode existir, ou seja, existir um caminho livre de definição até um certo nó ou arco (um potencial-uso) a potencial-associação entre a definição e o potencial-uso é caracterizada e, eventualmente requerida pelo critério Todos-Potenciais-Usos.

    4.1 TÉCNICA BASEADA EM ERROS

    Essa técnica tem por objetivo verificar se o software está livre de erros típicos e comuns cometidos pelo desenvolvedor durante o ciclo de desenvolvimento de um software, especificamente durante a fase de implementação.

    Os critérios Análise de Mutantes [DeMillo, 1980] e Semeadura de Erros [Budd, 1981] são exemplos de critérios dessa técnica, sendo que o critério Análise de Mutantes será apresentado na seqüência.

    Resumidamente as etapas para a aplicação do critério Análise de Mutantes são: i) definir um conjunto T de casos de teste; ii) executar o programa P em teste, com os casos de teste T; iii) selecionar os operadores de mutação para geração de mutantes; iv) executar os mutantes com os casos de teste definidos; v) analisar os mutantes vivos e; vi) calcular o escore de mutação [Vincenzi et al, 1997].

    Deve-se executar o programa original, atividade ii), com o conjunto de casos de teste T selecionado na atividade i) e, verificar se o resultado é o esperado. Se o programa apresentar um comportamento diferente para algum caso de teste, então um erro foi descoberto, caso contrário não. Vale ressaltar, que a decisão quanto ao resultado é tarefa desempenhada pelo testador.

    A geração de mutantes, atividade iii), é uma tarefa realizada pela aplicação de operadores de mutação no programa P que está sendo testado. Entende-se por operador de mutação as regras que definem as alterações que devem ser aplicadas no programa original.

    A execução dos mutantes, atividade iv), é realizada completamente de maneira automatizada. O conjunto de casos de teste T é aplicado a cada mutante Pi e os resultados obtidos são comparados com o resultado de P, finalizando a atividade v). Quando o mutante Pi apresenta um comportamento diferente de P diz-se que o mutante "morre". Do contrário, se para todo o conjunto de casos de teste T o comportamento do mutante for o mesmo que o de P então é dito que o mutante está "vivo" e deve ser analisado para verificar se ele é equivalente a P ou se o conjunto de casos de teste T é que precisa ser melhorado para que seja possível matar o mutante.

    O escore de mutação permite avaliar a adequação dos casos de testes usados e, como conseqüência, a confiança no programa testado, pois o escore de mutação relaciona o número de mutantes mortos com o número de mutantes gerados não equivalentes. O escore de mutação varia no intervalo entre 0 e 1 sendo que, quanto maior o escore mais adequado é o conjunto de casos de teste para o programa que está sendo testado [DeMillo, 1980].

    4 CONCLUSÕES

    Neste artigo foram apresentados alguns aspectos de garantia de qualidade de software, dando-se ênfase a algumas atividades de VV&T de caráter estático e dinâmico.

    Como exemplo de atividade estática foi apresentado o processo de inspeção e um resumo da técnica de leitura Abstração Passo a Passo, que é uma das técnicas que pode ser utilizada para se fazer inspeção de código.

    Como exemplo de atividade dinâmica foi apresentado o Teste de Software, destacando as técnicas de Teste Funcional, Estrutural e Baseada em Erros.

    Referências Bibliográficas

  • Katia Romero Felizardo

    Katia Romero Felizardo - Professora. Universidade do Oeste Paulista. Departamento de Computação