Um Método Automático de Teste Funcional para a
Verificação de Componentes
Daniel Lima Barbosa
Dissertação submetida à Coordenação do Curso de Pós-Graduação em Informática da Universidade Federal de Campina Grande – Campus I como parte dos requisitos necessários para obtenção do grau de Mestre em Informática.
Área de Concentração: Ciência da Computação Linha de Pesquisa: Engenharia de Software
Patricia Duarte de Lima Machado (Orientadora)
Jorge César Abrantes de Figueiredo (Co-Orientador)
Campina Grande, Paraíba, Brasil c
Resumo
O desenvolvimento baseado em componentes vem sendo cada vez mais considerado para a construção de software de alta qualidade com baixo custo e tempo de desenvolvimento. Entretanto, o reuso efetivo de componentes está fortemente relacionado à confiabilidade dos mesmos. A maioria dos processos de desenvolvimento de componentes realiza as atividades de teste de forma ad hoc e apenas no final do desenvolvimento, o que aumenta os custos inerentes à correção dos defeitos detectados. Por outro lado, quando métodos de teste são empregados, estes fazem uso de especificações complexas e/ou construídas especificamente para as atividades de teste, além de nem sempre disporem de ferramentas de suporte para automatizar estas atividades. Neste trabalho, propomos um método que realiza, de forma sistemática, a geração, execução e análise de resultados de testes funcionais para compo-nentes de software a partir de especificações UML e restrições OCL. O método faz uso de um conjunto mínimo de artefatos UML, que constitui o requisito principal para sua apli-cação em conjunto com uma metodologia de desenvolvimento. Uma ferramenta de suporte foi desenvolvida para automatizar suas atividades e um estudo de caso foi realizado para demonstrar a aplicação do método, assim como o funcionamento da ferramenta.
Abstract
Component-based development has been even more used to build high quality software in a cost-effective and reduced-time way. Nevertheless, effective reuse of components is closely related to their reliability. The majority of component development processes execute test activities in an ad hoc way and just at the end of development. This may lead to increased correction costs of detected defects. On the other hand, when test methods are employed, they use complex and/or test-only specifications. Futhermore, few of those methods have support tools to automatize their activities. In this work, we propose a method to generate, execute and analize the results of functional component tests in a systematic way from UML specifications and OCL constraints. The method uses a minimum set of UML artifacts that constitute the main requirements to its application in a development methodology. A support tool has been developed to automatize the activities of the method and a case study was performed to demonstrate its application as well as tool functionality.
Conteúdo
1 Introdução 1 1.1 Objetivos do Trabalho . . . 3 1.2 Metodologia de Trabalho . . . 4 1.3 Resultados e Relevância . . . 4 1.4 Estrutura da Dissertação . . . 6 2 Fundamentação Teórica 8 2.1 Teste de Software . . . 8 2.1.1 Tipos de Teste . . . 9 2.1.2 Terminologia de Teste . . . 10 2.2 Componentes de Software . . . 11 2.2.1 Definição . . . 122.2.2 Vantagens e Desvantagens do Desenvolvimento Baseado em Com-ponentes . . . 13
2.3 Teste de Componentes . . . 14
2.4 O Método FCT . . . 16
2.4.1 Planejamento . . . 17
2.4.2 Especificação dos Testes . . . 17
2.4.3 Construção, Execução e Análise de Resultados . . . 22
2.4.4 Empacotamento . . . 22
2.5 Considerações Finais . . . 23
3 O Método AFCT 24 3.1 Introdução . . . 24
CONTEÚDO iv
3.2 Especificação de Teste . . . 28
3.2.1 Seleção dos Casos de Teste . . . 28
3.2.2 Geração de Oráculos . . . 34
3.2.3 Seleção de Dados de Teste . . . 36
3.3 Construção e Empacotamento dos Artefatos de Teste . . . 39
3.4 Execução dos Testes e Análise de Resultados . . . 41
3.5 Considerações Finais . . . 42 4 A Ferramenta SPACES 43 4.1 Características da Ferramenta . . . 43 4.2 Visão Geral . . . 45 4.3 Projeto . . . 46 4.3.1 Gerenciador de Persistência . . . 50 4.3.2 Parser XMI . . . 51
4.3.3 Seletor de Casos de Teste . . . 53
4.3.4 Gerador de Oráculos . . . 57
4.3.5 Seletor de Dados de Teste . . . 63
4.3.6 Gerador de Artefatos de Teste . . . 67
4.3.7 Empacotador . . . 72
4.4 Testador de Componentes . . . 73
4.5 Considerações Finais . . . 76
5 Estudo de Caso 77 5.1 Descrição da Aplicação e Escolha do Componente . . . 77
5.2 Especificando o Componente . . . 78
5.3 Aplicando o Método AFCT . . . 86
5.3.1 Planejando o Teste . . . 86
5.3.2 Especificando os Testes . . . 87
5.3.3 Construindo e Empacotando os Artefatos de Teste . . . 91
5.3.4 Executando o Teste . . . 93
CONTEÚDO v
6 Conclusão 99
6.1 Contribuições . . . 100
6.2 Trabalhos Futuros . . . 101
A Mapeamento OCL – Java 109 A.1 Declaração de Context . . . 109
A.2 Tipos . . . 110
A.3 Operadores (Numéricos, Relacionais e Lógicos) . . . 110
A.4 Operações sobre Strings . . . 111
A.5 Construções Básicas . . . 111
A.6 Construções Collection . . . 112
B Subconjunto XMI Utilizado 117
Lista de Figuras
2.1 Interfaces de Componentes . . . 13
2.2 Teste de Componentes . . . 15
2.3 Exemplo de Modelo de Uso de Método FCT . . . 19
3.1 Integração entre Metodologias de Desenvolvimento de SBC e o Método de Teste . . . 26
3.2 Algoritmo para a Criação de Modelos de Uso . . . 29
3.3 Formato de Modelo de Uso do Método AFCT . . . 31
3.4 Algoritmo para a Criação de Diagramas de Seqüência a partir de Modelos de Uso . . . 31
3.5 Algoritmo para a Seleção de Caminhos em Modelos de Uso . . . 32
3.6 Exemplo de Conversão de OCL para Pseudo-Código . . . 35
4.1 Arquitetura da Ferramenta SPACES . . . 45
4.2 SPACES: Tela Principal . . . 47
4.3 SPACES: Menus Arquivo e Projeto . . . 47
4.4 SPACES: Abrindo Arquivos XMI . . . 48
4.5 Visão Geral do Projeto da Ferramenta SPACES . . . 49
4.6 SPACES: Tela Salvar Projeto . . . 50
4.7 SPACES: Gerenciador de Persistência . . . 51
4.8 SPACES: Tela de Seleção de Casos de Teste . . . 53
4.9 SPACES: Modelo de Uso . . . 54
4.10 SPACES: Tela de Edição de Probabilidades . . . 55
4.11 SPACES: Probabilidades Alteradas no Modelo de Uso . . . 55
4.12 SPACES: Seletor de Casos de Teste . . . 56
LISTA DE FIGURAS vii
4.13 Dresden OCL: Integração entre os Módulos . . . 58
4.14 Dresden OCL: Exemplo de Árvore Sintática Abstrata . . . 58
4.15 Integração Dresden OCL - SPACES: Acesso ao Modelo UML . . . 59
4.16 Dresden OCL: Gerador de Código . . . 60
4.17 SPACES: Gerador de Oráculos . . . 62
4.18 SPACES: Seletor de Dados . . . 64
4.19 SPACES: Gerador de Artefatos de Teste . . . 67
4.20 SPACES: Hierarquia do Código de Teste . . . 68
4.21 SPACES: Formato de Métodos de Teste para Cenários . . . 69
4.22 JTestCase: Formato de Arquivos XML . . . 70
4.23 SPACES: Formato XML para Descrição do Código de Teste Gerado . . . . 71
4.24 SPACES: Empacotador . . . 72
4.25 Testador de Componentes: Tela Principal . . . 74
4.26 Testador de Componentes: Tela de Alteração de Dados . . . 75
5.1 Diagrama de Componentes do Sistema de Reservas . . . 78
5.2 Diagrama de Funcionalidades para o Componente Gerenciador de Hotéis . 79 5.3 Modelo Conceitual do Componente Gerenciador de Hotéis . . . . 80
5.4 Modelo de Informação do Componente Gerenciador de Hotéis . . . . 80
5.5 Diagrama que descreve o cenário de uso Hotel Inexistente da funcionalidade Fazer Reserva . . . . 82
5.6 Diagrama que descreve o cenário de uso Tipo de Quarto Inexistente da fun-cionalidade Fazer Reserva . . . . 82
5.7 Diagrama que descreve o cenário de uso Período Inválido da funcionalidade Fazer Reserva . . . . 83
5.8 Diagrama que descreve o cenário de uso Quarto Não Disponível da funcio-nalidade Fazer Reserva . . . . 84
5.9 Diagrama que descreve o cenário de uso Sucesso da funcionalidade Fazer Reserva . . . . 85
5.10 Selecionando os casos de teste com SPACES . . . 87
LISTA DE FIGURAS viii
5.12 Modelo de Uso para a Funcionalidade Cancelar Reserva . . . 89
5.13 Oráculo correspondente à pré-condição do cenário Período Inválido da Fun-cionalidade Fazer Reserva . . . . 90
5.14 Oráculo correspondente à pós-condição do cenário Período Inválido da Fun-cionalidade Fazer Reserva . . . . 91
5.15 Dados selecionados para o cenário Tipo de Quarto Inexistente da funciona-lidade Fazer Reserva . . . . 92
5.16 Hierarquia de Classes de Teste geradas para o Componente Gerenciador de Hoteis . . . . 93
5.17 Execução dos Testes: Detecção de Falhas . . . 94
5.18 Execução dos Testes: Revisando os Dados de Teste . . . 95
5.19 Execução dos Testes: Testes Ok . . . 96
C.1 API UML: Visão Geral . . . 128
C.2 API UML: Hierarquia de Tipo . . . 129
C.3 API UML: Associação . . . 130
C.4 API UML: Generalização e Abstração . . . 130
C.5 API UML: Colaboração e Diagrama de Seqüência . . . 131
C.6 API UML: Hierarquia de Ações . . . 132
Lista de Tabelas
2.1 Exemplo de Tabela de Decisão do Método FCT . . . 21
3.1 Esquema de Conversão de Restrições OCL para Código Fonte . . . 35
3.2 Padrões Lógicos para Identificação de Partições de Equivalência . . . 37
3.3 Frameworks de Teste xUnit . . . . 40
5.1 Cenários de Uso das Funcionalidades do Componente Gerenciador de Hotéis 81 5.2 Determinando o número de casos de teste a ser realizados para cada funcio-nalidade . . . 86
A.1 Mapeamento OCL – Java: Declaração de Context . . . 109
A.2 Mapeamento OCL – Java: Tipos . . . 110
A.3 Mapeamento OCL – Java: Operadores . . . 111
A.4 Mapeamento OCL – Java: Operações sobre Strings . . . 111
A.5 Mapeamento OCL – Java: Construções Básicas . . . 111
A.6 Mapeamento OCL – Java: Construções Collection . . . 116
Capítulo 1
Introdução
O desenvolvimento de produtos de qualidade é hoje o maior desafio da indústria de soft-ware. Para tanto, processos de teste bem planejados e executados são necessários de forma a garantir que o produto final do desenvolvimento seja o mais livre de defeitos possível. Entre-tanto, como observado por McGregor [MS01], a concepção tradicional do processo de teste como sendo uma fase final e independente do processo de desenvolvimento propriamente dito, tem se mostrado muito ineficiente no que diz respeito ao custo necessário tanto para reparar os erros encontrados quanto com a própria manutenção do software.
Neste contexto, o teste funcional surge como uma técnica capaz de amenizar os cus-tos inerentes ao processo de teste, uma vez que, a partir da especificação do software, ca-sos de teste podem ser obtidos paralelamente ao seu desenvolvimento. Em função disto, muitas pesquisas vêm sendo desenvolvidas com o objetivo de produzir técnicas efetivas para a derivação de casos de teste a partir da especificação dos sistemas. Entretanto, isso só é possível se essa especificação for precisamente definida, caracterizando com exatidão o comportamento desejado para o sistema [Bei95]. Uma forma de se alcançar essa precisão é a partir da utilização de especificações formais.
Por outro lado, o desenvolvimento de especificações complexas e específicas para propósitos de teste só é justificado para projetos de alta complexidade e risco, como, por exemplo, sistemas de tempo real, em função do overhead (número de atividades extras) necessário para se gerar esses artefatos. Dessa forma, a utilização de especificações UML (Unified Modeling Language), tradicionalmente empregadas nas fases de análise e pro-jeto dos processos de desenvolvimento de software, como fonte de informações relevantes
2
para teste, aparece como uma alternativa altamente atraente uma vez que não se faz mais necessário a criação de artefatos específicos para propósitos de teste [BL01; OA99; FL00;
VDR00; RPG01; CCD+02]. Entretanto, é preciso, de alguma forma, compensar a falta
de formalidade das especificações UML para que estas possam ser utilizadas na geração automática de casos de teste. Uma forma interessante de resolver esse problema é enrique-cendo os artefatos UML com restrições OCL (Object Constraint Language), provendo, desta forma, informações relacionadas a invariantes, pré e pós-condições e outros comportamen-tos mais específicos. Embora o OCL esteja cada vez mais presente nas especificações UML, ainda não existem muitas pesquisas em cima de sua utilização como fator formalizador de especificações UML para propósitos de teste.
Um ponto de convergência entre os pesquisadores e especialistas da área é a necessidade de automação destas técnicas de forma que todo o processo de teste - geração, execução e análise de resultados - possa ser executado (e re-executado) com a menor interferência hu-mana possível. Entretanto, poucos são os trabalhos que apresentam ferramentas realmente em produção [TB02; FJJV97]. A grande maioria destas ferramentas é composta por protóti-pos que nunca vêm a ser finalizados. Além disso, as poucas ferramentas em produção não abrangem todo o processo de teste - muitas realizam apenas a geração dos casos de teste - e a maioria delas faz uso de especificações mais complexas e menos utilizadas do que o UML, a exemplo da ferramenta apresentada por Tretmans [TB02].
Aliada a esta busca por processos de teste mais eficientes, a composição de sistemas a partir de componentes, em função de sua alta capacidade de reuso, também desponta como um fator de determinação de software de alta qualidade, rápida construção e baixo custo. Para tanto os componentes precisam ser testados de forma a garantir a sua confiabilidade e, conseqüentemente, a confiabilidade do sistema como um todo.
Do ponto de vista de um componente, os testes a serem realizados precisam satisfazer duas expectativas distintas. Do lado do fornecedor, é preciso garantir que o componente se comportará adequadamente sob os mais diferentes contextos em que venha a ser utilizado. Do lado do cliente, é necessário assegurar que o componente apresente a funcionalidade desejada quando integrado aos demais componentes para compor uma aplicação. Para que isso seja possível, é necessário que os componentes apresentem interfaces bem especificadas. Em função disto, métodos para a verificação funcional de componentes e da integração
1.1 Objetivos do Trabalho 3
destes na composição de sistemas vem sendo propostos [dF03; MTY01; GMF04; CWO03]. Em particular, em [dF03], Farias propõe o método FCT, um método de teste funcional aplicável a componentes de software. A principal contribuição desse trabalho está em pos-sibilitar a verificação de propriedades individuais dos componentes e empacotar artefatos e resultados de teste de forma adequada, facilitando o reuso e a composição dos mesmos pelos clientes. Esta abordagem é complementada por Gouveia [GMF04] que apresenta um método de teste de integração funcional para sistemas baseados em componentes com a finalidade de testar o componente dentro do contexto onde ele será inserido, isto é, testar a forma como ele irá interagir com a aplicação e com os demais componentes que irão compor o sistema.
Entretanto, embora cubram todo o processo de teste, estes métodos não possuem fer-ramentas de suporte e, em função de definições incompletas e/ou informais, apresentam atividades bastante subjetivas e de difícil automação, o que constitui um obstáculo para a construção destas ferramentas. Dessa forma, todas as suas etapas precisam ser manualmente executadas. Com isso, até mesmo aplicações simples demandam um grande esforço por parte das equipes de desenvolvimento em atividades de teste, tornando-se, assim, um pro-cesso bastante oneroso. A redefinição destas atividades e o desenvolvimento de ferramentas de suporte é, portanto, fundamental para garantir a viabilidade destes métodos em aplicações práticas.
1.1
Objetivos do Trabalho
O objetivo maior deste trabalho é a definição de um novo método de teste funcional para componentes de software cujas atividades sejam precisamente definidas de forma a possi-bilitar o desenvolvimento de um ferramental de suporte que realize, de forma automática, a geração, execução e análise de resultados de casos de testes. Esse método, que é forte-mente baseado no método FCT [dF03], cobrirá inicialforte-mente apenas a perspectiva de teste do fornecedor e não estará limitado a uma única metodologia de desenvolvimento.
O ferramental de suporte a ser desenvolvido deverá apresentar, dentre outras, as seguintes características:
1.2 Metodologia de Trabalho 4
• Utilização de artefatos produzidos com a especificação UML durante as fases de
análise e projeto dos processos de desenvolvimento de software.
• Geração, implementação, execução e análise de resultado de casos de teste para os
componentes.
• Empacotamento dos casos de teste e dados experimentais gerados para o componente
de forma a permitir a sua distribuição e a re-execução dos testes pelos usuários do mesmo.
1.2
Metodologia de Trabalho
Com o intuito de se atingir o objetivo proposto para este trabalho, inicialmente foi feita uma análise crítica do estado da arte em automação de teste funcional para software orientado a objetos e/ou baseado em componentes. Em seguida fundamentamos o trabalho no estudo das linguagens UML e OCL, das principais técnicas empregadas no teste de componentes de software, e das atividades e técnicas empregadas no método FCT [dF03]. A partir das conclusões tomadas e das metas traçadas, realizamos a definição de cada uma das atividades e etapas do novo método de teste assim como o desenvolvimento do ferramental de suporte. Por fim, com o objetivo de guiar e fornecer feedbacks para estas definições, construímos a especificação e implementação de um componente a ser usado como estudo de caso.
1.3
Resultados e Relevância
O principal resultado deste trabalho é a disponibilização de um método para a geração, execução e análise de resultados de casos de teste para componentes de software de forma automática e a partir de especificações UML e restrições OCL das fases de análise e projeto dos processos de desenvolvimento.
No ciclo de desenvolvimento de software, o teste é, sem sombra de dúvidas, a ativi-dade em que podemos observar a maior distância entre a teoria (métodos e técnicas de teste propostas) e a prática (real aplicação destas técnicas). Uma das principais causas disso é o impacto causado pela adoção destas propostas, seja em função do overhead necessário à
1.3 Resultados e Relevância 5
sua utilização, ou devido ao alto grau de complexidade e/ou formalismo das especificações necessárias.
Ao contrário do que ocorre com outras propostas, o método faz uso de uma especificação usual e de grande utilização pela indústria, e os artefatos necessários já são comumente de-senvolvidos pelas principais metodologias de desenvolvimento, sendo desnecessária a cons-trução de artefatos específicos para propósitos de teste, fatores esses que facilitam a sua adoção.
Estudos mostram que cerca de 50% do custo do desenvolvimento de um software é desti-nado à atividade de teste [Har00]. Com o uso do método, esses custos podem vim a ser dras-ticamente reduzidos, uma vez que, faltas e/ou inconsistências na especificação podem ser detectadas nas primeiras etapas dos processos de desenvolvimento, tornando mais simples e barata a sua correção e evitando a sua propagação para as etapas futuras do desenvolvimento. Outra contribuição do trabalho é o desenvolvimento e disponibilização do ferramental de suporte necessário à automação das atividades do método de teste. Apesar de algumas ferramentas desenvolvidas para teste funcional de sistemas orientados a objetos poderem ser aplicadas também ao teste de componentes, muitas se baseiam em artefatos produzidos ex-clusivamente para propósitos de teste e/ou em especificações pouco usuais [PBB97; Gra94; TB02]. Além disso, nenhuma dessas ferramentas dá enfoque às características específicas dos componentes de software, a exemplo da verificação de propriedades e da forma como estes interagem entre si. Com tais ferramentas de suporte, o esforço empregado no processo de teste dos componentes será minimizado, o que viabiliza a sua utilização em sistemas com-plexos e/ou de grande porte. Além disso, a diminuição do número de atividades manuais re-duz a chance de inserção de defeitos durante a execução do processo de teste, maximizando assim a confiabilidade dos resultados obtidos e, conseqüentemente, a qualidade do produto final do desenvolvimento.
Por fim, o trabalho contribuirá com um estudo de caso a fim de demonstrar a aplicação do método de teste e da ferramenta de suporte.
1.4 Estrutura da Dissertação 6
1.4
Estrutura da Dissertação
O restante deste documento foi organizado da seguinte forma:
Capítulo 2: Fundamentação Teórica Na fundamentação teórica, apresentaremos os
prin-cipais conceitos e terminologias relativos a teste e componentes de software, que foram abordados neste trabalho. Por fim, será apresentado o método FCT, no qual este trabalho está baseado, a partir da descrição de suas fases e atividades.
Capítulo 3: O Método de Teste AFCT Neste capítulo, será apresentado o método de
teste proposto. Serão enumerados os requisitos para a sua aplicação e serão descritas as suas etapas e atividades assim como os algoritmos e técnicas utilizadas na sua definição.
Capítulo 4: A Ferramenta SPACES A ferramenta SPACES será apresentada neste
capí-tulo. A ferramenta foi construída para dar suporte às atividades do método AFCT. Será feita uma descrição da sua arquitetura e, para cada módulo, serão apresentadas as funcionalidades e os principais algoritmos.
Capítulo 5: Estudo de Caso Neste capítulo será demonstrada uma aplicação prática do
método AFCT e da ferramenta SPACES. Um componente será selecionado para ser especi-ficado, implementado e testado segundo as diretrizes do método de teste proposto e com o auxílio da ferramenta. Ao final, os resultados obtidos serão analisados.
Capítulo 6: Conclusões O trabalho será concluído neste capítulo a partir de uma avaliação
dos resultados obtidos assim como uma descrição das perspectivas de trabalhos futuros.
Apêndice A No Apêndice A, é apresentado o mapeamento proposto da linguagem OCL
para um sub-conjunto da linguagem de programação Java. Este mapeamento foi utilizado para a geração dos oráculos de teste sob a forma de código-fonte Java a partir das restrições OCL.
1.4 Estrutura da Dissertação 7
Apêndice B No Apêndice B, apresentamos um DTD que descreve o subconjunto dos
el-ementos XMI considerados para a obtenção de informações do modelo UML a partir da ferramenta SPACES.
Apêndice C No Apêndice C, são apresentados os diagramas que descrevem as classes
componentes da API UML, que é instanciada a partir do arquivo XMI para disponibilizar as informações do modelo UML da especificação dos componentes para a ferramenta SPACES.
Capítulo 2
Fundamentação Teórica
Este capítulo tem como objetivo principal fornecer um suporte técnico para os leitores com relação aos conceitos empregados neste trabalho. Serão apresentados os principais conceitos relacionados a teste, componentes e teste de componentes. O método FCT, no qual este trabalho está baseado, também será apresentado neste capítulo a partir da descrição de suas fases e atividades.
2.1
Teste de Software
À medida que cresce a utilização de sistemas de software na execução de tarefas críticas, cresce também a exigência de produtos cada vez mais qualificados. Teste é uma das técni-cas de verificação de software mais utilizadas na prática. Se usado de forma efetiva, pode fornecer indicadores importantes sobre a qualidade e confiabilidade de um produto.
O teste de software é uma atividade que consiste na descoberta de defeitos que possam ter sido introduzidos em qualquer fase do desenvolvimento ou manutenção de sistemas de software e que, em geral, são decorrentes de omissões, inconsistências ou mau entendimento das especificações ou dos requisitos dos sistemas [MS01]. Trata-se de um processo de difícil execução para o qual, raramente, os recursos necessários estão disponíveis.
O teste é importante porque contribui substancialmente para certificarmos que uma apli-cação faz tudo o que era esperado que fizesse, embora existam também alguns esforços que vão além disso, e tentam assegurar que as aplicações não fazem nada além do especificado. De qualquer forma, eles fornecem meios de avaliar a existência de defeitos que poderiam
2.1 Teste de Software 9
resultar em perda de tempo, clientes ou até mesmo, no caso de sistemas críticos, de vidas. Embora o principal objetivo do teste seja a detecção de defeitos, existem modalidades de teste mais específicas que visam a certificação de alguns requisitos não funcionais das aplicações, a exemplo dos testes de segurança, de stress e de performance.
Por muito tempo o teste de software foi definido dentro dos processos de desenvolvi-mento como uma atividade isolada e que só era realizada, de maneira informal, após a im-plementação dos sistemas. Entretanto, o alto custo atribuído a esta etapa – cerca de 50% do custo total de desenvolvimento, segundo alguns estudos – demonstrou que essa não era uma prática adequada. Isso contribuiu para a definição de métodos e técnicas sistemáticas de teste que constituíssem um processo a parte que pudesse ser aplicado, paralelamente, ao longo do processo de desenvolvimento [MS01]. Dessa forma, as atividades de teste poderiam ser aplicadas cada vez mais cedo, contribuindo para a verificação não apenas do código de-senvolvido, mas também para a descoberta de faltas nos próprios artefatos produzidos pelas etapas do processo de desenvolvimento, evitando, assim, a sua propagação para as etapas subseqüentes e reduzindo drasticamente o esforço e o custo decorrente de sua correção.
2.1.1
Tipos de Teste
Segundo Beizer [Bei95], três estratégias podem ser utilizadas para se realizar teste de software com o objetivo de detectar defeitos:
• Teste Funcional: Observa se um programa está de acordo com a sua especificação,
independentemente do código fonte. Teste funcional é também conhecido como teste black-box ou teste comportamental.
• Teste Estrutural: Baseia-se na estrutura do objeto testado. Requer, portanto, acesso
completo ao código fonte do programa para ser realizado. É também conhecido por teste glass-box ou teste white-box.
• Teste Híbrido: Combina as duas estratégias de teste anteriores.
Os testes funcional e estrutural são técnicas complementares. Alguns defeitos são tão relacionados à forma como uma determinada funcionalidade foi implementada que só po-dem ser revelados a partir do desenvolvimento de testes baseado na implementação desta
2.1 Teste de Software 10
funcionalidade, e portanto, estruturais. Por outro lado, a verificação da conformidade de um funcionalidade para com sua especificação através das técnicas funcionais, nos permite um controle maior sobre o que o software faz, possibilitando a detecção de defeitos mais con-ceituais e menos dependentes da implementação em si, a exemplo de inconsistência entre modelos.
Segundo J. Chang et. al. [CR99], teste funcional é freqüentemente mais fácil de com-preender e realizar do que as técnicas de teste tradicionais baseadas em código, porque o teste funcional baseia-se em uma descrição “black-box” da funcionalidade do software, enquanto que as técnicas de teste estrutural exigem um conhecimento mais detalhado da implemen-tação. Neste trabalho, nos concentraremos apenas nas técnicas de teste funcional, estando as técnicas estruturais fora de seu escopo.
Uma vez que são baseadas na especificação do software, as técnicas de teste funcional podem ser aplicadas antes mesmo da disponibilização da implementação, contribuindo para o aperfeiçoamento dos próprios artefatos de desenvolvimento. Além disso, quando as es-pecificações são construídas a partir de linguagens com semântica formal ou semi-formal, o teste funcional pode ser realizado de forma parcial ou completamente automática, mini-mizando o esforço necessário à sua aplicação assim como o risco de inserção de erros durante a execução manual de suas atividades.
Em geral, os testes funcionais compreendem três componentes básicos: Casos de Teste, Oráculos e Dados de Teste. Estes e outros conceitos relacionados à atividade de teste são apresentados na seção 2.1.2.
2.1.2
Terminologia de Teste
Teste de software apresenta um vocabulário bem estabelecido. A seguir, são listados alguns dos termos mais comumente usados [Bei95; Bin99]:
• Falha: É a manifestação do software em não executar de forma correta uma
determi-nada funcionalidade. Em função disso, o sistema em execução apresenta algum tipo de comportamento não esperado, ou seja, a saída produzida não coincide com o resultado previsto.
2.2 Componentes de Software 11
código ou a presença de código incorreto no sistema, o que pode ocasionar uma falha no mesmo.
• Erro: É a ação humana que dá origem a uma falta.
• Caso de Teste: Aspecto ou funcionalidade a ser testado em um sistema, cujo
compor-tamento esperado é expresso por critérios de entrada e aceitação.
• Dados de Teste: Valores de entrada necessários para a execução de um caso de teste. • Oráculo: Especificação de procedimentos responsáveis por verificar se uma dada
exe-cução do sistema (estimulada pelos dados de entrada) está de acordo com o comporta-mento definido pelos casos de teste.
• Validação: Processo que avalia se a especificação de um objeto está de acordo com o
domínio do problema. Este processo assegura que o software produzido é o software correto.
• Verificação: Processo que avalia se a implementação de um objeto satisfaz os
requi-sitos declarados em sua especificação. Assume-se neste processo que a especificação está correta. Este processo assegura que o software está sendo produzido corretamente.
• Testabilidade: Propriedade fundamental que engloba todos os aspectos que facilitam
a elaboração e execução dos testes do software.
2.2
Componentes de Software
Na indústria de software, requisitos relativos a menores custos de produção e manutenção, maior rapidez na entrega de sistemas e aumento da qualidade estão cada vez mais presentes, e a sua satisfação é determinante para o sucesso dos produtos em de-senvolvimento assim como para a sobrevivência das próprias organizações em um
mer-cado cada vez mais competitivo. Entre os pesquisadores, é consenso que tais
requisi-tos só podem ser atendidos pelo reuso generalizado e sistemático de software [Som03; Szy98]. O desenvolvimento de software baseado em componentes é uma das técnicas de reuso que vem apresentando melhores resultados na prática [Som03].
2.2 Componentes de Software 12
A engenharia de software baseada em componentes propõe a concepção de sistemas a partir da combinação de unidades pré-desenvolvidas com o objetivo de reduzir o tempo de desenvolvimento, facilitar o acoplamento de novas funcionalidades, ou a mudança de funcionalidades já existentes e ainda promover o reuso de partes do software. Contudo, embora tenha havido interesse no reuso de componentes desde o início da década de 80, foi somente nos últimos anos que ele se tornou aceito como uma abordagem prática para o desenvolvimento de sistemas de software [Som03].
2.2.1
Definição
Na engenharia de software, o termo Componente possui diversas definições. Uma das definições mais usadas e a que consideramos neste trabalho é a de C. Szyperski [Szy98]:
"Um componente de software é uma unidade de composição com interfaces especificadas através de contratos e dependências de contexto explícitas, que pode ser distribuída independentemente e está sujeita a composição com outras partes."
Dessa definição podemos concluir que o componente é uma unidade auto-suficiente de um sistema, que pode ser desenvolvida de forma isolada e que é projetada para utilização em diferentes contextos de um determinado domínio de aplicação. A definição também indica que um componente encapsula suas características, estando portanto bem separado de seu ambiente e de outros componentes. Sua interação com o mundo exterior se dá através de especificações bem definidas acerca do que o componente requer para o seu correto funcio-namento e de quais os serviços por ele fornecidos.
Estas especificações são as interfaces que constituem o contrato de utilização do mesmo (Figura 2.1). A interface requires (requer) especifica quais os serviços que devem ser forneci-dos pelo sistema ou, eventualmente, por outros componentes, para possibilitar o correto fun-cionamento do componente. A interface provides (fornece), por sua vez, define os serviços fornecidos pelo componente.
2.2 Componentes de Software 13
Figura 2.1: Interfaces de Componentes
2.2.2
Vantagens e Desvantagens do Desenvolvimento Baseado em
Com-ponentes
O desenvolvimento de sistemas baseados em componentes apresenta uma série de vanta-gens em relação às técnicas de desenvolvimento tradicionais [Som03]:
• Maior confiabilidade. Os componentes reutilizados que são empregados nos sistemas
em operação são, em geral, mais confiáveis do que os componentes novos. Eles já foram experimentados e testados em diferentes ambientes. Os defeitos de projeto e im-plementação são descobertos e eliminados no uso inicial dos componentes, reduzindo, assim, o número de falhas quando o componente é reutilizado.
• Redução dos riscos de processo. Se recorrermos a um componente já existente, serão
menores as incertezas sobre os custos relacionados ao reuso desse componente do que sobre custos de desenvolvimento. Esse é um fator importante para o gerenciamento de projetos, pois reduz as incertezas nas estimativas de custos de projeto.
• Uso efetivo de especialistas. Em vez de os especialistas em aplicações fazerem o
mesmo trabalho em diferentes projetos, eles podem desenvolver componentes reuti-lizáveis, que englobam seu conhecimento.
• Desenvolvimento acelerado. De modo geral, é mais importante fornecer um
sis-tema para o mercado o mais rápido possível do que se prender aos custos gerais de desenvolvimento. O reuso de componentes acelera a produção, porque o tempo de desenvolvimento e o de validação devem ser reduzidos.
Apesar das vantagens apresentadas, algumas desvantagens e/ou problemas também pre-cisam ser consideradas durante o projeto de sistemas baseados em componentes. Algumas
2.3 Teste de Componentes 14
destas desvantagens, que estão relacionadas aos requisitos para implantação do desenvolvi-mento baseados em componentes em si e à mudança de culturas organizacionais, são listadas a seguir [Som03].
• Aumento nos custos de manutenção. Se o código-fonte do componente não estiver
disponível, então os custos de manutenção poderão aumentar, uma vez que os ele-mentos reutilizados no sistema podem se tornar crescentemente incompatíveis com as mudanças do sistema.
• Síndrome do ‘não-foi-inventado-aqui’. Alguns engenheiros de software às vezes
preferem reescrever componentes porque acreditam que podem fazer melhor que o componente reutilizável. Isso tem a ver em parte com a confiança e em parte com o fato de que escrever um software original é visto como mais desafiador do que reutilizar o software de outras pessoas.
• Manutenção de uma biblioteca de componentes. Implementar uma biblioteca de
componentes e assegurar que os desenvolvedores de software utilizem essa biblioteca pode ser dispendioso, uma vez que as técnicas atuais de classificação, catalogação e recuperação de componentes de software ainda são imaturas.
• Encontrar e adaptar componentes reutilizáveis. Os componentes de software
de-vem ser encontrados em uma biblioteca, compreendidos e, algumas vezes, adaptados, a fim de trabalharem em um novo ambiente. Os engenheiros precisam ter uma razoável certeza de poder encontrar um componente em uma biblioteca, antes de incluírem a rotina de busca de componentes como parte de seu processo normal de desenvolvi-mento.
2.3
Teste de Componentes
A qualidade dos sistemas construídos a partir da composição de componentes assim como o reuso efetivo destes estão intimamente relacionados a sua confiabilidade. O teste é, na prática, uma das técnicas mais empregadas para se avaliar a qualidade e a confiabili-dade dos componentes.
2.3 Teste de Componentes 15
De acordo com Harrold [Har00], o teste de componentes pode ser visto sob duas perspec-tivas: a perspectiva do fornecedor do componente e a perspectiva do cliente do componente. O fornecedor vê o componente independentemente do contexto em que ele será utilizado e deve portanto, testar todas as configurações do componente de uma forma livre de contexto. Já para o cliente, o componente estará inserido no contexto de uma aplicação, interagindo com outros componentes para compor uma funcionalidade maior. Dessa forma, é mais in-teressante testar os aspectos do componente que são relevantes no contexto específico da aplicação, assim como a correta interação entre os componentes que a compõem.
Na Figura 2.2, vemos como se dá o teste de um sistema baseado em componentes através dessas perspectivas. O fornecedor dos componentes ‘A’, ‘B’ e ‘C’ precisa assegurar a sua funcionalidade como unidade funcional, verificando se o mesmo se comporta adequada-mente sob as características comuns ao domínio de aplicações para o qual foi projetado. O cliente, que está usando os componentes para compor o sistema ‘ABC’, precisa averiguar o correto funcionamento dos componentes neste contexto específico, o que implica na verifi-cação das interações estabelecidas entre eles.
<< component >> << component >>
Sistema ABC
<< component >>B
C A
Figura 2.2: Teste de Componentes
O teste dos componentes sob duas perspectivas distintas é fundamental para que esta atividade seja realizada com sucesso. Embora estas perspectivas tenham como objetivo a descoberta de diferentes classes de defeitos, uma falta não descoberto na funcionalidade de um componente durante o teste na perspectiva do fornecedor pode resultar em falhas na integração deste com outros componentes dentro de uma aplicação, contribuindo para o fun-cionamento incorreto dos demais componentes, assim como da aplicação como um todo. Por outro lado, a(s) funcionalidade(s) obtida(s) a partir da integração de componentes
correta-2.4 O Método FCT 16
mente testados de forma individual pode(m) apresentar falhas decorrentes desta integração, e que, portanto, só podem ser identificadas a partir do teste na perspectiva do cliente.
2.4
O Método FCT
Este trabalho é baseado no método FCT (Functional Component Testing), proposto por Farias em [dF03]. Este método tem como objetivo principal possibilitar o teste funcional das propriedades individuais de componentes de software, sob a perspectiva do fornecedor, a partir de especificações UML e restrições OCL. Para tanto, o método fornece um conjunto de diretrizes e técnicas que auxiliam a execução e o acompanhamento de cada uma das etapas do processo de teste: planejamento, especificação, construção, execução e análise de resultados, além de uma etapa extra que compreende o empacotamento dos artefatos de teste gerados para o componente para possibilitar a distribuição destes e favorecer o desenvolvimento de novos testes na perspectiva do cliente.
O método, cujas atividades estão integradas à metodologia UML Components [CD01] de desenvolvimento de sistemas baseados em componentes, faz uso dos seguintes artefatos UML:
• Diagrama de Classe, onde são descritos, de forma estrutural, o modelo conceitual, as
classes do sistema assim como as associações entre elas.
• Restrições OCL que especificam os contratos (pré e pós-condições) das operações das
interfaces do componente.
• Diagramas de Casos de Uso que representam as funcionalidades do componente. • Diagramas de Seqüência que especificam os cenários de uso das funcionalidades do
componente.
A seguir, vemos uma descrição sucinta das fases e atividades que constituem o método FCT.
2.4 O Método FCT 17
2.4.1
Planejamento
A etapa de planejamento de teste do método FCT é iniciada logo após a análise de requi-sitos do componente e consiste na tomada de decisões relativas à atividade de teste a partir dos artefatos produzidos pela análise de requisitos e dos recursos disponíveis. Estas decisões se concentram em que partes do componente serão testadas e no quanto cada parte deverá ser testada. Como o teste exaustivo nem sempre é passível de ser realizado frente aos recursos disponíveis, é necessário aplicar uma técnica sistemática que ajude a selecionar um subcon-junto das funcionalidades. Para tanto, é proposta a utilização da técnica de análise de riscos discutida em [MS01].
O objetivo principal da análise de risco é identificar o risco que cada caso de uso oferece à conclusão do projeto. A análise de risco envolve três tarefas: identificar os riscos relativo a cada caso de uso, quantificar estes riscos e produzir uma lista dos casos de uso, ordenada pelo grau de risco.
A quantificação dos riscos pode variar de um projeto para outro. Ela deve ter níveis suficientes para separar os casos de uso em grupos de tamanho razoável. De uma maneira geral, podem ser considerados 3 graus de risco: baixo, médio e alto. A idéia é que os casos de uso que se encaixam no grau mais alto de risco recebam uma atenção especial e, conseqüentemente, um maior número de casos de teste.
Ao final desta fase, o plano de teste do componente deverá estar concluído, indicando quantos casos de teste deverão ser desenvolvidos para cada caso de uso.
2.4.2
Especificação dos Testes
A especificação dos testes é a fase do método FCT em que são gerados os modelos de teste a partir dos quais são derivados os casos de teste, oráculos e dados de teste. Os mode-los de teste são gerados a partir da especificação do componente, que contém informações importantes sobre as funcionalidades a ser fornecidas pelo mesmo. Esta fase é composta por três etapas: Seleção de Casos de Teste, Geração de Oráculos e Seleção de Dados de Teste.
2.4 O Método FCT 18
Seleção de Casos de Teste
Nesta etapa são selecionados os casos de teste a ser desenvolvidos para o componente. Como dito anteriormente, o teste exaustivo é quase sempre impossível de ser realizado frente aos recursos disponíveis. Isto leva à necessidade de selecionar um subconjunto dos casos de teste possíveis que irão ser efetivamente desenvolvidos e testados. A análise de riscos rea-lizada durante o planejamento diz quantos cenários de cada funcionalidade do componente serão testados, mas não indica quais são esses cenários. Para preceder esta seleção, é us-ada uma combinação de duas técnicas de teste existentes, TOTEM (Testing Object-orienTed systEms with the unified Modeling language) [BL01] e Cleanroom [PTLP99].
A técnica TOTEM propõe que os diagramas de seqüência que descrevem os cenários de uso de uma funcionalidade, sejam expressos como expressões regulares, uma forma mais compacta e analisável dos mesmos. O alfabeto dessas expressões são os métodos públicos dos objetos presentes nos diagramas. As expressões são então constituídas de termos que
apresentam o formato OperacaoClasse, denotando qual é a operação que está sendo executada
e a que classe esta operação pertence. O método FCT faz uso da técnica TOTEM para derivar expressões regulares a partir de cada cenário de uso das funcionalidades do componente e, a partir da disjunção das expressões dos cenários, obtém uma única expressão regular para cada funcionalidade. Assim, a expressão regular para as funcionalidades segue o formato: <Exp_Cenario_1> ∨ <Exp_Cenario_2> ∨ ... ∨ <Exp_Cenario_n>.
De posse das expressões regulares das funcionalidades do componente, é feita a apli-cação da técnica Cleanroom. Esta técnica de teste propõe a construção de um modelo de uso do sistema que represente todos os possíveis usos do mesmo e suas probabilidades de ocorrência. Este modelo é expresso normalmente por meio de um grafo direcionado, onde um conjunto de estados são conectados através de arcos de transição. Cada arco representa um estímulo para o sistema, que o faz mudar de estado, e possui um valor de probabilidade associado. Os casos de teste são gerados percorrendo-se o modelo, partindo-se do seu estado inicial até o estado final e a seqüência de estímulos que leva o sistema do seu estado inicial ao estado final, através de um determinado caminho no modelo, é definida baseando-se nas probabilidades das transições.
O método FCT determina que seja construído um modelo de uso para cada expressão regular e, conseqüentemente, para cada funcionalidade do componente, de forma que cada
2.4 O Método FCT 19
estímulo seja correspondente a um termo e cada caminho corresponda exatamente a uma cláusula da expressão regular da funcionalidade. As probabilidades do modelo de uso são atribuídas a partir da análise de risco realizada na etapa de planejamento. Na Figura 2.3, é apresentado um exemplo de modelo de uso gerado para uma funcionalidade denominada Fazer Reserva. Como pode ser visto, o modelo de uso possui cinco caminhos distintos, o que indica que a funcionalidade em questão possui cinco cenários de uso.
2 0 1 3 (Gerenciador de Hoteis.getHotel, 1) (Gerenciador de Hoteis.fazerReserva, 1) 4 5 6 7 8 9 (Hotel inexistente, 0.1) (Hotel.fazerReserva, 0.9) (Hotel.getTipoQuarto, 1)
(Tipo quarto inexistente, 0.2)
(Hotel.isPeriodoReservaOk, 0.8)
(Quarto indisponivel, 0.5) (Periodo Invalido, 0.1)
(Hotel.verificarDisponibilidade, 0.9)
(Hotel.gerarCodReserva, 0.5)
(Reserva realizada com sucesso, 1)
(Reserva.Reserva, 1)
Figura 2.3: Exemplo de Modelo de Uso de Método FCT
Uma vez construído o modelo de uso da funcionalidade, a seleção dos casos de teste é feita, segundo as diretrizes da técnica Cleanroom, até o número de casos de teste a ser desenvolvidos para a funcionalidade em questão, de acordo com as decisões tomadas na fase de planejamento do método.
O processo de seleção do método FCT é bastante sistemático. No entanto, estudando a técnica TOTEM, observamos que ela realiza apenas a geração de casos de teste e que o próprio algoritmo apresentado pela técnica para a conversão dos diagramas de seqüência em expressões regulares faz uso de um grafo para tanto. Dessa forma, para efeito de seleção de casos de teste, o uso de expressões regulares é absolutamente dispensável uma vez que os modelos de uso da técnica Cleanroom, que são grafos, podem ser gerados diretamente a
2.4 O Método FCT 20
partir dos diagramas de seqüência. Além disso, a aplicação da técnica Cleanroom apresenta o problema da seleção de casos de teste repetidos, uma vez que os casos de teste selecionados não são desconsiderados a cada nova seleção, o que pode resultar em um conjunto de casos de teste ineficiente.
Geração de Oráculos
Para realizar a geração dos oráculos, o método FCT novamente faz uso da técnica TOTEM. A técnica propõe que seja construída uma tabela de decisão para cada expressão regular que representa os cenários de uso selecionados nas funcionalidades do componente. Esta tabela deve conter as condições de realização do uso e as ações que serão tomadas pelo componente diante da sua ocorrência. A principal fonte de informação para construção desta tabela de decisão são as pré e pós-condições definidas para as operações das classes que implementam as funcionalidades das interfaces do componente. Para cada cláusula da ex-pressão regular é necessário identificar suas condições de execução e expressá-las em OCL. O próximo passo é definir, também em OCL, que mudanças de estado ocorrem no com-ponente com a execução do caso de uso. Além disso, deve-se identificar também quais mensagens são retornadas para o ator do caso de uso.
Na Tabela 2.1, é apresentado um exemplo de tabela de decisão gerada para uma funcio-nalidade com cinco cenários de uso selecionados. Cada cenário na tabela representa uma cláusula da expressão regular correspondente. Para cada um desses cenários são associadas as condições de execução (A-E), as mensagens retornadas para o usuário após sua execução (I-V) e a indicação se a sua execução causa ou não uma mudança de estado no componente. As informações coletadas durante esta etapa do processo servirão de base para a imple-mentação dos oráculos e poderão auxiliar na seleção dos dados de teste.
Este processo, embora sistemático, é de difícil implementação, uma vez que o resultado dos testes só pode ser determinado após uma consulta a tabela para verificar se a execução de um determinado cenário produziu a mensagem de saída e a mudança de estado esperadas, o que compromete a automação da posterior atividade de construção dos casos de teste. Além disso, na construção das tabelas de decisão são consideradas apenas as pré e pós-condições OCL dos cenários. Contudo, tanto para as pós-condições de execução dos cenários quanto para a verificação de mudança de estado, as invariantes da classe que especifica a
2.4 O Método FCT 21
Condições Ações
Mensagem Mudança
de Estado
Cenários A B C D E I II III IV V
1 Sim Não Não Não Não Sim Não Não Não Não Não
2 Não Sim Não Não Não Não Sim Não Não Não Não
3 Não Não Sim Não Não Não Não Sim Não Não Não
4 Não Não Não Sim Não Não Não Não Sim Não Não
5 Não Não Não Não Sim Não Não Não Não Sim Sim
Tabela 2.1: Exemplo de Tabela de Decisão do Método FCT
funcionalidade também devem ser consideradas uma vez que estas são pré e pós-condições implícitas [MS01].
Seleção de Dados de Teste
A seleção de dados de teste significativos, i.e. que sejam capazes de revelar compor-tamentos anômalos em uma dada funcionalidade, é ainda um dos maiores problemas en-frentados pelos testadores de software. Além disso, esta é uma atividade bastante subjetiva e poucas são as técnicas sistemáticas existentes. Em função disso, o método FCT se limita a fornecer orientações sobre como selecionar os dados necessários para a execução dos casos de teste.
Para realizar a seleção dos dados de teste, o método propõe a utilização da técnica de particionamento por equivalência [Som03; Bei90]. Essa técnica parte do princípio que os dados de entrada de um programa podem ser agrupados em classes que apresentam carac-terísticas comuns e que o programa se comporta da mesma forma para todos os membros de uma mesma classe. Usando essa técnica, o trabalho de selecionar os dados de teste con-siste em identificar as partições e escolher dados particulares dentro de cada partição. A identificação das partições deve ser baseada na especificação e documentação do software. No caso do método FCT, são usadas as condições de execução definidas durante a geração dos oráculos para identificar partições adequadas ao teste. A escolha dos dados tanto pode ser feita de forma aleatória, como de forma mais direcionada, a fim de obter dados mais
2.4 O Método FCT 22
prováveis de revelar erros. Na escolha direcionada consideramos os dados encontrados nos limites da partição, por representarem normalmente valores atípicos, e dados considerados típicos, encontrados no meio da partição.
Embora a seleção dos dados seja realizada através da técnica de particionamento por equivalência, reconhecidamente uma das mais eficientes, a falta de um mecanismo sis-temático para a identificação das partições a partir das condições de execução dos cenários torna esta atividade bastante subjetiva uma vez que a escolha de um conjunto de dados efi-ciente é intimamente dependente da experiência de quem está realizando a atividade e do seu conhecimento sobre o domínio de aplicação do componente.
2.4.3
Construção, Execução e Análise de Resultados
Nesta etapa, as informações geradas até o momento são utilizadas para implementar os casos de teste e oráculos. As tabelas de decisão, que foram construídas durante a geração dos oráculos, são especialmente úteis. Para facilitar a construção das classes de teste, o método
FCT propõe o uso da ferramenta JUnit1 para implementar os casos de teste e oráculos. A
ferramenta é utilizada para executar os testes e analisar os resultados obtidos.
Cada funcionalidade dá origem a uma classe de teste que contém um método para testar cada cenário definido na tabela de decisão da mesma. Assim, para cada cenário, são im-plementadas, no método correspondente, as condições de execução definidas na tabela. Por fim, a mensagem retornada e a ocorrência ou não de mudança de estado são verificadas, de acordo com as definições da tabela, para indicarmos se o teste obteve sucesso ou não.
2.4.4
Empacotamento
O método FCT define, como última atividade, o empacotamento e a distribuição dos testes produzidos juntamente com o componente desenvolvido de maneira a contribuir para a sua re-avaliação por parte do cliente, i.e. dentro de um contexto de uso e integração especí-fico. Entretanto, o formato de empacotamento a ser utilizado e a forma de disponibilização deste pacote não são definidos pelo método.
2.5 Considerações Finais 23
2.5
Considerações Finais
Neste capítulo fornecemos a fundamentação teórica necessária para a compreensão do trabalho aqui proposto. Foram apresentados os principais conceitos e termos relacionados a teste de software, assim como as diferentes estratégias de teste empregadas para a detecção de defeitos em sistemas de software. Com relação a componentes de software, foi apresen-tada a definição e as características dos componentes abordados neste trabalho, as vantagens e desvantagens do desenvolvimento baseado em componentes, além das duas perspectivas sob as quais os componentes precisam ser testados. Por fim, apresentamos o método FCT, no qual este trabalho se baseia. Foram descritas as suas fases e atividades assim como as téc-nicas empregadas na sua definição. Também foi feita uma análise dos principais problemas do método que dificultam a sua aplicação de maneira automática.
No próximo capítulo, é feita a apresentação do método AFCT, um método de teste au-tomático que foi concebido a partir das idéias apresentadas no método FCT.
Capítulo 3
O Método AFCT
Embora concebido para potencializar a automação, o método FCT [dF03] ainda apre-sentava muitas atividades subjetivas e/ou pouco sistemáticas que, apesar de não representar nenhum problema para sua aplicação manual, consistia em um grande empecilho do ponto de vista de automação. Em função disso, definimos o método de teste AFCT, uma nova pro-posta fortemente baseada em [dF03], cujas atividades foram projetadas segundo algoritmos e técnicas pré-definidas com o objetivo de possibilitar a automação a partir do desenvolvi-mento de uma ferramenta de suporte. Neste capítulo será apresentado o método de teste proposto abordando desde os artefatos da especificação dos componentes necessários a sua aplicação, até a descrição de suas atividades e algoritmos.
3.1
Introdução
O método AFCT (Automatic Functional Component Testing) procede a verificação de componentes de software na perspectiva do fornecedor, fazendo uso da especificação destes componentes, sob a forma de artefatos UML e restrições OCL produzidos nas fases de análise e projeto dos processos de desenvolvimento, para derivar casos de teste, oráculos e dados de teste, e, a partir destas informações, proceder a geração e a execução de código de teste assim como a interpretação dos resultados obtidos. O método determina ainda, que os artefatos de teste gerados sejam disponibilizados juntamente com os componentes forneci-dos para favorecer a atividade de teste na perspectiva do cliente, ou seja, no momento da montagem da aplicação [Mac00].
3.1 Introdução 25
As atividades do método AFCT, como apresentado em [BAMF04b], estão agrupadas em 5 fases:
• Planejamento de Teste, onde são tomadas decisões relativas ao processo de teste. • Especificação de Teste, onde são derivados os casos de teste, oráculos e dados que
constituem a especificação dos testes.
• Construção, que é responsável pela geração do código de teste.
• Empacotamento, onde o código de teste é compilado e empacotado para distribuição. • Execução e Análise de Resultados, que realiza a execução e análise de resultados dos
códigos de teste gerados sobre as implementações dos componentes.
A fase de Planejamento de Teste do método AFCT é realizada de forma idêntica à apsentada na definição do método FCT, no Capítulo 2, o que torna desnecessária a sua re-apresentação. As atividades que constituem as demais fases são apresentadas ao longo deste capítulo.
O método AFCT foi concebido para ser utilizado paralelamente às metodologias de de-senvolvimento de sistemas baseados em componentes (SBC), sem, no entanto, estar asso-ciado a nenhuma em especial. Para prover esta independência, procuramos definir a sua integração ao longo do processo de desenvolvimento, a partir de atividades genéricas nor-malmente realizadas e de artefatos comumente desenvolvidos por estas metodologias. Dessa forma, constituímos uma metodologia abstrata que descreve as atividades e a especificação necessárias à aplicação do método de teste. Estas atividades e os artefatos que compõem a es-pecificação dos componentes são apresentados na Figura 3.1 que descreve como é realizada a integração das atividades do método de teste ao longo do processo de desenvolvimento de SBC.
Inicialmente, para cada componente a ser utilizado na aplicação, é feita a análise dos requisitos. A partir desta análise, são produzidos o Diagrama de Funcionalidades, um dia-grama de casos de uso que especifica as funcionalidades dos componentes e os relaciona-mentos entre elas, e o Modelo Conceitual, um diagrama de classe onde são representados os conceitos envolvidos no domínio de problema para o qual o componente está sendo desen-volvido. De posse destes artefatos, a atividade de planejamento do método de teste já pode
3.1 Introdução 26 Planejamento de Teste Especificação de Teste e Construção Empacotamento, Execução e Análise de Resultados Montagem Diagrama de Funcionalidades Requisitos Modelo Conceitual Implementação Materialização Modelo de Informação Diagramas de Cenário de Uso Contratos
Modelagem
Figura 3.1: Integração entre Metodologias de Desenvolvimento de SBC e o Método de Teste
ser realizada. Após o levantamento dos requisitos do componente, é feita a sua modelagem, que engloba as fases de análise e projeto do processo de desenvolvimento. Nesta etapa são produzidos o Modelo de Informação, um diagrama de classe que especifica o conjunto de elementos (classes e associações) que precisam ser explicitados para possibilitar a utilização do componente, os Diagramas de Cenário de Uso, diagramas de seqüência que especificam os cenários de uso das funcionalidades do componente, e os Contratos, restrições OCL que descrevem os contratos de uso (pré e pós-condições) das operações da(s) interface(s), assim como das invariantes de classe do componente. Estes artefatos são utilizados para a realiza-ção das fases de especificarealiza-ção de teste e construrealiza-ção do método AFCT. Uma vez concluída a modelagem do componente, é realizada a sua materialização, que pode se dar tanto a partir da implementação de um componente novo como através da aquisição de um componente previamente desenvolvido que esteja de acordo com a modelagem realizada. De posse da implementação do componente, o código de teste gerado pela fase de construção do método é compilado, e são realizadas as fases de empacotamento e execução e análise de resultados, concluindo assim o teste do componente na perspectiva do fornecedor. A última etapa do processo de desenvolvimento é a montagem da aplicação a partir dos componentes desen-volvidos e testados individualmente.
Conforme descrito, os artefatos necessários a aplicação do método AFCT, que são pro-duzidos ao longo do processo de desenvolvimento, nada mais são do que adaptações de
3.1 Introdução 27
artefatos comumente desenvolvidos pelas metodologias de desenvolvimento baseadas na linguagem UML. No entanto, para possibilitar a definição de um método passível de au-tomação, foi preciso estabelecer a forma como estes artefatos devem ser construídos, prin-cipalmente no que diz respeito à associação entre eles, uma vez que, do ponto de vista de automação, é impossível detectar a real associação entre os artefatos sem que essa infor-mação seja explicitamente fornecida. Para tanto, a abordagem de especificação proposta, que assemelha-se a apresentada por Larman [Lar98], consiste na construção dos artefatos da seguinte maneira:
• No diagrama de funcionalidades, cada funcionalidade da(s) interface(s) do
com-ponente deve ser especificada como um caso de uso que terá como ator, o sis-tema/componente que faz uso da funcionalidade correspondente.
• No modelo de informação do componente, construído a partir do modelo conceitual,
deverão ser especificadas todas as informações necessárias à utilização do componente além das restrições OCL correspondentes às invariantes de classe.
• Para explicitar a relação existente com os casos de uso (funcionalidades) que
de-screvem, os diagramas de cenário de uso deverão ser nomeados de acordo com o for-mato:
<nome_do_caso_de_uso>‘->’<nome_do_cenario>.
• Nestes diagramas de seqüência, o primeiro objeto deverá ser uma instância do
sis-tema/componente que é ator do caso de uso correspondente a funcionalidade, e o se-gundo uma instância da classe do componente que fornece a funcionalidade. Além disso, todos os objetos devem ter o tipo definido e presente no modelo de informação do componente.
• As mensagens dos diagramas de seqüência devem ser numeradas de acordo com sua
ordem de execução e, ou corresponder diretamente à chamada da operação no objeto receptor ou especificar o retorno de uma chamada. A última mensagem do diagrama deverá ser de retorno e corresponderá a mensagem de saída do cenário. Nos cenários que terminem com exceção, esta deve ser especificada na mensagem de saída. Para os
3.2 Especificação de Teste 28
demais cenários, a mensagem de saída deverá ser vazia ou conter a String‘return’
seguida do valor de retorno da funcionalidade (caso haja um).
• As restrições OCL que descrevem os contratos de uso deverão ser inseridas, sob a
forma de comentários, nos diagramas de cenário de uso, especificando suas condições de execução e as mudanças de estado esperadas. Embora esta não seja a maneira mais comum para a especificação de restrições OCL, é a única forma de associá-las aos cenários de uso e, ao mesmo tempo, possibilitar que as especificações sejam con-struídas em ferramentas de modelagem UML simples que disponham do mecanismo de notas de comentário. Estas restrições, respectivamente, pré e pós-condições, terão como contexto a operação correspondente à funcionalidade em questão na interface do componente. Para diferenciar de comentários comuns, cada um desses
comen-tários deverá ser iniciado com a String‘<<constraint>>’seguido da restrição OCL
(‘context...’).
Nas seções seguintes, são descritas as atividades que constituem cada uma das fases do método AFCT assim como os algoritmos e técnicas utilizados em suas definições.
3.2
Especificação de Teste
A fase de especificação de testes do método AFCT consiste na derivação de casos de teste, oráculos e dados de teste a partir da especificação do componente. É nesta fase que reside a maior contribuição do método AFCT para a automação do processo de teste. Cada uma das suas etapas foram sistematicamente definidas e algoritmos foram propostos para possibilitar a sua automação. Nas subseções seguintes, vemos a definição das atividades realizadas nesta fase assim como dos algoritmos e técnicas propostos.
3.2.1
Seleção dos Casos de Teste
Ao estudarmos a atividade de seleção de casos de teste do método FCT e, conseqüente-mente, as técnicas TOTEM [BL01], responsável pela geração dos casos de teste, e Clean-room [PTLP99], que realiza a seleção propriamente dita, observamos que haviam dois pontos falhos na aplicação conjunta das técnicas. A técnica TOTEM define um procedimento para
3.2 Especificação de Teste 29
a conversão dos diagramas de seqüência em expressões regulares que são utilizadas pelo método para a construção dos modelos de uso (grafos) necessários à aplicação da técnica Cleanroom. No entanto, segundo o algoritmo de conversão apresentado na técnica TOTEM, os diagramas são convertidos em grafos a partir dos quais são geradas as expressões regu-lares, o que demonstra a possibilidade de conversão dos diagramas diretamente em modelos de uso. Além disso, a técnica TOTEM só realiza esta conversão porque os casos de teste precisam ser representados sob a forma de expressões regulares para serem utilizados pelas demais atividades da técnica. Em função disso, procuramos simplificar este processo criando um algoritmo para a conversão direta dos diagramas de seqüência para o modelo de uso. Este algoritmo é apresentado na Figura 3.2.
UseModel createUseModel(Collection scenarioDiagrams){
UseModel um new UseModel();
Collection stimuli; SequenceDiagram d;
for each d in scenarioDiagrams{
stimuli sortStimuliByName(d.stimuli);
int numSt stimuli.size;
for each s in stimuli{ if (numSt > 1){ um.addAction(toUseModelAction(s)); } else{ um.addFinalAction(toUseModelAction(s)); } numSt numSt-1; } } return um; } ¬ ¬ ¬ ¬ String toUseModelAction(Stimulus st){
Object sender st.sender;
} ¬ ¬ ¬ ¬ ¬
Object receiver st.receiver;
Action action st.dispatchAction;
String actionSeparator; if (action is ReturnAction){ actionSeparator '|'; } else{ actionSeparator '.'; } return sender.instanceClassifier.name + '->' + receiver.instanceClassifier.name + actionSeparator + action;
Figura 3.2: Algoritmo para a Criação de Modelos de Uso
A função createUseModel recebe como parâmetro uma coleção de diagramas de seqüên-cia, que especificam os cenários de uso da funcionalidade correspondente, e produz como saída o modelo de uso da funcionalidade. Cada diagrama de seqüência possui, além do nome, uma coleção de objetos, do tipo Object, e uma coleção de mensagens, do tipo Stimu-lus. Os objetos possuem um nome e um classificador (instanceClassifier), que determina o tipo do objeto no modelo UML. Cada mensagem, por sua vez, é composta por um objeto transmissor (sender), um objeto receptor (receiver) e uma ação (do tipo Action). O modelo de uso (UseModel) nada mais é do que um grafo dirigido com dois vértices pré-definidos, o inicial e o final, no qual cada vértice possui uma coleção de arcos de saída e cada arco