Teste de Fluxo de Dados Técnicas de Teste Estrutural Teste de Fluxo de Dados Análise de Mutantes • Gera casos de teste analisando o comportamento das variáveis do programa. • Procura cobrir caminhos do grafo que correspondam a diferentes combinações de estados e usos das variáveis. • Conceitos chave: ▪ Definição de uma variável ▪ Uso de uma variável Teste de Fluxo de Dados • Um nodo n em G(P) é um nodo definição de uma variável v em V se v recebe um valor no comando representado por n e denotamos esse fato por def(v,n). • Um nodo n em G(P) é um nodo uso de uma variável v em V se o valor de v é usado (referenciado) no comando representado por n e denotamos esse fato por use(v,n). Teste de Fluxo de Dados • São nodos de definição: ▪ Comandos de atribuição. ▪ Comandos de entrada. ▪ Parâmetros por referência que são alterados no corpo da rotina. • São nodos de uso: ▪ Comandos condicionais. ▪ Comandos de saída. ▪ Parâmetros por valor ou parâmetros por referência que não são alterados no corpo da rotina. • Nodos híbridos: ▪ i := i + 1; Teste de fluxo de Dados • Classificação dos nodos uso: ▪ Uso-P: um nodo uso n em relação a uma variável v é um nodo uso-P (uso em predicado) se o comando correspondente a n é uma condição (o nodo é origem de mais de uma aresta). ▪ Uso-C: um nodo uso n em relação a uma variável v é um nodo uso-C (uso em computação) se o comando correspondente a n não é uma condição (ou seja, se o nodo é origem de no máximo uma aresta). Teste de Fluxo de Dados • O teste de fluxo de dados exige a cobertura de caminhos que podem não ser exigidos pelo teste de fluxo de controle. 2 • No exemplo ao lado, para satisfazer cobertura de condição basta cobrir os caminhos: 1,2,4,5,7 e 1,3,4,6,7 5 • Se 2 for um nodo definição para v e 6 um nodo uso para v, este caminho não será testado. 1 3 4 6 7 1 Teste de Fluxo de Dados • No teste de fluxo de controle consideram-se apenas caminhos completos (do nodo inicial ao final). No teste de fluxo de dados os subcaminhos se tornam importantes. • Consideram-se os caminhos que ligam um nodo definição a um nodo uso. Teste de Fluxo de Dados • Caminhos-d-u que não são caminhos d-c são caminhos onde a variável é inicializada duas vezes antes de ser usada. • Nodos uso que não pertencem a nenhum caminho-d-u correspondem a variáveis usadas sem terem sido previamente inicializadas. Teste de Fluxo de Dados • Tipos de sub-caminhos: ▪ Caminho-d-u: (definition-use) em relação a uma variável v em um grafo de programa G(P) é um sub-caminho de G tal que o nodo inicial m do caminho é um nodo definição de v e o nodo final n é um nodo uso de v (use(v,n)). ▪ Caminho-d-c: (definition-clear) em relação a uma variável v em um grafo de programa G(P) é um caminho-d-u tal que não existe outro nodo definição de v no caminho além do nodo inicial m. Critérios de cobertura • Critérios de cobertura de fluxo de dados: ▪ Cobertura de todas as definições (alldefs): para cada nodo definição de cada variável de P, cobre-se pelo menos um caminho-d-c para algum nodo uso. • Nodos definição que não pertencem a nenhum caminho-d-c correspondem a variáveis inicializadas mas não usadas. ▪ Cobertura de todos os usos (all-uses): para cada variável v de P cobre-se pelo menos um caminho-d-c de cada nodo definição de v para cada nodo uso de v e cada nodo sucessor deste nodo uso. Critérios de cobertura Critérios de cobertura ▪ Cobertura de todos os usos-P (all-Puses): para cada variável v de P cobrese pelo menos um caminho-d-c de cada nodo definição de v para cada nodo uso-P de v e cada nodo sucessor deste nodo uso. ▪ Cobertura de todos os usos-P/alguns usos C (all-p-uses.some-C-uses): para cada variável v de P cobre-se pelo menos um caminho-d-c de cada nodo definição de v para cada nodo uso-P de v ou para pelo menos um uso-C, caso v não tenha nenhum uso-P. ▪ Cobertura de todos usos-C (all-Cuses): para cada variável v de P cobrese pelo menos um caminho-d-c de cada nodo definição de v para cada nodo de uso-C de v e cada nodo sucessor deste nodo uso. 2 Critérios de cobertura Critérios de cobertura ▪ Cobertura de todos os usos-C/alguns usos-P (all-C-uses.some-P-uses): para cada variável v de P cobre-se pelo menos um caminho-d-c de cada nodo definição de v para cada nodo uso-C de v ou pelo menos um uso-P, caso v não tenha nenhum uso-C. ▪ Cobertura de todos os caminhos-d-u (all-DU-paths): para cada variável v de P cobre-se todos os caminhos-d-c acíclicos ou que percorrem cada ciclo no máximo uma vez, de cada nodo definição de v para cada nodo uso de v e cada nodo sucessor deste nodo uso. • Cobertura all-uses: Exemplo 1. 2. typedef struct { char num, code[ 20 ]; } node; static node tab[] = { {'0', "aeiouhywAEIOUHYW"}, {'1', "bpfvBPFV"}, {'2', "cskgjqxzCSKGJQXZ"}, {'3', "dtDT"},{'4', "lL"}, {'5', "mnMN"}, {'6', "rR"}}; 3. char groupfor(char t){ 4. int i = 0; 5. while(i < 7){ 6. if (strchr(tab[i].code,t)) 7. return tab[ i ].num; 8. i++; 9. } 10. printf("Not found: %c\n",t); 11. return '0'; 12. } • Conjunto de variáveis de P = {tab,t,i} • Nodos definição e uso: variável definição Uso tab 2 6,7 t 3 6,10 i 4, 8 5,6,7,8 • Cobertura all-defs: Variável caminho ▪ Hipótese do programador competente: a maioria dos erros cometidos em programas são relativos a detalhes (operador errado, confusão com nome de variáveis etc) ▪ Hipótese do acoplamento: um conjunto de casos de teste capaz de detectar erros relativos a detalhes será capaz de detectar erros mais complexos. caminho tab 2-3-4-5-6 tab 2-3-4-5-6-8 tab 2-3-4-5-6-7 tab 2-3-4-5-6-7-12 t 3-4-5-6 t 3-4-5-6-7 t 3-4-5-6-8 t 3-4-5-10 t 3-4-5-10-11 i 4-5 i 4-5-6 i 4-5-10 i 4-5-6-7 4-5-6-8 tab 2-3-4-5-6 i t 3-4-5-6 i 4-5-6-7-12 i 4-5 i 8-5 i 8-5 i 8-5-6 i 8-5-10 i 8-5-6-7 i 8-5-6-8 i 8-5-6-7-12 Análise de Mutantes • Também conhecida como teste baseado em erros. • É uma forma de avaliar a qualidade de um conjunto de casos de teste. • Baseia-se na análise do código fonte. • Hipóteses: Variável Análise de Mutantes • Idéia básica: ▪ a partir do programa original geram-se programas com pequenas falhas e criam-se casos de teste capaz de detecta-las. ▪ Para verificar se um teste detecta a existência de falhas introduzidas, compara-se o comportamento do mutante com o do programa original. 3 Análise de mutantes Análise de Mutantes • • Conceitos: Processo de teste de mutação: ▪ ▪ Mutante: dado um programa P, um mutante de P é um programa P’ sintaticamente correto obtido a partir de uma alteração no código fonte definida por um operador préestabelecido chamado operador de mutação. ▪ Morte de mutantes: Um teste T mata um mutante P’ se o resultado da execução de P’ para T é diferente do resultado da execução de P para T. ▪ ▪ ▪ ▪ Iniciar com o programa P a ser testado e um conjunto de testes T (possivelmente vazio); Gerar um conjunto M de mutantes de P, através de operadores de mutação (pequenas alterações sintaticamente corretas) no programa original; Verificar se os testes em T matam todos os mutantes em M; Se todos os mutantes foram mortos, o conjunto T é adequado, e o processo é encerrado. Senão, acrescenta-se um teste para cada mutante M sobrevivente, até que o conjunto T seja adequado. Análise de Mutantes Exemplo • Exemplos de operadores de mutação: ▪ substituição de uma variável por outra; ▪ substituição de uma variável escalar por um vetor; ▪ substituição de um vetor por uma variável escalar; ▪ substituição de operador relacional; ▪ substituição de operador aritmético; ▪ etc. int max(int* input, int size){ int j, result; result = 0; for (j =1; j < size; j++) { if (input[j] > input[result]) result = j; } return result; teste input[0] } } 1 1 input[1] input[2] size Result. Esp. 2 3 3 2 2 3 1 2 3 0 3 1 3 2 3 1 Exemplo • Conjunto inicial: int max(int* input, int size){ int j, result; # result = 0; 1 for (j =1; j < size; j++) { if (input[j] >= input[result]) 2 result = j; 3 } return result; } • Conjunto } input[0] input[1] input[2] size Result. esperado Result. obtido 1 2 3 3 2 2 3 1 2 3 0 0 1 3 2 3 1 1 que mata o mutante: # input[0] input[1] input[2] size Result. esperado Result. obtido 1 1 2 3 3 2 2 2 3 1 2 3 0 0 3 1 3 2 3 1 1 4 1 2 2 3 1 2 4