Conteúdo


Uma Nova Técnica de Seleção de Testes com Compiladores IBM XL C/C++ e Fortran para Desenvolver Ambientes de Teste Mais Inteligentes

Reduza o número de casos de teste usados para teste de regressão sem degradar a cobertura de teste

Comments

Introdução a profile-directed selection

O teste de regressão é frequentemente necessário para garantir a qualidade do software. No entanto, é considerado um processo caro. Para reduzir esse custo, técnicas de seleção de teste são aplicadas à suíte de testes completa.

O resultado de uma técnica de seleção de teste é uma suíte de testes reduzida. Existem várias técnicas de seleção que tentam reduzir o tamanho da suíte e, ao mesmo tempo, manter uma alta taxa de detecção de erros.

Este artigo apresenta uma técnica de seleção de testes rápida e eficiente, que usa dados de perfil para selecionar os casos de teste. Nós chamamos essa técnica de profile-directed selection (PDS).

PDS opera em duas fases:

Preenchendo o banco de dados com dados de perfil
A primeira fase relaciona o código fonte do aplicativo com os casos de teste. Ela executa a suíte de testes completa em uma cobertura de código ou aplicativo com o recurso de perfil. Os dados de perfil resultante catalogam os arquivos de origem, nomes de funções e instruções de linha chamados por cada caso de teste. Em seguida, cada caso de teste é associado a arquivos e funções do aplicativo encontrados. Esse relacionamento é armazenado em um banco de dados relacional. Os dados de cobertura do código podem ser aplicados com maior granularidade, mas isso tem um custo: mais dados significam mais tempo de processamento e manutenção.
 
Selecionando os casos de teste com base no conjunto de mudanças
A segunda fase, seleção do teste, desenvolve e executa uma consulta baseada nas modificações feitas no código fonte do aplicativo. A consulta seleciona os casos de teste, que atravessam uma função modificada pela alteração no código.
 
Figura 1. Seleção de teste em testes de regressão
Seleção de teste em testes de regressão
Seleção de teste em testes de regressão

Instrumentação do gerenciador de perfis

PDS depende de dados de perfil para relacionar casos de teste com o código fonte de um aplicativo. Portanto, é necessário instrumentar o aplicativo para gerar dados de perfil. Ao selecionar um gerenciador de perfis, leve em conta o seguinte:

  • Impacto relativo no desempenho
  • Usabilidade da saída para conjunto de ferramentas
  • Requisitos de armazenamento

Em nossos experimentos, escolhemos usar os ganchos de função através do recurso functrace nos compiladores IBM XL C/C++ e Fortran. Usamos esse recurso de compilador para colocar um gancho func_trace_enter e func_trace_exit em cada função em nosso aplicativo de amostra. Essas funções geram os dados do perfil.

PDS usa um banco de dados relacional para relacionar cada função e arquivo de origem com cada caso de teste. Usamos IBM DB2® em nossos experimentos.

PDS é uma técnica de seleção granular no nível de função. A granularidade de cobertura de código pode ser maior ou menor, conforme necessário. Escolhemos a granularidade no nível de função porque representa uma média entre os custos do processamento da técnica de seleção e a redução no tamanho da suíte de testes. Técnicas de seleção mais finas, como técnicas de fluxo de controle baseadas em gráfico, podem selecionar menos casos de teste, mas têm um custo maior em tempo de processamento e requisitos de armazenamento. Para aplicativos que consistem em centenas de milhares de casos de teste, o custo de processamento pode exceder o benefício da técnica. Por isso, a granularidade deve ser levada em conta.

Detecção de conjunto de mudanças de função

Modificações feitas nos arquivos de origem entre S e S' são chamadas de conjunto de mudanças. A extração das funções e arquivos de origem modificados no conjunto de mudanças foi feita manualmente nos nossos experimentos.

A maioria dos aplicativos de ambiente de desenvolvimento integrado (IDE) pode detectar as alterações entre S e S' automaticamente.

Modelagem da eficácia da técnica de seleção

A eficácia de profile-directed selection foi baseada na eficácia da detecção de defeitos e na taxa de redução média da suíte de testes.

Técnica profile-directed selection, ou PDS

A técnica PDS procura selecionar casos de teste que foram afetados pelo conjunto de mudanças entre S e S'.

Como mencionamos, PDS consiste em duas fases:

  1. Preenchendo o banco de dados com dados de perfil
  2. Selecionando os casos de teste com base no conjunto de mudanças
Figura 2. Visão geral de profile-directed selection
Visão geral de profile-directed selection
Visão geral de profile-directed selection

Algoritmo

PDS pode ser expresso no seguinte pseudocódigo:

1. Test S with T to generate P={P1,…,Pn}, the profile data of S. 
2. For each test case Ti for i=1 to n, relate Pi to Ti in a relational database. 
3. Let P' be the source code change set between S and S'. 
4. For i=1 to n, if |P'∩Pi|>0 then return Ti.

Fase 1. Preenchendo o banco de dados de casos de teste

A Fase 1 do PDS preenche o banco de dados relacionando os casos de teste ao código fonte.

