aula 05 teste automatizado partes i e iiinf1301/docs/2019_1/aula_5.pdfteste automatizado –partes i...
TRANSCRIPT
Aula 05Teste Automatizado – Partes I e II
Alessandro Garcia
LES/DI/PUC-Rio
Março de 2019
2 / 30 LES/DI/PUC-Rio
Exercício sobre Introdução à Teste
• Se organizem em grupos (2 a 3 pessoas): de preferência o grupo
que irá realizar os trabalhos na disciplina
• Escrevam em uma folha de papel o seguinte cabeçalho:
EXERCÍCIO 1: INTRODUÇÃO À TESTEData: (data de hoje)Integrantes: (nome dos integrantes do grupo)
Atividade 1: Casos de teste para uma função
3 / 30 LES/DI/PUC-Rio
Exemplo: criando casos de teste
• Um bom conjunto de casos de teste é aquele que possui alta
probabilidade em revelar defeitos na função ou no módulo
que a contém
int verificaTriangulo (int segmentoAB, int segmentoAC, int segmentoBC);
EXERCÍCIO 1: INTRODUÇÃO À TESTEData: (data de hoje)Integrantes: (nome dos integrantes do grupo)
Atividade 1: Casos de teste para uma função
•C1: (a, b, c) -> (d)•C2: (x, y, z) -> (k)•...
4 / 30 LES/DI/PUC-Rio
Criando casos de teste com a especificação
EXERCÍCIO 1: INTRODUÇÃO À TESTE
Data: (data de hoje)
Integrantes: (nome dos integrantes do grupo)
•Atividade 1: Casos de teste para uma função
•C1: (a, b, c) -> (d)
•C2: (x, y, z) -> (k)
•...
•Atividade 2: Casos de teste com especificação
•C1: (a, b, c) -> (d)
•C2: (x, y, z) -> (k)
5 / 30 LES/DI/PUC-Rio
Criando casos de teste com a especificação
A função recebe como entrada três valores inteiros. Cada valor
corresponde ao segmento de um lado do triângulo. Assim, o primeiro valor
corresponde ao segmento do lado AB, o segundo valor ao segmento do
lado AC e o terceiro valor correspondem ao segmento do lado BC. Esses
três segmentos correspondem aos três lados que formam o triângulo ABC.
A função recebe os três valores como entrada e verifica que tipo de
triângulo é formado com base nos três segmentos. Por fim, a função
retorna a quantidade de lados que possuem o mesmo tamanho. Assim, se
o triângulo for equilátero, ou seja, os três lados possuem o mesmo
tamanho, então a função retorna 3. Se o triângulo for isósceles (dois lados
com o mesmo tamanho), então a função retorna 2. Se o triângulo for
escaleno (os três lados possuem tamanhos diferentes), então a função
retorna 0. Caso os três valores não correspondam a um triângulo, a função
retorna o código -1.
int verificaTriangulo (int segmentoAB, int segmentoAC, int segmentoBC);
6 / 30 LES/DI/PUC-Rio
Casos de teste
• Um dos problemas comuns ao testar software e determinar
quando os módulos foram suficientemente testados
• Mesmo usando a especificação, é difícil produzir bons casos de
teste que sejam capazes de revelar a existência de defeitos
• Por exemplo, para o problema do triângulo, é necessário apenas
quatro casos de testes para se testar a função em termos dos três
tipos de triângulo
– 1 caso válido para triângulo equilátero (retorna 3)
– 1 casos válidos para triângulo isósceles (retorna 2)
– 1 caso válido para triângulo escaleno (retorna 0)
– 1 caso válido para valores que não correspondem a um triângulo
(retorno -1)
7 / 30 LES/DI/PUC-Rio
Criando casos de teste com a especificação
int verificaTriangulo(int segAB, int segAC, int segBC){
if ((segAB == segAC) && (segAC == segBC)){
return 3; //equilatero
}
if (((segAB == segAC) && (segAC != segBC)) ||
((segAC == segBC) && (segAB != segAC)) ||
((segAB == segBC) && (segBC != segAB))){
return 2; //isosceles
}
if ( (segAB != segAC) && (segAC != segBC)) {
return 0; //escaleno
}
return -1;
}Qual é a saída para o caso de teste (2, 1, 1)?
8 / 30 LES/DI/PUC-Rio
Criando casos de teste com a especificação
• Qual é a saída para o caso de teste (2, 1, 1)?
– 2 -> triângulo isósceles
• Esses valores não correspondem a um triângulo.
• Para se ter um triângulo, é necessário que ele tenha em cada um
dos seus lados, uma medida menor que a soma das medidas dos
outros dois lados.
AB < AC + BC
AC < AB + BC
BC < AB + AC
–Observação: Se AB for o maior lado, para existir um triângulo,
basta que AB < AC + BC.
9 / 30 LES/DI/PUC-Rio
Função verificaTriangulo
int verificaTriangulo(int segAB, int segAC, int segBC){
if ( (segAB < segAC+ segBC) && (segAC< segAB + segBC) && (segBC< segAB +
segAC)){
if ((segAB == segAC) && (segAC == segBC)){
return 3; //equilatero
}
if (((segAB == segAC) && (segAC != segBC)) ||
((segAC == segBC) && (segAB != segAC)) ||
((segAB == segBC) && (segBC != segAC))){
return 2; //isosceles
}
if ( (segAB != segAC) && (segAC != segBC)) {
return 0; //escaleno
}
}
return -1;
} E agora, o código está livre de defeitos???
10 / 30 LES/DI/PUC-Rio
Completude dos casos de teste
• Um bom conjunto de casos de teste seria aquele que cobrisse os
seguintes casos:
– 1 caso válido para triângulo equilátero (retorna 3)
– 1 caso válido para triângulo escaleno (retorna 0)
– 3 casos válidos para triângulo isósceles (retorna 2)
– 3 casos para testar Lk > Li + Lj (retorna -1)
– 3 casos para testar Lk = Li + Lj (retorna -1)
– 3 casos para testar um dos lados igual a 0 (retorna -1)
– 3 casos para testar um dos lados menor do que 0 (retorna -1)
• Testes somente sao capazes de mostrar a presenca de
faltas, mas nao a ause ncia delas.
11 / 27
Especificação
• Objetivo dessa aula
– Apresentar os uma técnica para a criação testes automatizados
e mostrar como desenvolver dirigido por testes.
• Referência básica:
– Monografia: Arcabouço para a Automação de Testes de Programas Redigidos em
C; contido no arquivo TesteAutomatizado.zip acessível para download através
do site da disciplina, aba: Software
• Referência adicional:
– Beck, K.; Test-Driven Development by Example; Reading, Massachusetts:
Addison-Wesley; 2003
• Slides adaptados de: Staa, A.v. Notas de Aula em Programacao Modular;
2008.
12 / 27
Sumário
• Automação simplória
• Arcabouço de apoio ao teste automatizado
• Linguagem de diretivas de teste
• Interpretador de diretivas
• Exemplo de uso do arcabouço
• Arquitetura do arcabouço
• Vantagens e desvantagens
13 / 30
Como testar um módulo?
• Uso de um módulo controlador do teste
desenvolvido que auxilia a execução de casos de
teste para testar um módulo sob teste
• Três formas:
– Teste manual: controlador exibe um menu e
comparação é feita pelo próprio testador à olho nu
– Teste de comparação automatizado:
• Casos de teste são implementados em C
• Casos de teste são redigidos em scripts em uma linguagem
com uma sintaxe própria
14 / 30
Exemplo simplório
. . .
ContaCaso ++ ;
if ( CriarArvore( ) != ARV_CondRetOK )
{
printf( "\nErro ao criar árvore" ) ;
ContaFalhas ++ ;
}
ContaCaso ++ ;
if ( InserirEsquerda('a' ) != ARV_CondRetOK )
{
printf( "\nErro ao inserir nó raiz da árvore" ) ;
ContaFalhas ++ ;
}
ContaCaso ++ ;
if ( IrPai( ) != ARV_CondRetEhRaiz )
{
printf( "\nErro ao ir para pai de nó raiz" ) ;
ContaFalhas ++ ;
}
. . .
instância de caso de teste
compara obtido com o
esperado
É NECESSÁRIO REPETIR O MESMO CÓDIGO PARA CADA CASO DE TESTE
15 / 27
Por que é simplório?
• O código é muito repetitivo
– viola a regra de evitar duplicações de código
16 / 30 LES/DI/PUC-Rio
Exemplo simplório
. . .
ContaCaso ++ ;
if ( CriarArvore( ) != ARV_CondRetOK )
{
printf( "\nErro ao criar árvore" ) ;
ContaFalhas ++ ;
}
ContaCaso ++ ;
if ( InserirEsquerda('a' ) != ARV_CondRetOK )
{
printf( "\nErro ao inserir nó raiz da árvore" ) ;
ContaFalhas ++ ;
}
ContaCaso ++ ;
if ( IrPai( ) != ARV_CondRetEhRaiz )
{
printf( "\nErro ao ir para pai de nó raiz" ) ;
ContaFalhas ++ ;
}
. . .
Muita repetição
17 / 27
Por que é simplório?
• O código é muito repetitivo
– viola a regra de evitar duplicações de código
• O módulo controlador do teste pode tornar-se muito
extenso
– dificulta verificar se o teste é um bom teste
• O código não produz um laudo do teste
– as mensagens impressas não explicitam o porquê da falha
• quais foram os valores que causaram a mensagem?
18 / 27
Como reduzir o volume de repetição?
• Criar um arcabouço (framework) com as funções de
comparação.
Framework
Pontos deextensão
Aplicação
Serviço do
arcabouço
19 / 30
Exemplo de uma função de comparação
TST_tpCondRet TST_CompararInt( long ValorEsperado ,
long ValorObtido ,
char * pMensagem )
{
if ( ValorObtido != ValorEsperado )
{
ContaFalhas ++ ;
fprintf( pArqLog , "\nErro >>> %s" , pMensagem ) ;
fprintf( pArqLog , "Deveria ser: %ld É: %ld" ,
ValorEsperado , ValorObtido ) ;
return TST_CondRetErro ;
} /* if */
return TST_CondRetOK ;
} /* Fim função: TSTG &Comparar inteiro */
mensagem de erro
gera-se um laudo de
erros
20 / 27
Como fica o código agora?
. . .
if ( TST_CompararInt( ARV_CondRetOK , CriarArvore( ) ,
"Erro ao criar árvore" ) != TST_CondRetOK )
{
return ARV_CondRetOK;
}
if ( TST_CompararInt( ARV_CondRetOK , InserirEsquerda('a' ) ,
"Erro ao inserir nó raiz da árvore" ) != TST_CondRetOK )
{
return ARV_CondRetOK;
}
if ( TST_CompararInt( ARV_CondRetEhRaiz , IrPai( ) ,
"Erro ao ir para pai na raiz" ) != TST_CondRetOK )
{
return ARV_CondRetEhRaiz;
}
. . .
Opção 1: massa de teste escrita com a própria linguagem de programação
Como fica o código agora? Melhorou?
. . .
if ( TST_CompararInt( ARV_CondRetOK , CriarArvore( ) ,
"Erro ao criar árvore" ) != TST_CondRetOK )
{
return ;
}
if ( TST_CompararInt( ARV_CondRetOK , InserirEsquerda('a' ) ,
"Erro ao inserir nó raiz da árvore" ) != TST_CondRetOK )
{
return ;
}
if ( TST_CompararInt( ARV_CondRetEhRaiz , IrPai( ) ,
"Erro ao ir para pai na raiz" ) != TST_CondRetOK )
{
return ;
}
if ( TST_CompararInt( ARV_CondRetOK , InserirEsquerda(‘b' ) ,
"Erro ao inserir nó raiz da árvore" ) != TST_CondRetOK )
{
return ;
}
. . .
ainda é repetitivo, verboso
AINDA É NECESSÁRIO REPETIR O MESMO CÓDIGO PARA CADA CASO DE TESTE
22 / 30
Melhorou?
• Sim, pode-se adicionar mais funcionalidades ao arcabouço
– outros comparadores
• Sim, ganhou-se uniformidade
• Não, o código continua extenso
• Não, pára na primeira falha encontrada
23 / 27
3ª. Opção: nossa solução
• A solução que adotamos é um módulo interpretador
inspirado no JUnit
– permite-se criar linguagens de script para definição da massa
de teste
– Objetivos:
• redução da complexidade e repetição do código de teste
• abstração de detalhes para o testador
– escreve-se cada caso de teste em uma única linha
– desnecessário alterar o módulo controlador de teste
• torna-se a linguagem muito próxima da “linguagem natural”
• Para quem programa Java, C++, C# e outros, existem
diversos arcabouços: JUnit, CppUnit, CSUnit, ...
24 / 27
Exemplo de uma massa de teste
. . .
== Criar árvore
=criar OK
=irdir ArvoreVazia
== Inserir à direita
=insdir CharA OK
== Obter o valor inserido
=obter CharA OK
== Ir para no pai, não tem
=irpai EhRaiz
== Inserir à esquerda
=insesq CharB OK
=obter CharB OK
== Ir para no pai, tem
=irpai OK
=obter CharA OK
. . .
25 / 27
Exemplo de uma massa de teste
. . .
== Criar árvore
=criar OK
=irdir ArvoreVazia
== Inserir à direita
=insdir CharA OK
== Obter o valor inserido
=obter CharA OK
== Ir para no pai, não tem
=irpai EhRaiz
== Inserir à esquerda
=insesq CharB OK
=obter CharB OK
== Ir para no pai, tem
=irpai OK
=obter CharA OK
. . .
Se fôssemos escrever estes
mesmos casos de teste com
a abordagem anterior, teríamos
que produzir 9 x 5 = 45 linhas de
código em C.
Definição da Linguagem de Script de Teste
• Passo 1 – definir comandos (não são declarados no início do script):
Sintaxe: =<nome da função> <lista parâmetros> <condição retorno>
Exemplo – Módulo Árvore:=criar < > <OK, FaltouMemoria, etc...>
=irdir < > <OK, ArvoreVazia, etc...>
=insdir <char> <OK, ValorInvalido, FaltouMemoria, etc..>
=irpai < > <OK, EhRaiz, etc...>
=obter <char> <OK, ValorInvalido, etc...>
etc..
• Passo 2 - declaração dos valores simbólicos
Sintaxe: =declararparm <nome simbólico> <tipo> <valor>
• tipos conhecidos: int , char , float , string
Exemplos== Declarar as condições de retorno
=declararparm OK int 0
=declararparm ArvoreVazia int 5
== Declarar os valores contidos nos nós
=declararparm CharA char 'a'
26 / 27
27 / 30 LES/DI/PUC-Rio
Arquitetura simplificada da nossa abordagem
Programaprincipal
Móduloem teste
Teste deitem deinterface
Funçãode controleespecífica
Módulo de controlegenérico
Diretivasde teste
Logdo teste
Módulo deleitura
LERPARM.h
GENERICO.h
PRINCIP.h
coordena a realização
dos testes; disponibiliza
funções genéricas de
comparação
TST_ESPC.h
interpreta os comandos
genéricos e específicos
*.script
Construindo módulo controlador de teste...
28 / 27
• Exemplo: TestaArvore.c
– interface: TST_ESPC.h (interface única para todos módulos de teste)
– Passo 1 – incluindo as interfaces necessárias
#include "TST_ESPC.H"
#include "generico.h"
#include "lerparm.h"
#include "arvore.h"
interface do módulo sendo
testado...
interfaces do arcabouço...
Construindo módulo controlador de teste...
29 / 27
• Exemplo: TestaArvore.c
– Passo 2 – declarando comandos da linguagem de script de teste
/* Nomes dos comandos de teste específicos */
#define CRIAR_ARV_CMD "=criar"
#define INS_DIR_CMD "=insdir"
#define INS_ESQ_CMD "=insesq"
#define IR_PAI_CMD "=irpai"
#define IR_ESQ_CMD "=iresq"
#define IR_DIR_CMD "=irdir"
#define OBTER_VAL_CMD "=obter"
#define DESTROI_CMD "=destruir"
Instrução de pré-processamento
para definição de constantes...
Construindo módulo controlador de teste...
30 / 27
• Exemplo: TestaArvore.c
– Passo 3 – implementar função de tratamento e execução do comando de teste
/*****************************************************************
******
*
* $FC Função: Efetuar operações de teste específicas para árvore
*
* $EP Parâmetros
* $P ComandoTeste - String contendo o comando
*
******************************************************************
*****/
TST_tpCondRet TST_EfetuarComando( char * ComandoTeste )
{
}
completar tratamento de cada comando
sequencia de if e else ifs
31 / 27
Exemplo de fragmento do controlador
if ( strcmp( ComandoTeste , CRIAR_ARV_CMD ) == 0 ) {
…
/* Testar ARV Adicionar filho à direita */
else if ( strcmp( ComandoTeste , INS_DIR_CMD ) == 0 )
{
NumLidos = LER_LerParametros( "ci" ,
&ValorDado , &CondRetEsperada ) ;
if ( NumLidos != 2 )
{
return TST_CondRetParm ;
} /* if */
return TST_CompararInt( CondRetEsperada ,
ARV_InserirDireita( ValorDado ) ,
"Retorno errado inserir a direita." );
} /* fim: Testar ARV Adicionar filho à direita */
Ago 2008 32 / 27Arndt von Staa © LES/DI/PUC-Rio
Como usar?
• Forma “big bang”
1. Especificar a interface do módulo
• arquivo .h
2. Especificar a linguagem de diretivas de teste, sintaxe
• =<nome da função> <lista parâmetros> <condição retorno>
3. Redigir a massa de teste nesta linguagem
• serve como uma especificação executável!
4. Redigir o módulo de teste específico
5. Redigir o módulo a ser testado
6. Rever cuidadosamente os cinco artefatos
7. Compilar
• usar a biblioteca: ArcaboucoTeste.lib
8. Executar o teste
9. Corrigir até zero falhas observadas pelo teste
Ago 2008 33 / 27Arndt von Staa © LES/DI/PUC-Rio
Como usar?
• Forma iterativa
– preparação inicial
1. Especificar parcialmente a interface do módulo a desenvolver
2. Especificar a linguagem de diretivas de teste
3. Redigir parte da massa de teste nesta linguagem
4. Redigir a versão inicial do módulo de teste específico
– todos os comandos retornam TST_CondRetNaoImplementado
5. Redigir o enchimento (stub) do módulo a ser testado
– todas as funções fazem nada, se necessário retornam um valor neutro
6. Rever cuidadosamente os cinco artefatos
7. Compilar
– usar a biblioteca: ArcaboucoTeste.lib
8. Executar o teste
9. Corrigir até que sejam gerados somente erros de comando não
implementado
Ago 2008 34 / 27Arndt von Staa © LES/DI/PUC-Rio
Como usar?
• Forma iterativa
– iteração
1. Escolher as funções a serem implementadas
2. Rever ou completar a interface do módulo
3. Rever a linguagem de diretivas de teste
4. Redigir a massa de teste nesta linguagem
5. Redigir a parte do módulo de teste específico visando as funções
6. Redigir as funções do módulo a ser testado
7. Rever cuidadosamente os cinco artefatos
8. Compilar
– usar a biblioteca: ArcaboucoTeste.lib
9. Executar o teste
10.Corrigir até que
– esteja tudo correto
– sejam gerados somente erros de comando não implementado
Ago 2008 35 / 27Arndt von Staa © LES/DI/PUC-Rio
Exemplo prático
• Exemplo \arcabouc\simples
Ago 2008 36 / 27Arndt von Staa © LES/DI/PUC-Rio
Quais seriam as vantagens?
• Teste automatizado exige rigor ao escrever
– as especificações das interfaces
– o script de teste
– embora alguns não acreditem, rigor é sempre vantagem!
• Facilita o desenvolvimento incremental do módulo
– a cada incremento pode-se retestar integralmente o que já foi
feito (teste de regressão)
• o que não foi alterado ou acrescido deveria continuar operando tal
como esperado
• o que foi alterado e acrescido tem o teste ajustado
• A função de teste específico serve como exemplo de uso do
módulo
Ago 2008 37 / 27Arndt von Staa © LES/DI/PUC-Rio
Quais seriam as vantagens?
• O script de teste serve como especificação executável do
módulo
– apesar de ser uma especificação incompleta e baseada em
exemplos, freqüentemente é mais precisa do que
especificações textuais
• Os problemas encontrados são repetíveis, facilitando a
depuração
– reduz significativamente o esforço de teste quando se leva em
conta a necessidade de reteste após corrigir ou evoluir o
módulo
• Caso a realização do teste gere um arquivo de log
– documenta os laudos dos testes realizados
– assegura a existência da história da evolução durante o teste.
– passa a ser um atestado da qualidade do módulo
Ago 2008 38 / 27Arndt von Staa © LES/DI/PUC-Rio
Quais seriam as vantagens?
• O esforço de redação do módulo de teste específico é pouco
maior do que o esforço de redação de um controlador de
teste manual.
• Através da linguagem de diretivas de teste pode-se realizar
testes tão complexos e detalhados quanto se queira.
– casos de teste selecionados segundo critérios de seleção bem
definidos
– redigir as diretivas de teste requer monos esforço do que
redigir um controlador de teste contendo o roteiro
• Permite documentar os casos de teste
– os títulos ( “==” ) informam a intençao do caso de teste
– necessário para que outros possam mantê-los
Ago 2008 39 / 27Arndt von Staa © LES/DI/PUC-Rio
Quais seriam as vantagens?
• Permite estabelecer com precisão onde o debugger deve ser
ativado.
– permite longos processamentos necessário para estabelecer o
contexto
– comando de teste genérico: =breakpoint
• Pode ser combinado com funções de teste complexas
disparadas por diretivas
• Reduz o estresse do desenvolvedor
– é possível particionar o desenvolvimento em etapas –
desenvolvimento incremental
– cada qual culminando com um módulo parcial porém
corretamente implementado
Ago 2008 40 / 27Arndt von Staa © LES/DI/PUC-Rio
E quais seriam as desvantagens?
• O arquivo de diretivas é na realidade um programa
– pode conter defeitos
– se não tomar cuidado, a linguagem e/ou o script de teste
tornam-se extensos e complicados
• Ao encontrar uma falha é necessário determinar se é
– defeito no módulo em teste
– defeito no módulo de teste específico
– defeito no script de teste
– defeito na especificação do módulo
• A linguagem ad hoc utilizada aqui não permite a redação de
subprogramas (por enquanto )
– subprogramas podem ser codificados no módulo de teste
específico e disparados por um comando.
Ago 2008 41 / 27Arndt von Staa © LES/DI/PUC-Rio
E quais seriam as desvantagens?
• Custo inicial maior
– ganha-se ao retestar, ou seja, sempre
• Ao alterar um módulo, obriga a evoluir coevolução
– obviamente o módulo sob teste
– o módulo de teste específico
– o script de teste, isso pode tornar-se um problema
• se os casos de teste forem mal documentados
• se o script de teste for mal organizado
• se o script de teste não utilizar parâmetros simbólicos
• Nem sempre é possível utilizar teste automatizado
– exibição (rendering) de figuras, gráficos
– interfaces com os sistemas GUI (ex. windows)
– leiaute de janelas
– . . .
42 / 30 LES/DI/PUC-Rio
PARA CASA: exemplos práticos de uso do arcabouço
• Teste manual:
– Exemplo \arcabouc\manual: mostrado como construir um módulo
e o respectivo controlador de teste manual
• Teste automatizado:
– Exemplo \arcabouc\simples: mostrado como construir um módulo
e o respectivo módulo de teste específico utilizando a biblioteca
do arcabouço de teste
• Comparar a implementação dos dois
– os exemplos tratam de um módulo editor de árvores binárias
• VIDE VÍDEOS EM “INSTALAÇÃO E USO DO ARCABOUÇO”
Aula 05Teste Automatizado – Parte I e II
Alessandro Garcia
LES/DI/PUC-Rio
Março de 2019