Um pré-requisito da Fase 1 é que S deve ser instrumentado com um gerenciador de perfis. Em nosso caso, nós usamos o recurso functrace no compilador IBM XL C/C++ e Fortran para inserir ganchos func_trace_enter e func_trace_exit em cada função.

Figura 3. Desenvolver um aplicativo que use perfis com functrace
Desenvolver um aplicativo que use perfis com functrace
Desenvolver um aplicativo que use perfis com functrace

Nossas definições de gancho geraram dados de perfil que incluíam os caminhos dos arquivos de origem e as funções que foram exercitados. Para reduzir os requisitos de armazenamento totais, usamos uma estrutura de dados Set para armazenar nomes de função exclusivos e caminhos de origem exercitados. Também retivemos a frequência de chamada de cada função. Os dados de perfil resultantes possuem o seguinte formato para cada caso de teste:

 Source:Function:CallFrequency main.c:main():1 main.c:foo():2 boo.c:goo():1

A primeira coluna representa o arquivo de origem, a segunda representa o nome da função e a última representa a frequência de chamada.

Com esse design, executamos toda a suíte de testes T em S. Isso gerou os dados de perfil P para cada caso de teste.

Para cada caso de teste Ti para i=1 a n, relacionamos os dados de perfil Pi com Ti em um banco de dados relacional.

Este é o esquema do banco de dados:

Tabela: TestCaseToSourceCode

 Test Case Path, Function Name, Source Path

Em nossa implementação, como mostra a Listagem 1, normalizamos essa tabela e criamos três tabelas para o caminho de caso de teste, arquivo de origem e nome da função.

Listagem 1. Exemplo de implementação usando functrace
#include <iostream>
#include <sstream>
#include <fstream>
#include <map>
#include <demangle.h>

static bool exitedMain = false;

struct ProfileData
{
    std::string sourceFile;
    std::string functionName;
};

struct Comparator
{
    bool operator()(ProfileData s1, ProfileData s2) const
    {
        return strcmp((s1.sourceFile+ s1.functionName).c_str(), 
        (s2.sourceFile + s2.functionName).c_str()) < 0;
    }
};

static std::map<ProfileData, int, Comparator>* tccMap = NULL;

static std::string getDemangledName(std::string mangledFunctionName)
{
    char *rest;
    Name* name;
    if ((name = Demangle(const_cast<char*>(mangledFunctionName.c_str()), rest)) != NULL)  
        return ((FunctionName *)name)->Text();    
    else
        return mangledFunctionName;    
}

static void printData(const ProfileData &data, int callFrequency)
{
    std::string functionName = getDemangledName(data.functionName);
    
    std::ofstream outputFile("func_trace_profile.dat", std::ios::out | std::ios::app); 
    outputFile << data.sourceFile << "|" 
    << functionName << "|" << callFrequency << std::endl; 
    outputFile.close();
}

std::string basename(std::string path)
{
    std::string::size_type size = path.find_last_of ('/');

    if ( size == std::string::npos )
        return path;

    return path.substr(size + 1);
}

extern "C"
void __func_trace_enter(const char *functionName, 
const char *fileName, int lineNumber, void** const id)
{ 
    if (tccMap == NULL)
        tccMap = new std::map<ProfileData, int, Comparator>;

    ProfileData data;
    data.sourceFile = basename(fileName);
    data.functionName = functionName;

    if (tccMap->find(data) == tccMap->end()) 
    {
        if (exitedMain) 
            printData(data, 1);

        (*tccMap)[data] = 1;
    }
    else 
    {
        (*tccMap)[data]++;
    }
}

extern "C"
void __func_trace_exit(const char *functionName, 
const char*fileName, int lineNumber, void** const id)
{
    if (strcmp(functionName, "main") == 0) 
    {
        std::map<ProfileData, int, Comparator>::iterator myIterator;

       for(myIterator = tccMap->begin(); myIterator != tccMap->end(); myIterator++)
        {
    int callFrequency = myIterator->second;
    printData(myIterator->first, callFrequency);
        }
        exitedMain = true;
	}	 
}

Nós omitimos a frequência de chamada dos dados de perfil em nossos experimentos, mas planejamos usá-la para fins de classificação no futuro.

Quando o software modificado S' ficou disponível, prosseguimos para a Fase 2.

Fase 2. Seleção de teste

Na Fase 2, detectamos manualmente as funções modificadas, incluídas e excluídas entre S e S'. No caso de funções excluídas e incluídas, identificamos a função-pai. A partir desse conjunto de mudanças, criamos uma consulta para o banco de dados relacional, que produziu uma suíte de testes reduzida T'.

Análise empírica

Após estabelecer um mecanismo para selecionar casos de teste com base no conjunto de mudanças do software, realizamos um experimento para analisar a eficácia da técnica PDS.

Experimento

Nosso experimento comparou três técnicas de seleção de teste (aleatório, seleção manual e PDS) com uma suíte de testes completa. Testamos a suíte usando os compiladores IBM XL C/C++ Versão 11.1 para AIX®.

Selecionamos duas suítes de teste internas. Uma tinha 832 casos de teste a outra, 781.

Análise

Usando as suítes de teste internas, houve um total de 16 defeitos localizados entre os dois desenvolvimentos do compilador IBM XL C/C++ V11.1 que escolhemos testar. Demorou aproximadamente 900 segundos para executar cada suíte de teste.

As técnicas de seleção aleatória e manual foram muito baratas, demorando menos de 50% de uma execução da suíte de testes completa. No entanto, elas apresentaram taxas de detecção de defeito muito menores, encontrando apenas 37,5% (6 de 16) e 43,75% (7 de 16) do número total de defeitos.

A técnica PDS também reduziu o tempo de processamento em aproximadamente 50%, em média, entre as duas suítes de testes. Ela foi significativamente mais eficaz na detecção de defeitos, apresentando uma taxa de detecção de 100% (16 de 16) em relação à execução aleatória e de seleção manual.

Portanto, ao determinar a efetividade em custo de cada técnica, examinamos o número de defeitos encontrados no tempo de execução de cada suíte. A técnica PDS apresentou um resultado de 0,3 defeito por segundo, mas as técnicas completa, aleatória e seletiva (seleção manual) tiveram resultados de menos de 0,2 defeito por segundo.

Figura 4. Dados técnicos empíricos, por técnica
Dados técnicos empíricos, por técnica
Dados técnicos empíricos, por técnica
Figura 5. Eficácia das técnicas de seleção
Eficácia das técnicas de seleção
Eficácia das técnicas de seleção

Instruções futuras

No futuro, estudaremos o impacto de várias granularidades. Mais especificamente, pretendemos comparar os custos de processamento e manutenção do preenchimento do relacionamento entre caso de teste e código fonte. Além disso, pretendemos encontrar a granularidade que fornecerá o menor custo de processamento enquanto mantém uma alta taxa de redução da suíte de testes.

Essa técnica poderia ser aplicada a execuções de teste de sanidade, nas quais é necessário um número fixo de casos de teste. O problema que precisa ser investigado é quais casos escolher e como classificá-los. Uma possibilidade é combinar essa técnica com técnicas de priorização, uma das quais seria o uso da frequência de chamada como classificação.

Integração de ferramenta para verificação automática do código

No futuro, gostaríamos de ver esse algoritmo implementado em aplicativos IBM existentes, como Rational Application Developer, Rational Developer para System z, Rational Developer para Software Power Systems e Rational Team Concert™. O ideal é que, quando um desenvolvedor enviar mudanças, uma lista de casos de teste afetados pelo conjunto de mudanças dado seja selecionado e executado.

Os casos de teste devem ser executados on demand, com base nas mudanças do código fonte. Isso permite que os desenvolvedores verifiquem automaticamente as alterações em seu código, evitando a necessidade de executar regressões completas.

Convidamos você a enviar sugestões, feedback ou outros comentários sobre a técnica PDS e seu uso em potencial no campo de testes.

Agradecimentos

Os autores agradecem às seguintes pessoas, que tornaram este artigo possível: Preston Koo, Roch Archambault e Kobi Vinayagamoorthy.


Recursos para download


Temas relacionados

  • Referências relacionadas a este artigo:
    • G. Rothermel e M.J. Harrold. A Safe, Efficient Regression Test Selection Technique. In ACM Trans. Software Eng. and Methodology, vol. 6, no. 2, pp. 173-210, abril de 1997.
    • Todd L. Graves, Mary Jean Harrold, Jung-Min Kim, Adam Porter, Gregg Rothermel. An empirical study of regression test selection techniques. In Proceedings of the 20th international conference on Software engineering, p.188-197, 19-25 de abril de 1998, Kyoto, Japão.
    • IBM XL C/C++ for AIX, V11.1, Compiler Reference, 2009.
  • Para mais informações sobre os compiladores:
    • Compiladores IBM XL C/C++ para AIX, Linux, IBM z/OS®, IBM z/VM®, mais a XS C/C++ Advanced Edition para Blue Gene® e Development Studio para IBM System i®.
    • Compiladores IBM Fortran: XL Fortran para AIX, XL Fortran para Linux, XL Fortran Advanced Edition para Blue Gene, VS Fortran
  • Assine a newsletter semanal de email do developerWorkse escolha os tópicos que irá seguir.
  • Faça download e avalie XL C/C++ para AIX, que oferece tecnologias avançadas de compilação e otimização criadas para AIX e Power Systems.
  • Faça download de uma versão gratuita do software Rational.
  • Avalie outros produtos de software da IBM da maneira que for melhor para você: faça o download da versão de teste, sua avaliação online, use-a em um ambiente de nuvem ou passe algumas horas no SOA Sandbox aprendendo a implementar Arquitetura Orientada a Serviços de forma eficiente.

Comentários

Acesse ou registre-se para adicionar e acompanhar os comentários.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Rational
ArticleID=826293
ArticleTitle=Uma Nova Técnica de Seleção de Testes com Compiladores IBM XL C/C++ e Fortran para Desenvolver Ambientes de Teste Mais Inteligentes
publish-date=07202012