Teste de Software Orientado a Objetos e a Aspectos: Teoria ... · Teste de Software Orientado a...

59
Teste de Software Orientado a Objetos e a Aspectos: Teoria e Prática Paulo Cesar Masiero, Otávio Augusto Lazzarini Lemos, Fabiano Cutigi Ferrari e José Carlos Maldonado Abstract Software testing has been shown to be important for reducing the number of product faults, attracting interest from both academy and industry. As novel development approaches and support technologies are proposed, new challenges arise in the testing of software developed in these new contexts. For instance, object-oriented (OO) programming, already adopted by the developer community, and, more recently, aspect-oriented (AO) programming, have motivated various efforts for the establishment of novel testing approaches and criteria. The goal of this chapter is to present theoretical and practical aspects of the software testing activity in the OO and AO software development context. In particular, we emphasize structural and fault-based techniques with the support of JaBUTi and JaBUTi/AJ testing tools, developed by the software engineering research group at ICMC/USP. Resumo O teste de software tem se mostrado importante para reduzir a quantidade de defeitos presentes nos produtos, atraindo o interesse tanto da academia quanto da indústria. À medida que surgem novas abordagens de desenvolvimento e tecnologias de apoio, surgem também novos desafios re- lacionados ao teste de software aplicado nesses contextos. Por exemplo, o paradigma orientado a objetos (OO) já consolidado entre a comunidade desenvolvedora e, mais recentemente, a programa- ção orientada a aspectos (POA) têm motivado diversos esforços dedicados ao estabelecimento de abordagens e critérios de teste nesses contextos. O objetivo deste texto é apresentar os aspectos teóricos e práticos relacionados à atividade de teste de software, tanto no contexto de software OO quanto orientado a aspectos (OA). Em particular, são enfatizadas as técnicas estrutural e baseada em defeitos com o apoio das ferramentas de teste JaBUTi e JaBUTi/AJ, desenvolvidas pelo grupo de pesquisas em Engenharia de Software do ICMC/USP. 13

Transcript of Teste de Software Orientado a Objetos e a Aspectos: Teoria ... · Teste de Software Orientado a...

Teste de Software Orientado a Objetos e a Aspectos:Teoria e Prática

Paulo Cesar Masiero, Otávio Augusto Lazzarini Lemos,Fabiano Cutigi Ferrari e José Carlos Maldonado

Abstract

Software testing has been shown to be important for reducing the number of product faults, attracting

interest from both academy and industry. As novel development approaches and support technologies

are proposed, new challenges arise in the testing of software developed in these new contexts. For

instance, object-oriented (OO) programming, already adopted by the developer community, and, more

recently, aspect-oriented (AO) programming, have motivated various efforts for the establishment of

novel testing approaches and criteria. The goal of this chapter is to present theoretical and practical

aspects of the software testing activity in the OO and AO software development context. In particular,

we emphasize structural and fault-based techniques with the support of JaBUTi and JaBUTi/AJ testing

tools, developed by the software engineering research group at ICMC/USP.

Resumo

O teste de software tem se mostrado importante para reduzir a quantidade de defeitos presentes

nos produtos, atraindo o interesse tanto da academia quanto da indústria. À medida que surgem

novas abordagens de desenvolvimento e tecnologias de apoio, surgem também novos desafios re-

lacionados ao teste de software aplicado nesses contextos. Por exemplo, o paradigma orientado a

objetos (OO) já consolidado entre a comunidade desenvolvedora e, mais recentemente, a programa-

ção orientada a aspectos (POA) têm motivado diversos esforços dedicados ao estabelecimento de

abordagens e critérios de teste nesses contextos. O objetivo deste texto é apresentar os aspectos

teóricos e práticos relacionados à atividade de teste de software, tanto no contexto de software OO

quanto orientado a aspectos (OA). Em particular, são enfatizadas as técnicas estrutural e baseada

em defeitos com o apoio das ferramentas de teste JaBUTi e JaBUTi/AJ, desenvolvidas pelo grupo de

pesquisas em Engenharia de Software do ICMC/USP.

13

Masiero, Lemos, Ferrari e Maldonado

1.1. IntroduçãoMuitas propostas de técnicas e ferramentas para a verificação, validação

e teste de software têm sido apresentadas no contexto da área agregada sobo nome de Garantia de Qualidade de Software (GQS). Dentre elas, o teste éuma das mais utilizadas e consiste na execução de um produto com a intençãode revelar defeitos. Sua importância deve-se ao fato das outras atividades deGQS serem insuficientes para a descoberta dos erros introduzidos ao longo dodesenvolvimento do software [Pressman 2000, Myers et al. 2004].

Apesar de ser impossível provar que um software está absolutamente cor-reto por meio de testes, a sua utilização fornece evidências da conformidadecom as funcionalidades especificadas [Myers et al. 2004]. Além disso, umaatividade de teste conduzida de forma sistemática e criteriosa auxilia no en-tendimento dos artefatos testados e evidencia as características mínimas doponto de vista da qualidade do software [Harrold 2000, Weyuker 1996].

A aplicação de um critério de teste está fortemente condicionada à suaautomatização, sendo então de fundamental importância o desenvolvimentode ferramentas de teste. Sem a utilização de ferramentas que automatizema aplicação das técnicas e critérios associados, a atividade de teste torna-se onerosa, propensa a erros e limitada a programas muito simples. Dessaforma, a qualidade do teste, além de depender das técnicas e critérios sendoutilizados, também depende da disponibilidade de ferramentas de apoio. Alémdisso, ferramentas de teste facilitam a condução de estudos experimentais quevisam avaliar e comparar os diversos critérios de teste [Vincenzi 2004].

Com a consolidação do paradigma orientado a objetos (OO) para de-senvolvimento de software durante os anos 80, diversos esforços têm sidodedicados ao estabelecimento de técnicas e critérios de teste no contextodesse paradigma. Embora a orientação a objetos tenha trazido benefíciospara o projeto e desenvolvimento de software, oferecendo uma nova visãode como decompor problemas e novas técnicas para modelar e implemen-tar soluções para esses problemas [Booch 1994], o teste de software OOapresenta um novo conjunto de desafios ao engenheiro de software, relacio-nados às características e construções específicas do paradigma OO como,por exemplo, encapsulamento, herança, polimorfismo e acoplamento dinâmico[McDaniel and McGregor 1994, Binder 1999].

Além disso, mesmo com o desenvolvimento de técnicas para construçãode software OO, observa-se que independentemente da técnica utilizada ou domodo como o sistema é decomposto, alguns interesses pertinentes encontram-se espalhados ou entrelaçados nas várias unidades do código desenvolvido,não estando portanto decompostos de forma organizada e localizada em mó-dulos separados [Elrad et al. 2001]. A Programação Orientada a Aspectos(POA) surgiu no final da década de 90 como uma opção para solucionar es-sas dificuldades, permitindo que interesses que se encontram espalhados ouentrelaçados possam ser implementados tão separadamente quanto possível[Kiczales et al. 1997]. Contudo, assim como para a orientação a objetos, a

14

Teste de Software OO e OA: Teoria e Prática

adoção da POA como técnica de desenvolvimento não evita que defeitos espe-cíficos relacionados às novas características e construções relacionadas sejamintroduzidos no software. Ressalte-se que a maioria dos esforços dedicados àpesquisa envolvendo orientação a aspectos é concentrada no estabelecimentodos conceitos e em como implementá-los nas tecnologias de apoio. Poucasainda são as iniciativas para estabelecer critérios e estratégias de teste paraprogramas orientados a aspectos (OA)

Diversos grupos de pesquisa na área de teste vêm desenvolvendo ativida-des de pesquisa concentradas no estudo de princípios, estratégias, métodose critérios de teste e validação de software, bem como na especificação e im-plementação de ferramentas de teste que apóiem a realização das atividadesde teste e viabilizem a avaliação do aspecto complementar dos critérios deteste por meio de estudos empíricos. Dentre os critérios de teste investigadospelo grupo de Engenharia de Software do ICMC/USP destacam-se os crité-rios estruturais e baseados em mutação, explorados nos contexto de softwareprocedimental e, mais recentemente, para programas OO e OA.

O presente texto busca abordar os aspectos teóricos e práticos relaciona-dos à atividade de teste de software OO e OA, enfatizando o teste de unidade.Nesta seção foi apresentado o contexto no qual este texto está inserido. Aspróximas seções estão organizadas da seguinte forma: na Seção 1.2 são intro-duzidos os conceitos básicos e a terminologia pertinentes ao teste de software,incluindo os critérios de teste mais difundidos das técnicas estrutural e baseadaem defeitos. Além disso, são apresentados os principais conceitos relaciona-dos à programação OO e OA. Na Seção 1.3 são apresentados os conceitosenvolvendo a aplicação de critérios estruturais e baseados em mutação aplica-dos nesses contextos. Em seguida, na Seção 1.4, apresentam-se exemplos daaplicação dos critérios abordados. Para os critérios estruturais, são discutidasas ferramentas JaBUTi (OO) e JaBUTi/AJ (OA), incluindo uma breve apresen-tação de seus aspectos operacionais. Apresenta-se também uma aplicaçãoprática do teste de mutação em programas OO e OA. Por fim, na Seção 1.5,são apresentadas as conclusões e perspectivas de trabalhos futuros na áreade teste de software.

1.2. Terminologia e Conceitos BásicosEm um projeto de software, os artefatos submetidos ao teste podem incluir

programas, especificações de requisitos e de projeto, estruturas de dados ouquaisquer outros artefatos conceitualmente executáveis utilizados no desenvol-vimento da aplicação. De maneira geral, cada um desses artefatos representauma função que, por sua vez, descreve a relação entre um elemento de entrada(chamado de elemento do domínio) e um elemento de saída. Para se testar osoftware são necessários cinco elementos: o artefato conceitualmente executá-vel, a descrição do comportamento esperado (obtida por meio de um oráculo),a observação da execução do artefato em teste, a descrição do domínio funcio-nal e um método para determinar se o comportamento observado corresponde

15

Masiero, Lemos, Ferrari e Maldonado

ao esperado [Adrion et al. 1982]. Dessa forma, podem ser definidas quatroatividades no teste de software: planejamento, projeto dos casos de teste, exe-cução dos casos de teste e avaliação dos resultados [Pressman 2000]. Essasatividades podem ser realizadas em cada uma das seguintes fases, nas quaisse divide tradicionalmente o teste de um software:

• Teste de unidade: busca identificar defeitos1 na lógica e na implemen-tação de cada unidade do software, isoladamente. Segundo o padrãoIEEE 610.12 de 1990 [IEEE 1990], uma unidade é um componente desoftware indivisível. Para se testar as unidades de um sistema, tem-sea necessidade da implementação de pseudo-controladores (drivers) epseudo-controlados (stubs). Os drivers são responsáveis por coordenare ativar a unidade que está sendo testada, passando a ela os dados deteste. Os stubs, por sua vez, consistem em implementações simplifica-das que substituem entidades que interagem com a unidade em teste.

• Teste de integração: visa a descobrir defeitos nas interfaces das unida-des, durante a integração da estrutura do programa. No caso de umprograma OO, considerando-se o método como uma unidade, um tipode teste de integração consiste em testar cada método juntamente comos métodos chamados direta ou indiretamente por ele dentro da mesmaclasse. Para um dado paradigma, a definição do que consiste uma uni-dade implica em diferentes definições de teste de unidade e de integra-ção. Neste texto, essas definições são apresentadas na Seção 1.3.1.

• Teste de sistema: quer identificar defeitos nas funções e característicasde desempenho do sistema como um todo. Além disso, procura-se verifi-car se todos os elementos do sistema (por exemplo, hardware, pessoal ebases de dados) combinam adequadamente e se a função/desempenhoglobal do sistema é alcançada.

Um caso de teste é um par ordenado (d,S(d)) tal que d é um elementode um determinado domínio D (d ∈ D) e S(d) é a saída esperada para umadada função da especificação, quando d é utilizado como entrada. Uma veri-ficação completa de um determinado programa P poderia ser obtida testando-se P com um conjunto de casos de teste T que inclui todos os elementos dodomínio. Entretanto, como geralmente o conjunto de elementos do domínioé infinito ou muito grande, torna-se necessária a obtenção de subconjuntosdesses casos de teste. Para isso, podem ser utilizados critérios de teste queauxiliam o testador, fornecendo um método para a avaliação de conjuntos de

1 Neste texto procura-se seguir o padrão IEEE 610.12-1990 [IEEE 1990] nas definiçõesutilizadas para os termos defeito, erro e falha. Defeito é definido como uma divergên-cia entre o produto de software desenvolvido e o produto supostamente correto; erro édefinido como um estado interno do produto de software que diverge do estado correto,gerado pela execução de um defeito presente nesse produto; e falha é definida como amanifestação externa de um erro quando esse interfere nos comandos que produzem asaída do produto em execução.

16

Teste de Software OO e OA: Teoria e Prática

casos de teste e uma base para a seleção de casos de teste. No primeirocaso os critérios de adequação servem para evidenciar a suficiência da ativi-dade de teste e, no segundo caso, para ajudar na construção de casos de teste[Frankl and Weyuker 2000].

Os critérios de teste são geralmente derivdos a partir de quatro técnicasconhecidas: funcional, estrutural, baseada em defeitos e baseada em estados,que diferem pela abordagem de teste subjacente utilizada para gerar e avaliaros casos de teste [Zhu et al. 1997].

A técnica funcional, também chamada de teste caixa-preta, tem o objetivode determinar se o programa satisfaz aos requisitos funcionais e não-funcionaisencontrados na especificação. A técnica estrutural ou teste caixa-branca, porsua vez, concentra-se na cobertura de partes do código-fonte a partir dos ca-sos de teste, sendo portanto baseada em uma implementação específica. Oscritérios estruturais são baseados na idéia de que não se pode confiar em umprograma se alguns elementos estruturais nunca foram executados durante aatividade de teste. O teste baseado em defeitos utiliza informações sobre ostipos de erros freqüentemente cometidos no processo de desenvolvimento desoftware para derivar os requisitos de teste. Nesse contexto, modelos de de-feitos característicos à tecnologia de implementação do software geralmentesão utilizados, sendo elaborados a partir da experiência dos engenheiros desoftware e de dados experimentais. Por fim, o teste baseado em estados uti-liza uma representação baseada em estados para modelar o comportamentodo sistema ou unidade que será testada. Com base nesse modelo, critériosde geração de seqüências de teste podem ser utilizados de modo a garantir ocorreto funcionamento do sistema ou unidade.

Essas técnicas são consideradas complementares, sendo de fundamentalimportância o estabelecimento de estratégias de teste capazes de explorar asvantagens de cada critério. Nesse contexto, têm sido realizados vários estudosteóricos e experimentais envolvendo a aplicação das diferentes técnicas deteste e a definição dessas estratégias [Zhu et al. 1997].

Neste texto são enfatizadas as técnicas de teste estrutural e baseada emdefeitos devido à sua grande relevância, demonstrada pela extensa investiga-ção na comunidade de Engenharia de Software e grande quantidade de publi-cações sobre essas técnicas. Além disso, elas constituem os principais tópicosinvestigados pelo grupo de pesquisa dos autores na área de teste. Em parti-cular, é abordada a aplicação dos critérios estruturais baseados em fluxo decontrole e em fluxo de dados e, para a técnica baseada em defeitos, critériosbaseados em mutação.

Teste EstruturalA técnica estrutural é vista como complementar à técnica funcional e

baseia-se na estrutura de um programa para derivar seus casos de teste. Emgeral, os critérios dessa técnica utilizam o grafo de fluxo de controle ou grafode programa. Esse grafo ilustra o fluxo de controle lógico de um programautilizando uma notação-padrão [Pressman 2000].

17

Masiero, Lemos, Ferrari e Maldonado

Considerando um programa P, constituído de vários comandos, um blococonsiste em um conjunto de comandos no qual a execução do primeiro co-mando do bloco acarreta a execução de todos os outros comandos do mesmobloco, na seqüência determinada. Sendo assim, cada comando de um bloco,possivelmente com exceção do primeiro, tem um único predecessor e exata-mente um sucessor, exceto possivelmente o último comando. Um grafo defluxo de controle consiste em uma correspondência entre nós e blocos de umprograma, que indica possíveis fluxos de controle entre esses blocos atravésde arestas [Zhu et al. 1997].

No que diz respeito ao grafo de fluxo de controle, alguns conceitos sãoimportantes para o seu uso na técnica estrutural. Dado um grafo de fluxo decontrole G = (N,E,s) em que N é o conjunto de nós, E o conjunto de ares-tas e s o nó de entrada, um caminho corresponde a uma seqüência finita denós (n1,n2, ...,nk), para k ≥ 2, tal que existam arestas de ni para ni+1 parai = 1,2, ...,k− 1. Um caminho é dito simples se todos os nós que compõemesse caminho, exceto possivelmente o primeiro e o último, forem distintos. Setodos forem distintos, incluindo o primeiro e o último, o caminho é dito livrede laço. Os nós de entrada e nós de saída correspondem, respectivamente,aos nós que não possuem nenhum antecessor e aos nós que não possuemnenhum sucessor. Em outras palavras, os nós de entrada não possuem ne-nhuma aresta de entrada e os nós de saída não possuem nenhuma aresta desaída [Zhu et al. 1997]. Um caminho completo é um caminho que vai do nó deentrada a um nó de saída do grafo de fluxo de controle.

Outro termo importante no teste estrutural refere-se aos caminhos não-exe-cutáveis. Um caminho não-executável é um caminho do grafo de fluxo decontrole impossível de ser coberto para qualquer elemento do domínio de en-trada. Isso acontece quando as condições lógicas que deveriam ser satisfeitaspara que a seqüência de nós do caminho fosse executada são contraditórias[Howden 1986].

A partir do grafo de fluxo de controle, podem ser escolhidos os caminhosa serem executados, com o apoio dos diferentes critérios da técnica estrutural.Na Figura 1.1(a) é mostrado o código Java de um método para validação deidentificadores, e na Figura 1.1(b) o seu respectivo grafo de fluxo de controleadicionado também de informações de fluxo de dados.

Os critérios de teste estrutural baseiam-se na idéia de que não se podeconfiar em um programa P se existem certos caminhos que ainda não forampercorridos. Esses critérios geralmente associam um conjunto T de casos deteste (que contém um subconjunto das entradas do programa) com um con-junto Π de caminhos no grafo de fluxo de controle de P, que são percorridosquando esses casos de teste são executados. T satisfaz o critério C para Pse e somente se todo caminho requerido por C é um subcaminho de um doscaminhos de Π [Rapps and J.Weyuker 1982]. Nesse caso, diz-se que T é C-adequado para o teste de P.

Os primeiros critérios estruturais propostos são baseados apenas no fluxo

18

Teste de Software OO e OA: Teoria e Prática

public static boolean verify(String s){

/ * 01 * / if (s == null || s.length() == 0)/ * 02 * / return false ;/ * 03 * / char achar;/ * 03 * / boolean valid_id;/ * 03 * / valid_id = true ;/ * 03 * / achar = s.charAt(0);/ * 03 * / valid_id = valid_s(achar);/ * 03 * / if (s.length() == 1 && valid_id)/ * 04 * / return true ;/ * 05 * / int i = 1;/ * 06 * / while (i < s.length()) {/ * 07 * / achar = s.charAt(i);/ * 07 * / if (!valid_f(achar))/ * 08 * / valid_id = false ;/ * 09 * / i++;/ * 09 * / }/ * 10 * / if (valid_id && (s.length() <= 6))/ * 11 * / return true ;/ * 12 * / return false ;/ * 12 * / }

up={s}

1

2 3

4 5

6

7 10

8

9

11 12

13

up={s}up={s}

d={achar, valid_id}

up={s,valid_id}up={s,valid_id}

d={i}

up={i,s}

d={achar}uc={achar, s}

d={i}uc={i}

d={valid_id}

up={i,s}

d ={s}

d=definiçãouc=c−usoup=p−uso

uc={achar, s}

up={s}

Figura 1.1. Grafo Def-Uso do método para validação deidentificadores

de controle da aplicação e, entre eles, os mais conhecidos são:

• Todos-Nós: Este critério exige que cada comando do programa seja exe-cutado ao menos uma vez, ou seja, que cada nó do grafo de fluxo decontrole seja coberto. Em geral esse é um critério tido como fraco, pois,na prática, não revela nem mesmo a existência de defeitos mais simples[Myers et al. 2004].

• Todas-Arestas: Todas-Arestas requer que cada desvio do fluxo de con-trole do programa seja exercitado pelo menos uma vez, ou seja, que cadaaresta do grafo seja coberta. No grafo de fluxo de controle/dados para ométodo de validação de identificadores apresentado na Figura 1.1, é fácilver que, para este caso, a cobertura de todos os nós do grafo não implicaa cobertura de todos as arestas. Por exemplo, pode-se ter casos deteste que passem pelos caminhos (1,2), (1,3,4), (1,3,5,6,7,8,9,6,10,11)e (1,3,5,6,10,12) e que cobrem todos os nós, porém não exercitem aaresta 7−9.

• Todos-Caminhos: O critério Todos-Caminhos requer que todos os cami-nhos possíveis do grafo de fluxo de controle sejam executados. Esse

19

Masiero, Lemos, Ferrari e Maldonado

critério é o mais exigente do teste caixa-branca e o número de requisi-tos de teste gerados para ele, mesmo em um programa simples, podeser demasiadamente grande (possivelmente infinito) [Myers et al. 2004].Na Figura 1.1, por exemplo, em decorrência do laço (6,7,8,9,6), tem-seinúmeros caminhos no grafo.

Além dos critérios baseados em fluxo de controle, existem ainda os basea-dos em fluxo de dados, os quais utilizam o grafo Def-Uso, construído a partir dografo de fluxo de controle, estendido com informações adicionais sobre as de-finições e subseqüentes usos das variáveis contidas em um programa (como oexemplo apresentado na Figura 1.1 (b)). Uma definição de variável ocorre todavez que um valor é atribuído a uma variável. O uso de variáveis, por sua vez,pode ser de dois tipos: uso computacional (c-uso), em que o valor da variávelé utilizado em uma computação; e uso predicativo (p-uso), em que o valor davariável é utilizado em uma estrutura condicional ou de repetição. A partir daí,se existe alguma sentença em um bloco contendo um c-uso ou definição deuma variável, adiciona-se essa informação ao nó referente no grafo. Já os p-usos são associados às arestas do grafo, visto que o valor das variáveis afetaa seqüência de execução do programa. Um caminho livre de definição parauma variável x dos nós i a j, é um caminho (i,n1, ...,nm, j), m≥ 1, no qual nãohá definições de x nos nós n1, ...,nm [Rapps and J.Weyuker 1982]. Outro con-ceito importante é o de par Def-Uso, que se refere a um par de definição esubseqüente c-uso ou p-uso de uma variável.

O critério mais básico da família de critérios definidos por Rapps e Weyuker[1984] é o critério Todas-Definições. Entre os critérios dessa família, o critérioTodos-Usos tem sido um dos mais utilizados e investigados. Para o propósitodeste texto, podem ser destacados os seguintes critérios baseados em fluxode dados:

• Todas-Definições: Requer que cada definição de variável seja exercitadapelo menos uma vez, não importa se por um c-uso ou por um p-uso.

• Todos-Usos: Requer que todas as associações entre uma definição devariável e seus subseqüentes usos (c-usos e p-usos) sejam exercitadaspelos casos de teste, através de pelo menos um caminho livre de defini-ção.

• Todos-Potenciais-Usos: Requer que pelo menos um caminho livre dedefinição de uma variável definida em um nó i para todo nó e todo arcopossível de ser alcançado a partir de i seja exercitado.

Teste de MutaçãoUm dos critérios mais investigados da técnica baseada em defeitos é o

teste baseado em Análise de Mutantes, ou teste de mutação. Esse critério temum forte relacionamento com o modelo de teste de falha única de circuitos digi-tais, para a detecção de defeitos lógicos [Friedman 1975, Acree 1980]. A idéiabásica é gerar várias versões do programa original ligeiramente modificado,com o objetivo de revelar os defeitos mais comuns introduzidos nos progra-

20

Teste de Software OO e OA: Teoria e Prática

mas pelos programadores [DeMillo 1978]. Dessa forma, o conjunto de casosde teste também é avaliado quanto à sua capacidade de revelar esses defei-tos. Se as saídas obtidas na execução de um mutante são iguais às saídas doprograma original, cabe ao testador determinar se o mutante é equivalente aoprograma original ou se existe a necessidade de se projetar novos casos deteste que resultem em uma saída diferente para o mutante em questão.

A geração dos mutantes tem como base duas hipóteses: a hipótese do pro-gramador competente e a hipótese do efeito de acoplamento [DeMillo 1978]. Aprimeira assume que programas a serem testados estão corretos ou próximosdo correto. A segunda, por sua vez, assume que casos de teste capazes derevelar defeitos mais simples também revelam defeitos mais complexos. Umdefeito simples está associado a uma alteração sintática simples, enquanto queum defeito complexo pode ser considerado como uma composição de defeitossimples.

Para a aplicação do teste de mutação, dados um programa P e um conjuntocasos de teste T, quatro passos são necessários:

1. Geração do conjunto M de mutantes:Operadores de mutação são aplicados em P para gerar um conjunto demutantes M. Os operadores implementam as regras de alteração quesão aplicadas no programa original. Ressalta-se que a definição dosoperadores de mutação depende da linguagem de programação ou deespecificação em que os artefatos a serem testados estão escritos.

2. Execução de P com T :A execução de P com T é realizada e verifica-se se o resultado é oesperado. O processo é encerrado caso alguma saída seja incorreta.Caso isso não ocorra, o próximo passo é realizado. Geralmente cabe aopróprio testador decidir se a saída é ou não a esperada para cada casode teste, fazendo portanto o papel de oráculo [Weyuker 1982].

3. Execução dos mutantes de M com T :Cada um dos mutantes em M é executado com os casos de teste em T.Se um mutante m apresenta uma saída diferente de P executado como mesmo caso de teste, conclui-se que o conjunto de casos de testes ésensível e consegue expor a diferença entre m e P, e m é consideradomorto. Caso contrário, duas possibilidades são consideradas: ou T é debaixa qualidade, não conseguindo revelar a diferença entre m e P, ou mé equivalente a P. Encerrada a execução dos mutantes, o escore (score)de mutação pode ser calculado pela seguinte fórmula:

ms(P,T) = DM(P,T)M(P)−EM(P)

Sendo:

• ms(P,T): escore da mutação (mutation score);

• DM(P,T): número de mutantes mortos por T;

21

Masiero, Lemos, Ferrari e Maldonado

• M(P): número total de mutantes gerados;

• EM(P): número de mutantes equivalentes identificados.

O resultado desse cálculo é sempre um valor no intervalo [0,1], forne-cendo uma medida de adequação de T, sendo que quanto mais próximode 1 for o resultado, mais adequado é T.

4. Análise dos mutantes:Esse é o passo que requer mais intervenção humana. Inicialmente,decide-se se o teste deve ou não continuar, dependendo do escore damutação. Resultados próximos a 1 sugerem um conjunto de casos deteste T suficientemente bom e pode-se optar por encerrar o teste. Casodecida-se por continuar o teste, cada um dos mutantes m “vivos” é ana-lisado se ele é ou não equivalente a P.

O problema de determinar a equivalência entre dois programas, nessecaso P e um mutante m, é geralmente indecidível. Nesse contexto, em-bora a automatização completa dessa atividade não seja possível, al-guns métodos e heurísticas têm sido propostos para a determinaçãoda equivalência entre P e m [Budd 1981, Simão and Maldonado 2000,Vincenzi et al. 2002].

A aplicação da Análise de Mutantes apresenta um alto custo devido princi-palmente à grande quantidade de mutantes gerados até mesmo para progra-mas pequenos e à dificuldade na identificação dos mutantes equivalentes aoprograma original. Diversos trabalhos têm investigado a adoção de estratégiasde aplicação do critério de forma a reduzir seu custo, principalmente relaci-onado ao número de mutantes gerados, entretanto sem perdas significativasna eficácia em revelar defeitos. Dentre elas, destacam-se a Mutação Aleató-ria, que consiste em gerar apenas uma porcentagem de mutantes para cadaoperador selecionado [Acree et al. 1979]; a Mutação Restrita, em que um sub-conjunto de operadores de mutação é selecionado e aplicado na geração dosmutantes [Mathur and Wong 1993]; e a Mutação Seletiva, na qual os operado-res responsáveis pela geração do maior número de mutantes não são utilizados[Offutt et al. 1993]. Os resultados obtidos com a aplicação dessas estratégiasde mutação apontam que é possível reduzir expressivamente o custo de aplica-ção do critério (ganhos próximos a 80%) sem reduções significativas no escorede mutação, que pode ficar muito próximo a 1, ou seja, quase 100% de mutan-tes não-equivalentes mortos.

Para o contexto de teste de integração, Delamaro et al. [2001a] estende-ram o critério Análise de Mutantes, definindo o critério Mutação de Interface,que busca assegurar que as interações entre as diferentes unidades sejamtestadas adequadamente. Foi proposto um conjunto de operadores de muta-ção cuja principal diferença para os operadores de mutação de unidade estános pontos do código que são enfatizados para serem mutados. Para essesnovos operadores, esses pontos correspondem aos pontos de comunicaçãoentre duas unidades.

22

Teste de Software OO e OA: Teoria e Prática

Conforme destacado anteriormente, novas abordagens e tecnologias dedesenvolvimento requerem a adaptação ou proposição de novas técnicas e cri-térios de teste adequados para esses contextos. Em particular, isso se aplicaàs técnicas e critérios de teste enfatizados nesta seção, no que diz respeito aoteste de software OO e OA. Assim, faz-se necessário o entendimento dos prin-cipais conceitos e construções introduzidos pelo paradigma OO e pela POA.

1.2.1. Programação Orientada a ObjetosO paradigma OO consolidou-se nos anos 80 como o paradigma de escolha

para muitos desenvolvedores de produtos de software [Pressman 2000]. Seusconceitos foram estendidos além das tecnologias de apoio à programação, re-sultando desde em linguagens de modelagem até em modelos de processosde desenvolvimento, como a UML e o Processo Unificado.

Os objetos constituem o principal elemento da orientação a objetos. Umobjeto pode ser definido como uma estrutura que retém informações e umconjunto de operações, disponibilizando funcionalidades específicas para seususuários [Wirfs-Brock et al. 1990, Cox and Novobilski 1991]. As informaçõesretidas em um objeto são conhecidas como atributos e as operações são co-nhecidas como métodos (ou “funções-membro” em algumas linguagens como,por exemplo, C++ [Stroustrup 1986]). Um objeto é solicitado para realizaruma determinada operação por meio do envio de uma mensagem (chamadaao respectivo método) que informa a esse objeto o que realizar. A respostaé gerada primeiramente pela escolha do método que implementa a opera-ção solicitada; em seguida, a operação é executada e o controle é retornadoao solicitante juntamente com uma possível resposta à mensagem recebida[Cox and Novobilski 1991].

Uma classe consiste em uma entidade estática que contém a definiçãodos atributos de um objeto e a implementação das operações disponíveis paraele. Um objeto é uma instância de uma classe gerada em tempo de execução,sendo que cada um dos objetos possui, a princípio, suas próprias instâncias2

de cada um dos atributos e operações. O conjunto corrente dos valores dosatributos de um objeto caracteriza o estado desse objeto.

A visibilidade do estado do objeto é controlada pelos mecanismos de ocul-tamento de informações, que estão diretamente relacionados com os mecanis-mos de encapsulamento. O encapsulamento permite que um objeto respondaà chamada de uma determinada operação, isentando o usuário de precisarsaber “como” essa operação é executada e quais dados serão manipuladospara realizar essa operação. Em conjunto com o conceito de ocultamento deinformações, que possibilita a remoção da visibilidade de determinados dadose operações encapsuladas em uma classe [Wirfs-Brock et al. 1990], pode-seisolar a implementação dessa classe das demais. Dessa forma, permite-se

2 Em geral, as linguagens OO permitem a definição de atributos e métodos estáticos, quesão compartilhados por todos os objetos instanciados a partir da classe em que sãodefinidos.

23

Masiero, Lemos, Ferrari e Maldonado

a realização de manutenção no código dessa classe sem a necessidade demudanças em outras classes do sistema que dependem dos seus serviços.

A identificação de atributos e operações comuns a várias classes de obje-tos permite a definição de uma nova classe que é incluída em um nível superiorde uma hierarquia [Booch 1994]. Todas as classes que aparecem no nível in-ferior da hierarquia possuem, a princípio, todas as informações e operações declasses que estão em níveis superiores do mesmo ramo da hierarquia. Tem-se então uma relação de herança, sendo que as classes em um nível inferior“herdam” as informações e operações definidas nas classes em níveis superi-ores. Assim, novas classes não precisam ser completamente desenvolvidas,mas podem herdar partes de funcionalidades de outras classes. A partir daísomente as funcionalidades específicas devem ser implementadas na classeherdeira [Cox and Novobilski 1991].

Definidas as hierarquias de classes utilizando-se os mecanismos de he-rança, o desenvolvedor pode usufruir de tipos polimórficos, em geral apoiadopelas tecnologias de desenvolvimento de software OO. De acordo com a defi-nição de Booch [1994] , “polimorfismo representa o conceito da teoria dos tiposna qual um simples nome pode denotar objetos instanciados a partir de diferen-tes classes que são relacionadas a uma superclasse comum”. Dessa forma,um identificador de objeto do tipo de uma superclasse pode ser atribuído paradiferentes objetos instanciados a partir de suas subclasses. Isso permite queum objeto envie uma mensagem sem saber para qual objeto a mensagem estásendo enviada. Nesse caso, diferentes tipos de objetos podem estar definidospara responder à mensagem, cada um à sua maneira [Wirfs-Brock et al. 1990].No caso do uso de tipos polimórficos, o mecanismo de acoplamento dinâmicoencarrega-se de resolver, em tempo de execução, qual objeto responderá àmensagem e qual método será executado.

Na Figura 1.2 é apresentado um modelo de uma aplicação OO, adaptadodo trabalho do AspectJ Team [2003]. As classes presentes no modelo re-presentam a funcionalidade básica de um sistema de simulação de telefoniano qual um cliente pode realizar e receber chamadas telefônicas locais oude longa distância, e pode atender mais de uma chamada simultaneamente,resultando em uma conferência. Cada chamada simples (classes Local ouLongDistance ) possui uma conexão (classe Connection ), e uma conferên-cia possui duas ou mais conexões.

Nesse exemplo, alguns dos conceitos introduzidos pela orientação a obje-tos podem ser observados. Entre eles, os conceitos de classe, métodos e atri-butos, e a relação de herança estabelecida entre a superclasse Connectione suas subclasses Local e LongDistance . Embora não seja explícito noexemplo, o polimorfismo pode ser observado com a utilização de um identifi-cador declarado como sendo do tipo Connection , cuja instanciação pode sertanto do tipo Local quanto LongDistance . O tipo corrente instanciado e atri-buído ao identificador é decidido em tempo de execução, e os comportamentosapropriados são executados dependendo do tipo identificado.

24

Teste de Software OO e OA: Teoria e Prática

+addCall()

+removeCall()

+localTo()

+pickup()

+hangup()

+merge()

-name : String

-phoneNumber : String

-areacode : int

-password : String

-calls

Customer

Call

+complete() : void

+drop() : void

+connects() : bool

-isMobile : Boolean

-state : int

Connection

Local LongDistance

*

-caller1

*

-receiver

1

-caller

1

*

-receiver1

*

-connections *1

Figura 1.2. Modelo de aplicação OO de telefonia

A linguagem Java [Sun Microsystems 2006] é uma das linguagens de de-senvolvimento de software OO que possibilita a aplicação dos principais con-ceitos e recursos do paradigma. Java será utilizada nos exemplos apresenta-dos nas próximas seções deste texto.

1.2.2. Programação Orientada a AspectosEm meados dos anos 90, alguns pesquisadores constataram a existência

de certos interesses que, mesmo com a utilização da programação OO, não seencaixavam em módulos individuais, ficando espalhados por várias unidadesdo código (também chamados de interesses transversais). A POA foi con-cebida como uma proposta de resolução desse problema, a partir do uso demecanismos que permitem o isolamento desses interesses.

O mecanismo de quantificação, introduzido pela POA, possibilita a imple-mentação de módulos isolados, os aspectos, que têm a capacidade de afetaroutros módulos do sistema de forma transversal. Dessa forma, em POA umúnico aspecto pode contribuir para a implementação de diversos outros módu-los que implementam as funcionalidades básicas, chamados de módulos base[Elrad et al. 2001].

A quantificação permite que a estrutura de implementação represente maisadequadamente o projeto do sistema, pois sem ela certos tipos de interessestendem a emaranhar-se com outros e/ou espalharem-se por diversos módulos.Por exemplo, na Figura 1.3 são representados por meio de barras os diferentesmódulos presentes na implementação OO do servidor Tomcat. As partes dosmódulos que implementam o interesse de registro (logging) estão destacadas

25

Masiero, Lemos, Ferrari e Maldonado

nas barras. O espalhamento pode ser verificado pelo fato de diferentes mó-dulos possuírem código referente a esse interesse. O emaranhamento podeser verificado pelo fato da existência de código que implementa mais de umafuncionalidade – uma principal e a de registro – em módulos isolados. Dessaforma existe uma separação de interesses não rigorosamente adequada, naqual cada módulo implementaria um único interesse. Alguns tipos de emara-nhamento e espalhamento podem ser resolvidos pelas técnicas de softwaretradicionais, entretanto, existem outros que, por sua própria natureza, não fi-cam bem modularizados quando utilizados esses paradigmas.

Figura 1.3. Estrutura de módulos do Tomcat

Para que os aspectos possam adicionar comportamento em outros mó-dulos por meio do mecanismo de quantificação é necessário que se pos-sam identificar pontos concretos da execução de um programa. Esses pon-tos são chamados pontos de junção (join points) do programa. Dessa forma,uma linguagem orientada a aspectos deve fornecer um modelo por meiodo qual esses pontos possam ser identificados. Por exemplo, em AspectJ[Kiczales et al. 2001, Kiczales and Mezini 2005], que consiste em uma exten-são de Java que apóia a POA, o modelo de pontos de junção é baseadonas construções da linguagem e os pontos são identificados por eventos dotipo: chamada ou execução de um método com um certo nome (ou padrão denome), leitura/escrita de atributos com um certo nome (ou padrão de nome),entre outros. AspectJ será utilizada nos exemplos apresentados nas próximasseções deste texto.

Um conjunto de pontos de junção – ou somente conjunto de junção (point-cut) – identifica diversos pontos de junção em um sistema. Através do con-junto de junção, um comportamento transversal (crosscutting behavior ) podeser definido nos pontos de junção identificados por ele. Esses comportamentostransversais são implementados por meio de construções similares aos méto-dos chamadas de adendos (advice), que podem executar antes, depois ou nolugar dos pontos de junção identificados. Esses adendos são chamados de

26

Teste de Software OO e OA: Teoria e Prática

adendos anteriores, posteriores e de contorno, respectivamente. Por exem-plo, para implementar um interesse de registro que imprime uma mensagemtoda vez que um método é chamado, poderia ser utilizado um aspecto comum adendo anterior que executaria toda vez que qualquer método do sistemafosse chamado.

Outro mecanismo importante introduzido pela POA é a declaração intertipo(ou introdução). Esse tipo de declaração permite que um aspecto introduzamembros (geralmente métodos ou atributos) em algum outro tipo (classe ouinterface, por exemplo). Essas introduções são utilizadas quando o interesseimplementado no aspecto também utiliza atributos e métodos que deveriamfazer parte dos módulos que o aspecto afeta.

Depois do código-base (referente aos módulos base) e os aspectos seremcodificados, é necessário um processo de combinação (weaving) dos módulosem uma aplicação executável. Assim, toda linguagem de programação orien-tada a aspectos deve contar também com um combinador (weaver ), respon-sável por essa tarefa. A combinação pode ser feita em diversos momentos,dependendo da decisão de implementação tomada para cada linguagem deprogramação específica. Por exemplo, a combinação dinâmica é o processode combinação que ocorre em tempo de execução, enquanto que combinaçãoestática ocorre em tempo de compilação.

Na Figura 1.4 é apresentado o modelo da aplicação de telefonia mostradaanteriormente para ilustrar a POO, acrescido de uma classe e três aspectos. Aclasse Timer implementa um cronômetro e é utilizada no cálculo do tempo deuma chamada. As funcionalidades adicionadas pelos aspectos são descritasabaixo:

• Timing implementa o interesse de cronometragem e é responsávelpor medir os tempos das conexões por cliente, iniciando e parando umcronômetro da classe Timer associado a cada conexão;

• Billing implementa o interesse de faturamento e é responsável porgarantir que cada conexão tenha um cliente pagador – o cliente que iniciaa chamada – e também que as chamadas locais, de longa distância e decelulares devem ser cobradas com taxas diversas;

• TimerLog implementa um registro que imprime na tela os horários emque um cronômetro inicia e pára.

Ambos os aspectos Timing e Billing entrecortam a classe Call paramarcar os tempos das ligações e tarifar cada uma com base na duração. Elespossuem adendos posteriores que afetam os métodos responsáveis pelos iní-cio e fim das ligações, já que esses são os pontos adequados para se marcaros tempos e atribuir as tarifas.

O exemplo apresentado mostra os principais conceitos da POA. No entanto,pode-se observar que os aspectos afetam poucos pontos da aplicação. Emaplicações maiores, em geral os aspectos afetam mais pontos, o que é maisinteressante do ponto de vista da moduralização de interesses transversais. De

27

Masiero, Lemos, Ferrari e Maldonado

+addCall()

+removeCall()

+localTo()

+pickup()

+hangup()

+merge()

-name : String

-phoneNumber : String

-areacode : int

-password : String

-calls

Customer

Call

+complete() : void

+drop() : void

+connects() : bool

-isMobile : Boolean

-state : int

Connection

Local LongDistance

*

-caller1

*

-receiver

1

-caller

1

*

-receiver1

*

-connections *1

«aspect»

Billing

«aspect»

Timing

«aspect»

TimerLog

-startTime : long

-stopTime : long

Timer

«crosscuts»«crosscuts» «crosscuts»

Figura 1.4. Modelo da aplicação de telefonia acrescidodos aspectos

qualquer modo, o mecanismo pode ser entendido mesmo quando um conjuntorestrito de pontos é afetado pelos aspectos.

AspectJA linguagem AspectJ é uma extensão de Java criada para permitir a pro-

gramação orientada a aspectos de maneira genérica, no contexto dessa lingua-gem. Basicamente, as novas construções do AspectJ consistem em: conjuntosde junção (pointcut ) que identificam conjuntos de pontos de junção; aden-dos que definem o comportamento em um dado conjunto de junção; constru-ções para afetar estaticamente a estrutura dos módulos básicos do programa(declarações intertipos e que alteram a hierarquia de classes); e os aspec-tos (aspect ) que encapsulam as construções novas e as tradicionais de umaclasse Java3.

Na Figura 1.5 é mostrado um exemplo de conjunto de junção nomeado. Umconjunto de junção pode ser definido como uma combinação de outros conjun-tos de junção, utilizando operadores lógicos binários ‘e’ (&& ) e ‘ou’ (||). Alémdisso, o operador unário de negação (!) também pode ser usado quando nãose quer capturar pontos de junção definidos por um conjunto de junção espe-cífico. O primeiro conjunto de junção que compõe atualiza , por exemplo,

3 A maior parte desta seção foi baseada no livro de Laddad [Laddad 2003].

28

Teste de Software OO e OA: Teoria e Prática

captura todas as chamadas ao método move da classe ElementoDeFigura ,que recebe dois inteiros como parâmetros e não retorna nenhum valor.

(): public pointcut atualiza ( . ( , )) call public void ElementoDeFigura move int int || ( . *(..));call public void ElementoDeFigura set

palavra-chave paradeclarar um

conjunto de junção

nome do conjuntode junção

tipo do conjuntode junção

modificadorde acesso assinatura a casar

operador decomposição

Figura 1.5. Exemplo de definição de conjunto de junçãonomeado

As notações coringa “*”, “..” e “+”, denotam respectivamente: qualquernúmero de caracteres com exceção do “.”, qualquer número de caracteres in-cluindo o “.”, e qualquer subclasse ou subinterface de um dado tipo (tipo noAspectJ refere-se a uma classe, interface, tipo primitivo ou aspecto). Uma re-ferência completa sobre a linguagem AspectJ pode ser encontrada no websitedesenvolvido pelo AspectJ Team [2003].

A seguir são discutidas as aplicações das técnicas de teste estrutural ebaseadas em defeitos no contexto de software OO e OA,enfatizando o testede unidade. Para o teste estrutural, são abordados os critérios baseados emfluxo de controle e fluxo de dados, e para o teste baseado em defeitos, oscritérios baseados em Análise de Mutantes.

1.3. Teste de Software OO e OAA POO e a POA acrescentaram novas construções e conceitos aos já

conhecidos das linguagens de programação tradicionais e que, portanto, de-vem ser explorados em abordagens de teste adequadas para esses contextos.Neste texto são apresentadas tanto as aplicações diretas das abordagens deteste que foram propostas para o paradigma procedimental, quanto algumasadaptações necessárias para a aplicação de cada uma das técnicas de testenesses novos contextos. Particularmente, são tratadas as técnicas estrutural(baseada em fluxo de controle e de dados) e baseada em defeitos (teste demutação).

Em geral, os critérios estruturais definidos para teste de unidade do para-digma procedimental são diretamente aplicáveis para o contexto de teste deunidade OO, considerando-se os métodos como as unidades a serem testa-das. No entanto, para o teste de integração, as propriedades específicas aoparadigma OO têm maior impacto. Por exemplo, o mecanismo de acoplamentodinâmico pode trazer indecidibilidade para o teste, pois pode ser impossível

29

Masiero, Lemos, Ferrari e Maldonado

predizer em tempo de compilação qual trecho de código será executado emum determinado ponto. Como neste texto a ênfase está no teste de unidade,questões envolvendo essas dificuldades são discutidas em outros trabalhos[Binder 1999, Alexander and Offutt 2000, Vincenzi 2004].

Por outro lado, em POA um dos principais mecanismos que devem serconsiderados nas abordagens de teste é a quantificação. Quando aspectosadicionam comportamento por meio dos conjuntos de junção em diversos mó-dulos do programa, a estrutura original dos módulos-base é modificada apósa combinação, ou seja, novas interfaces são introduzidas dentro dos módulos[Kiczales and Mezini 2005]. Como os métodos afetados têm sua estrutura in-terna modificada, uma abordagem de teste adequada deve levar em conta eexplicitar essas alterações. Na abordagem estrutural apresentada neste ca-pítulo, o modelo de fluxo de controle e de dados é adaptado para explicitar oslocais em que os adendos são executados nos métodos. A partir desse modelo,critérios de teste foram concebidos para fazer com que o testador concentre-senesses pontos, exercitando-os a partir dos casos de teste.

1.3.1. Fases do Teste de Software OO e OACom base nos conceitos de teste apresentados na seção anterior, pode-se

considerar que em programas OO as menores unidades a serem testadas sãoos métodos; e em programas OA, considerando-os como extensões de progra-mas OO (como acontece nos escritos em AspectJ), os métodos (inclusive osintertipo declarados) e os adendos. A classe à qual o método pertence pode servista como o driver do método, pois sem ela não é possível executá-lo. Alémdisso, o aspecto somado a um ponto de junção que faça com que o adendoseja executado podem ser vistos como o driver do adendo, pois sem eles nãoé possível executar o adendo (a não ser que haja alguma infra-estrutura es-pecial que permita a execução do adendo como se fosse um método, o quedescartaria a necessidade do ponto de junção). Além disso, métodos comunse intertipo declarados pertencentes a aspectos podem também necessitar deinfra-estrutura especial para serem executados em isolamento.

Por definição, uma classe engloba um conjunto de atributos e métodos quemanipulam esses atributos; e um aspecto engloba basicamente conjuntos deatributos, métodos, adendos e conjuntos de junção. Assim sendo, conside-rando uma única classe ou um único aspecto já é possível pensar em teste deintegração. Métodos da mesma classe ou mesmo aspecto, bem como adendose métodos de um mesmo aspecto, podem interagir para desempenhar funçõesespecíficas, caracterizando uma integração que deve ser testada.

Levando em conta tais considerações, e baseando-se no particionamentoutilizado por Sommerville [2001] e na abordagem de Harrold e Rother-mel [1994] , a atividade de teste de programas OO e OA poderia ser divididanas seguintes fases [Lemos et al. 2004]:

1. Teste de Unidade : O teste de cada método e adendo isoladamente,também chamado de teste intra-método ou intra-adendo.

30

Teste de Software OO e OA: Teoria e Prática

2. Teste de Módulo : O teste de uma coleção de unidades dependentes– unidades que interagem por meio de chamadas ou interações comadendos. Essa fase pode ser dividida nos seguintes tipos de teste (con-siderando classes e aspectos como entidades diferentes):

• Inter-método: Consiste em testar cada método público juntamentecom outros métodos da mesma classe chamados direta ou indire-tamente (chamadas indiretas são aquelas que ocorrem fora do es-copo do próprio método, dentro de um método chamado em qual-quer profundidade).

• Adendo-método: Consiste em testar cada adendo juntamente comoutros métodos chamados por ele direta ou indiretamente.

• Método-adendo: Consiste em testar cada método público junta-mente com os adendos que o afetam direta ou indiretamente (con-siderando que um adendo pode afetar outro adendo). Nesse tipode teste não é considerada a integração dos métodos afetados comos outros métodos chamados por eles, nem com métodos chama-dos pelos adendos.

• Inter-adendo: Consiste em testar cada adendo juntamente com ou-tros adendos que o afetam direta ou indiretamente.

• Inter-método-adendo: Consiste em testar cada método público jun-tamente com os adendos que o afetam direta e indiretamente, ecom métodos chamados direta ou indiretamente por ele. Esse tipode teste inclui os quatro primeiros tipos de teste descritos acima.

• Intra-classe: Consiste em testar as interações entre os métodos pú-blicos de uma classe quando chamados em diferentes seqüências,considerando ou não a interação com os aspectos.

• Inter-classe: Consiste em testar as interações entre classes dife-rentes, considerando ou não a interação dos aspectos.

3. Teste de Sistema : A integração de todos os módulos, inclusive com oambiente de execução, forma um subsistema ou um sistema completo.Para essa fase geralmente é utilizado o teste funcional.

1.3.2. Teste Estrutural de Programas OONeste texto, seguindo os trabalhos de Vincenzi [2004] e Vincenzi et al.

[2005], a linguagem Java é utilizada para demonstrar os critérios estruturaisaplicados no contexto de programas OO. Mais especificamente, a idéia é via-bilizar o teste estrutural de programas Java a partir do bytecode4 Java, permi-tindo também, com isso, o teste estrutural de componentes5 para os quais os

4 Código objeto de Java que consiste em instruções similares às instruções de linguagensde montagem, porém que armazenam informações de alto nível das classes compiladas.

5 Componente, nesse caso, é definido como um conjunto de entidades de composição desoftware com interface bem definida e especificada.

31

Masiero, Lemos, Ferrari e Maldonado

códigos-fonte nem sempre se encontram disponíveis.Um modelo de fluxo de dados subjacente foi definido, caracterizando as

instruções de bytecode responsáveis pela definição e/ou o uso de variáveis. Deposse do modelo de fluxo de dados, um modelo de representação de programa– o Grafo Definição-Uso (DU) – é construído, considerando os mecanismos detratamento de exceção, em geral, presentes nas linguagens OO. Desse modo,o grafo DU é utilizado para representar o fluxo de controle e o fluxo de dadosintra-método, tanto da execução normal do programa quanto na presença deexceções.

Um grafo DU de uma dada unidade u é definido como um grafo dirigido DU(u) = (N,E,s,O), tal que:

• N representa o conjunto de nós de um grafo DU: N = {n|n correspondea uma seqüência linear de computações, ou bloco de instruções, de u};

• E⊆N×N = Er ∪Ee é o conjunto completo de arestas do DU. Cada arestae∈ E representa a transferência de controle que pode ocorrer entre doisnós, tal que:

– Er e Ee correspondem a dois subconjuntos disjuntos de arestasregulares e de exceção, respectivamente:

∗ Er é o conjunto de arestas regulares definido como Er ={(ni ,n j )| o bloco de instruções em n j pode ser executado ime-diatamente após o bloco de instruções em ni e (ni ,n j ) /∈ Ee}.

∗ Ee é o conjunto de arestas de exceção definido como Ee ={(ni ,n j )| as instruções de ni estão no escopo de um tratadorde exceção que inicia em n j };

• s∈ N é o nó de entrada de u. s é o único nó do grafo que não possuinenhuma aresta de entrada;

• O⊆N é o conjunto (possivelmente vazio) de nós de saída. Ou seja, cadao∈O não possui nenhuma aresta de saída.

Para cada DU construído a partir do bytecode Java, as componentes dografo são representadas da seguinte forma:

• Nós regulares são representados por círculos com o rótulo contendo aprimeira instrução de bytecode do bloco;

• Nós de chamada são representados por círculos duplos;• Nós de saída são representados por nós negritados;• Arestas de exceção são representadas por arestas tracejadas, repre-

sentando o fluxo de controle do ponto onde uma exceção é gerada até oprimeiro nó correspondente ao tratador daquela exceção.

Na Figura 1.6 é mostrada parte do código-fonte da aplicação de telefoniadiscutida anteriormente nas seções 1.2.1 e 1.2.2. O construtor da classe Call(linhas 5–18) será utilizado para demonstrar o DU. A lógica envolvida no cons-trutor consiste em checar se a chamada é de longa distância ou local e instan-ciar um objeto Connection correspondente. Na Figura 1.7 é mostrada parte

32

Teste de Software OO e OA: Teoria e Prática

do bytecode gerado para o construtor da classe Call e o DU correspondente(porém sem informações de fluxo de dados).

1 public class Call {2 private Customer caller, receiver;3 private Vector connections = new Vector();45 public Call(Customer caller, Customer6 receiver, boolean iM)7 {8 this .caller = caller;9 this .receiver = receiver;

10 Connection c;11 if (receiver.localTo(caller)) {12 c = new Local(caller, receiver, iM);13 } else {14 c = new LongDistance(caller,15 receiver, iM);16 }17 connections.addElement(c);18 }1920 public void pickup() {21 ...22 }2324 public boolean isConnected(){25 ...26 }2728 public void hangup(Customer c) {29 for (Enumeration e =30 connections.elements();31 e.hasMoreElements();) {32 ((Connection)e.nextElement()).drop();33 }34 }3536 public boolean includes(Customer c){37 ...38 }3940 public void merge(Call other){41 ...42 }43 }

Figura 1.6. Parte do código da classe Call da aplicaçãode telefonia

0: aload_01: invokespecial java.lang.Object.<init> ()V (15)4: aload_0...30: ifeq #4833: new <telecom.Local> (32)...48: new <telecom.LongDistance> (36)60: aload_0...

Figura 1.7. Parte do bytecode e DU do construtor da classe Call

Uma vez que o grafo DU de cada método tenha sido obtido, critérios po-

33

Masiero, Lemos, Ferrari e Maldonado

dem ser definidos para derivar diferentes requisitos de teste, os quais podemser utilizados tanto para avaliar a qualidade de um determinado conjunto deteste quanto para a própria geração de dados de teste. Ao todo, oito critériosde teste estruturais foram definidos. Vincenzi [Vincenzi 2004] optou por sepa-rar os requisitos de teste em dois conjuntos disjuntos: (1) os que podem sercobertos durante a execução normal do programa, denominados independen-tes de exceção; e (2) os que para serem cobertos exigem, obrigatoriamente,que uma exceção tenha sido lançada, denominados dependentes de exceção.Desse modo, foram estabelecidos os seguintes critérios:

• Todos-Nós:– Todos-Nós-Independentes-de-Exceção (Todos-Nósei): Requer que

cada nó n∈N do grafo DU que é alcançável por pelo menos um ca-minho livre de exceção, ou seja, caminhos que não incluam arestasde exceção, seja exercitado por algum caso de teste.

– Todos-Nós-Dependentes-de-Exceção (Todos-Nósed): Requer quecada nó n ∈ N do grafo DU que não é alcançável por pelo menosum caminho livre de exceção seja exercitado por algum caso deteste.

• Todas-Arestas:– Todas-Arestas-Independentes-de-Exceção (Todas-Arestasei): Re-

quer que cada aresta e∈ E do grafo DU que é alcançável por pelomenos um caminho livre de exceção seja exercitada por algumcaso de teste.

– Todas-Arestas-Dependentes-de-Exceção (Todas-Arestased): Re-quer que cada aresta e∈ E do grafo DU que não é alcançável porpelo menos um caminho livre de exceção seja exercitada por algumcaso de teste.

• Todos-Usos:– Todos-Usos-Independentes-de-Exceção (Todos-Usosei): Requer

que seja exercitado pelo menos um caminho livre de exceção elivre de definição para uma variável definida em um nó n para todonó e toda aresta que possui um uso da mesma variável e que possaser alcançada a partir de n.

– Todos-Usos-Dependentes-de-Exceção (Todos-Usosed): Requerque seja exercitado pelo menos um caminho que não seja livrede exceção mas que seja livre de definição para uma variável defi-nida em um nó n para todo nó e toda aresta que possui um uso damesma variável e que possa ser alcançada a partir de n.

• Todos-Potenciais-Usos [Maldonado 1991]:– Todos-Pot-Usos-Independentes-de-Exceção (Todos-Pot-Usosei):

Requer que seja exercitado pelo menos um caminho livre deexceção e livre de definição de uma variável definida em um nó npara todo nó e toda aresta possível de ser alcançada a partir de n.

34

Teste de Software OO e OA: Teoria e Prática

– Todos-Pot-Usos-Dependentes-de-Exceção (Todos-Pot-Usosed):Requer que seja exercitado pelo menos um caminho que não sejalivre de definição mas que seja livre de definição para variáveldefinida em um nó n para todo nó e toda aresta possível de seralcançada a partir de n.

Apesar de se ter evidências da viabilidade prática da aplicação desses cri-térios no teste intra-método, estudos experimentais e teóricos ainda são ne-cessários para avaliar detalhadamente os resultados [Vincenzi 2004]. Alémdisso, o teste estrutural por si só apresenta algumas limitações em sua apli-cação como, por exemplo, o problema da não-executabilidade, que acontecequando não existem dados de teste que façam com que requisitos de teste emparticular sejam satisfeitos [Zhu et al. 1997].

Em particular o custo, eficácia e dificuldade de satisfação entre os critériossão propriedades interessantes para serem investigadas nesse contexto. Aferramenta JaBUTi que implementa os critérios apresentados aqui, e será vistana Seção 1.4, pode auxiliar na condução de tais estudos, já que fornece umamaneira automatizada de aplicação do teste estrutural.

Teste Estrutural de Integração OOAté o presente momento, poucos trabalhos abordam o teste estrutu-

ral de integração no contexto de programas OO. Entretanto, alguns es-forços podem ser observados nessa direção [Harrold and Rothermel 1994,Alexander and Offutt 2000]. Em particular, pode ser destacado o trabalho deHarrold e Rothermel [1994], que adapta o teste de fluxo de dados para clas-ses.

Assim como são testados os procedimentos de forma isolada (teste intra-procedimental) e a interação entre eles (teste interprocedimental), a mesmaidéia pode ser aplicada aos métodos isolados de uma classe (teste intra-método abordado anteriormente), e aos métodos de uma classe que intera-gem entre si (teste inter-método). Além disso, no paradigma OO devem serconsideradas também as interações de fluxo de dados que acontecem quandousuários de uma classe invocam seqüências de métodos de maneira arbitrária[Harrold and Rothermel 1994]. Por exemplo, em uma classe D, com atributo a emétodos m1, m2, m3, deve ser analisado se as diferentes seqüências de chama-das (por exemplo: m2, m1, m3, m2) não provocam nenhum estado inconsistentena classe D com relação ao atributo a.

Harrold e Rothermel consideram ainda o teste de fluxo de dados na inte-gração das classes, que corresponde a um quarto nível de teste, o inter-classe.Esse tipo de teste envolve os pares def-uso em que a definição de uma variá-vel se encontra em uma classe e o uso em outra. Detalhes sobre o teste deintegração não são tratados aqui, já que neste texto é dada ênfase ao testede unidade. No entanto cabe destacar que esses problemas são objetos dostrabalhos em andamento de alguns grupos de pesquisas em Engenharia deSoftware.

Assim como na POO, para que os critérios estruturais possam ser aplicados

35

Masiero, Lemos, Ferrari e Maldonado

na POA, é necessário que sejam definidos os grafos de fluxo de controle ede dados adequados. A seguir definem-se genericamente os grafos de fluxopara programas OA e sua instanciação para a linguagem AspectJ, seguido dadefinição dos critérios baseados nesses modelos.

1.3.3. Teste Estrutural de Programas OANo fluxo de controle de programas OA, quando os aspectos definem com-

portamento em algum ponto do sistema por meio dos adendos, pode ser detec-tado um novo tipo de interação. Quando o ponto de junção é alcançado, o fluxode controle é passado para o adendo do aspecto que afeta aquele ponto, retor-nando ao final da execução. Esse tipo de interação pode ser comparada comuma chamada a método, na qual o fluxo de controle é passado para o métodochamado, retornando ao final de sua execução. Porém, a dependência é in-versa, pois no primeiro caso o desenvolvedor do método insere explicitamentea chamada ao método para atender os propósitos do método em programação,enquanto que no segundo caso são os aspectos os responsáveis por definiro comportamento extra em pontos de junção específicos. Como comentadopor Kiczales & Mezini [Kiczales and Mezini 2005], a composição de programasOA define o surgimento de novas interfaces em diversos módulos do sistema.Dessa forma, esses novos elementos estruturais devem ser levados em contadurante o teste estrutural desses programas.

O Grafo de Fluxo de Controle Orientado a Aspectos (AOCFG) é um grafodefinido com o objetivo de apoiar o teste de unidade de um programa OA[Lemos 2005]. Como é possível que em uma linguagem OA os adendos sejamafetados por outros adendos – como, por exemplo, em AspectJ – o AOCFGpode ser usado para representar tanto métodos quanto adendos.

Um grafo AOCFG de uma dada unidade u tem as mesmas componentesdo DU definido para programas OO mais a componente C, ou seja, AOCFG(u) = (N,E,C,s,O). A componente C é definida como segue:

• C ⊆ N é o conjunto (possivelmente vazio) de nós transversais que re-presentam um nó no qual ocorre uma interação com um adendo de umdado aspecto. Esses nós representam as interfaces adicionadas pelosaspectos para que os adendos sejam executados;

O Grafo Def-Uso Orientado a Aspectos (AODU) é o AOCFG com informa-ções de definições e usos de variáveis, para a aplicação de critérios baseadosem fluxo de dados. Como o grafo AODU é um AOCFG estendido, é necessáriaapenas a construção do AODU para se derivar requisitos de teste tanto parao fluxo de controle quanto para o fluxo de dados do programa. Neste texto ografo AODU é definido a partir do bytecode Java produto da compilação/com-binação de programas escritos na linguagem AspectJ, seguindo o trabalho deVincenzi et al. [Vincenzi et al. 2005], como discutido na seção anterior.

A representação gráfica do AODU é semelhante à do DU apresentado naseção anterior, porém, neste caso existe um tipo de nó extra:

• Nós transversais são representados por elipses tracejadas, com infor-

36

Teste de Software OO e OA: Teoria e Prática

mação de qual tipo de adendo afeta aquele ponto (posterior, anterior oude contorno – before, after ou around), e a qual aspecto pertence. Porexemplo, se há uma interação com um adendo posterior de um aspectoAspect em um certo ponto, o nó transversal correspondente é adicionadocom a informação � a f ter−Aspect�.

1 public aspect Timing {2 public long Customer.totalConnectTime = 0;34 public long5 getTotalConnectTime(Customer cust) {6 return cust.totalConnectTime;7 }89 private Timer Connection.timer =

10 new Timer();1112 public Timer getTimer(Connection conn) {13 return conn.timer;14 }15 after (Connection c) returning () :16 target (c) && call ( void17 Connection.complete()) {18 getTimer(c).start();19 }20 pointcut endTiming(Connection c): target (c)21 && call ( void Connection.drop());2223 after (Connection c) returning () :24 endTiming(c) {25 getTimer(c).stop();26 c.getCaller().totalConnectTime +=27 getTimer(c).getTime();28 c.getReceiver().totalConnectTime +=29 getTimer(c).getTime();30 }31 }3233 public aspect Billing {34 declare precedence: Billing, Timing;35 public static final long LOCAL_RATE = 3;36 public static final long37 LONG_DISTANCE_RATE = 10;38 public static final long39 MOBILE_LD_RECEIVER_RATE = 5;4041 public Customer Connection.payer;42 public Customer getPayer(Connection conn)43 { return conn.payer; }

44 after (Customer cust) returning45 (Connection conn): args (cust, ..)46 && call (Connection+.new(..))47 { conn.payer = cust; }4849 public abstract long Connection.callRate();5051 public long LongDistance.callRate() {52 return LONG_DISTANCE_RATE;53 }5455 public long Local.callRate() {56 return LOCAL_RATE;57 }5859 after (Connection conn) returning () :60 Timing.endTiming(conn) {61 long time = Timing.aspectOf().62 getTimer(conn).getTime();63 long rate = conn.callRate();64 long cost = rate * time;65 if (conn.isMobile()) {66 if (conn instanceof LongDistance) {67 long receiverCost =68 MOBILE_LD_RECEIVER_RATE* time;69 conn.getReceiver().addCharge70 (receiverCost);71 }72 }73 getPayer(conn).addCharge(cost);74 }7576 public long Customer.totalCharge = 0;77 public long getTotalCharge(Customer cust)78 { return cust.totalCharge; }7980 public void Customer.addCharge( long charge)81 { totalCharge += charge; }82 }

Figura 1.8. Código dos aspectos Timing e Billing daaplicação de telefonia

Na Figura 1.8 é mostrada outra parte do código-fonte da aplicação de tele-fonia apresentada nas Seções 1.2.1 e 1.2.2, referente aos aspectos Timing eBilling . O mesmo construtor da classe Call (Figura 1.6), utilizado na seçãoanterior para mostrar o DU, é utilizado para demonstrar o AODU. Como o as-pecto Billing possui um adendo para atribuir a tarifa da chamada ao clienteno momento da instanciação de alguma classe filha de Connection (linhas45–48), o construtor de Call é afetado por esse adendo nos dois pontos emque as instâncias de Local (linha 12 na Figura 1.6) e LongDistance (linhas14–15 na Figura 1.6) são criadas. Na Figura 1.9 é mostrada parte do bytecode

37

Masiero, Lemos, Ferrari e Maldonado

gerado para o construtor da classe Call e o AODU correspondente (porémsem informações de fluxo de dados).

0 aload_01 invokespecial #15 <Method Object()>4 aload_0...

27 invokevirtual #30 <Method booleanlocalTo(telecom.Customer)>

30 ifeq 7833 aload_1...52 invokespecial #34 <Method

Local(telecom.Customer,telecom.Customer, boolean)>

...69 invokevirtual #110 <Method void

ajc$afterReturning$telecom_Billing$1$8a338795(telecom.Customer, telecom.Customer,boolean, telecom.Connection)>

72 nop73 astore 475 goto 12078 aload_1...97 invokespecial #37 <Method

LongDistance(telecom.Customer,telecom.Customer, boolean)>

...114 invokevirtual #110 <Method void

ajc$afterReturning$telecom_Billing$1$8a338795(telecom.Customer, telecom.Customer,boolean, telecom.Connection)>

117 nop118 astore 4120 aload_0121 getfield #20 <Field java.util.Vector

connections>124 aload 4126 invokevirtual #41 <Method void

addElement(java.lang.Object)>129 return

Figura 1.9. Bytecode e AODU do construtor da classe Call

Com base no AODU, três critérios de teste estruturais foram definidos[Lemos 2005, Lemos et al. 2005]:

• Todos-Nós-Transversais (Todos-Nósc): Requer que cada nó c ∈ C sejaexercitado por pelo menos um caso de teste. Ou seja, este critério exigeque cada execução de adendo que ocorre na unidade afetada seja al-cançada.

• Todas-Arestas-Transversais (Todas-Arestasc): Requer que cada arestatransversal, ou seja, cada aresta que tem como nó origem ou destino umnó transversal, seja exercitada por pelo menos um caso de teste.

• Todos-Usos-Transversais (Todos-Usosc): Requer que cada par def-usocujo uso está em um nó transversal seja exercitado por pelo menos umcaso de teste.

Os critérios definidos anteriormente para a POO continuam sendo válidostambém nesse contexto pois, no caso da linguagem AspectJ, programas OAsão superconjuntos de programas OO. Assim, também se torna interessantedefinir uma estratégia de aplicação desses critérios, baseada na dificuldade

38

Teste de Software OO e OA: Teoria e Prática

de satisfação dos critérios e em outras propriedades. Porém, até o presentemomento, não se observam investigações nesse sentido, o que indica umanecessidade de trabalhos futuros.

Além disso, é importante salientar que os mesmos problemas encontradosanteriormente para a POO continuam valendo para a POA (como, por exemplo,o problema da não-executabilidade).

Teste Estrutural de Integração OAOs mesmos problemas encontrados no teste de integração de programas

OO também são encontrados no contexto de software OA. Além disso, com aintrodução dos aspectos como módulos de programação, novas questões de-vem ser investigadas no que diz respeito à integração interna desses módulos(por exemplo, adendos de um aspecto que interagem com métodos do mesmoaspecto); integração com as classes; e integração de aspectos com aspectos(quando esses interagem em pontos de junção comuns).

Considerando um programa OA, um defeito em particular pode estar locali-zado em três locais diferentes: na lógica de um adendo; na lógica dos módulosbase; ou na lógica de composição (no caso deste texto no conjunto de junção),que define onde os adendos irão executar [Alexander 2003]. Como um adendoé uma construção similar a método, e o programa-base no geral é compostode classes, poucas adaptações são necessárias para a aplicação do teste es-trutural nessas unidades, como visto anteriormente. Entretanto, nota-se umanecessidade maior de adaptações no que diz respeito ao teste da lógica decomposição (ou dos conjuntos de junção), para o teste eficaz das regras decomposição, procurando identificar defeitos no que diz respeito à seleção dospontos de junção.

Detalhes sobre o teste de integração OA não são tratados aqui, já que nestetexto é dada ênfase ao teste de unidade. No entanto, na Seção 1.3.5 algumasoutras questões gerais são discutidas, sob a ótica do teste de mutação.

A seguir são discutidas as adaptações do teste de mutação para sua apli-cação nos contextos de software OO e OA.

1.3.4. Teste de Mutação de Programas OOAs linguagens de programação possuem características próprias que impli-

cam na definição dos operadores de mutação, embora os mesmos conceitospossam ser aplicados independentemente da linguagem na qual o produto aser testado esteja escrito [Delamaro et al. 2001]. Por outro lado, linguagenscom construções sintáticas semelhantes possibilitam que operadores de mu-tação possam ser aplicados indistintamente em produtos gerados nessas lin-guagens, ou requerem alterações simples na descrição dos operadores. Porexemplo, as linguagens C e C++ permitem a declaração de variáveis globais,porém o conceito de variável global não existe na linguagem Java. Portanto,a princípio, operadores de mutação para programas C que fazem alteraçõesrelacionadas a esses elementos são aplicáveis a programas C++, mas não aprogramas Java. Por outro lado, tanto em Java quanto em C++, os atributos de

39

Masiero, Lemos, Ferrari e Maldonado

uma classe podem ser considerados como variáveis globais, pois são visíveispara todos os métodos pertencentes à classe. Dessa forma, o conjunto de va-riáveis globais na aplicação de teste de mutação em programas Java pode serrestringido ao conjunto dos atributos da classe em teste.

Para o teste de mutação de programas OO, escritos em C++ e Java, Vin-cenzi [2004] avaliou a aplicabilidade de conjuntos de operadores de mutaçãodefinidos originalmente para serem aplicados em teste de mutação de pro-gramas C [Agrawal et al. 1989, Delamaro 1997, Delamaro et al. 2001] e Java[Ma et al. 2002]. Vincenzi procurou adaptar a aplicação destes operadorespara os níveis de teste intra-método, inter-método e inter-classe. Cada um dosconjuntos foi avaliado e, quando necessário, foram propostas as adaptações necessárias.

Vale ressaltar que cada linguagem possui particularidades. Sendo assim,dependendo do tipo de defeito que se deseja enfatizar no teste, novos ope-radores de mutação podem ser definidos para modelá-los. Por exemplo, emalguns pontos, a linguagem C++ pode ser considerada mais flexível do queJava pela possibilidade de se definir heranças múltiplas e sobrecargas de ope-radores. Novos operadores de mutação poderiam ser propostos para tratarexclusivamente esses defeitos.

A aplicação do teste de mutação de programas OO nos níveis de unidadee integração é discutida a seguir. Os conjuntos de operadores para cada fasede teste são apresentados, inclusive suas aplicabilidades em programas Javae C++.

Teste de Mutação de Unidade (Teste Intra-Método)Agrawal et. al [1989] definiram um conjunto de 80 operadores de mutação

para teste de unidade de programas escritos na linguagem C. Os operado-res são distribuídos em quatro classes: cinco operadores para mutação deconstantes, 16 operadores para mutação de comandos, 12 operadores paramutação de variáveis e 47 operadores para mutação de operadores6. Cadaclasse de operadores é formada por um conjunto de operadores que modelamdiferentes defeitos em estruturas de um mesmo tipo. Por exemplo, o operadorSWDD (while Replacement by do-while ), que pertence à classe de ope-radores para mutação de comandos, substitui o comando de iteração whilepelo comando do-while . A letra inicial do nome de cada operador possibilitaa identificação da classe à qual esse operador pertence, sendo elas C – cons-tante, S – comando (statement), V – variável e O – operador. As demais letrassão utilizadas para descrever o operador, como no exemplo apresentado.

Analisando a aplicabilidade dos 80 operadores para as linguagens C++ e

6 Neste texto, o termo “operador” é utilizado tanto para se referir a um operador de muta-ção quanto a um operador tradicional de uma linguagem de programação (por exemplo,operadores aritméticos ou lógicos). Se a utilização do termo causar ambigüidade, otermo completo (por exemplo, “operador de mutação” ou “operador aritmético”) será uti-lizado.

40

Teste de Software OO e OA: Teoria e Prática

Java, Vincenzi [2004] concluiu que todos são aplicáveis a C++, por se tratar deum superconjunto da linguagem C, e 59 são aplicáveis a Java. Dentre os ope-radores não aplicáveis a Java, estão um operador que trata do comando goto ,cinco operadores que são específicos para mutação de ponteiros e registros(struct ), e 15 operadores de mutação de operadores.

Vincenzi também definiu sete novos operadores para tratar de característi-cas específicas da linguagem Java, sendo que três desses também são aplicá-veis a C++. Os quatro novos operadores exclusivos para Java foram definidospara tratar dos comandos break e continue , enquanto os três aplicáveistanto a Java quanto C++ foram definidos para tratar de variáveis de referência.A descrição desses operadores é apresentada na Tabela 1.1. A descrição dosdemais operadores pode ser encontrada no trabalho de Agrawal et al. [1989].

Tabela 1.1. Novos operadores de mutação de unidade deprogramas OO

Operador DescriçãoVGCR Troca uma referência de classe por todas as demais referências de classe globais

do mesmo tipo.VLCR Troca uma referência de classe por todas as demais referências de classe locais

do mesmo tipo.VCAR Troca as referências a um atributo da classe por todas as outras referências a

atributos do mesmo tipo.SBLR Troca os comandos break , com ou sem rótulos, pelos demais comandos break

e break rotulados válidos da unidade.SCLR Troca os comandos continue , com ou sem rótulos, pelos demais comandos

continue e continue rotulados válidos da unidade.SLBC Troca os rótulos dos comandos break pelos rótulos dos comandos continue

válidos da unidade.SLCB Troca os rótulos dos comandos continue pelos rótulos dos comandos break

válidos da unidade.

O conjunto final de operadores ficou totalizado em 66 operadores para Javae 83 operadores para C++. Na Tabela 1.2 (adaptada do trabalho de Vincenzi[2004]) está sumarizado o conjunto resultante de operadores. A primeira co-luna da tabela contém as siglas que representam o nome dos operadores. Osímbolo “�” indica que esse operador não estava presente no conjunto inicial.A segunda e terceira coluna são utilizadas para indicar se o operador é aplicá-vel para as linguagens Java e C++, respectivamente. O símbolo “

√” significa

que o operador é aplicável para a respectiva linguagem, e “∅” significa queo operador não é aplicável. Ainda, “M” significa que o operador não é direta-mente aplicável, mas foi adaptado gerando um ou mais dos novos operadorespropostos por Vincenzi.

Na Figura 1.10 é apresentado um exemplo de aplicação de um operadorde mutação em um método de um programa Java. O método merge utilizadonesse exemplo foi obtido do classe Call , que compõe a aplicação de telefoniaintroduzida na seção 1.2.1. O operador utilizado é o operador SMVB (MoveBrace Up or Down), que move o “} ” que encerra um bloco de comandos de

41

Masiero, Lemos, Ferrari e Maldonado

Tabela 1.2. Operadores de mutação de unidade

Operador Java C++CGCR

√ √

CLCR√ √

CGSR√ √

CLSR√ √

CRCR√ √

Total 5/5 5/5(a) Mutação de Constantes

Operador Java C++VASM

√ √

VDTR√ √

VGAR√ √

VGPR M√

� VGCR√ √

VGSR√ √

VGTR M√

VLAR√ √

VLPR M√

� VLCR√ √

VLSR√ √

VLTR M√

VSCR M√

� VCAR√ √

VTWD√ √

Total 10/15 15/15(b) Mutação de Variáveis

Operador Java C++SBRC

√ √

SBRn√ √

SCRB√ √

SCRn√ √

SDWD√ √

SGLR M√

� SBLR√

∅� SCLR

√∅

� SLBC√

∅� SLCB

√∅

SMTC√ √

SMTT√ √

SMVB√ √

SRSR√ √

SSDL√ √

SSOM√ √

SSWM√ √

STRI√ √

STRP√ √

SWDD√ √

Total 19/20 16/20(c) Mutação de Comandos

Operador Java C++OAAA

√ √

OAAN√ √

OABA√ √

OABN√ √

OAEA√ √

OALN ∅√

OARN ∅√

OASA√ √

OASN√ √

OBAA√ √

OBAN√ √

OBBA√ √

OBBN√ √

OBEA√ √

OBLN ∅√

OBNG√ √

OBRN ∅√

OBSA√ √

OBSN√ √

OCNG√ √

OCOR√ √

OEAA√ √

OEBA√ √

OESA√ √

OIPM ∅√

OLAN ∅√

OLBN ∅√

OLLN√ √

OLNG√ √

OLRN ∅√

OLSN ∅√

OMMO√ √

OPPO√ √

ORAN ∅√

ORBN ∅√

ORLN ∅√

ORRN√ √

ORSN ∅√

OSAA√ √

OSAN√ √

OSBA√ √

OSBN√ √

OSEA√ √

OSLN ∅√

OSRN ∅√

OSSA√ √

OSSN√ √

Total 32/47 47/47(d) Mutação de Operadores

42

Teste de Software OO e OA: Teoria e Prática

um laço para cima e para baixo, excluindo ou incluindo comandos no bloco.O código original do adendo é apresentado na Figura 1.11(a), e os mutantesgerados são apresentados nas Figuras 1.11(b), 1.11(c) e 1.11(d).

public void merge(Call other){for (Enumeration e =

other.connections.elements();e.hasMoreElements();){

Connection conn =(Connection)e.nextElement();

other.connections.removeElement(conn);connections.addElement(conn);

}}

public void merge(Call other){for (Enumeration e =

other.connections.elements();e.hasMoreElements();){

Connection conn =(Connection)e.nextElement();

other.connections.removeElement(conn);}

connections.addElement(conn);}

(a) Método Original (b) Mutante 1

public void merge(Call other){for (Enumeration e =

other.connections.elements();e.hasMoreElements();){

Connection conn =(Connection)e.nextElement();

}other.connections.removeElement(conn);connections.addElement(conn);

}

public void merge(Call other){for (Enumeration e =

other.connections.elements();e.hasMoreElements();){

}Connection conn =

(Connection)e.nextElement();other.connections.removeElement(conn);connections.addElement(conn);

}

(c) Mutante 2 (d) Mutante 3

Figura 1.10. Exemplo de mutação de um método escrito em Java

Na Seção 1.4.3 é apresentado um exemplo de aplicação do teste de muta-ção de unidade em programas OO, inclusive com os casos de teste que podemser utilizados para matar os mutantes gerados e a identificação de possíveismutantes equivalentes.

Teste de Mutação de Integração (Teste inter-método e inter-classe)Vincenzi [2004] adaptou o critério Mutação de Interface

[Delamaro et al. 2001] para o teste de mutação inter-método. Conformeapresentado na Seção 1.2, a principal diferença entre os operadores demutação de unidade e mutação de interface está nos pontos do códigoque são enfatizados para serem mutados. Os operadores de mutação deinterface estão relacionados aos pontos de comunicação entre duas unidades.Considerando duas unidades u1 e u2, com u1 chamando u2, as mutações sãorealizadas tanto nos pontos em que u1 faz chamadas a u2 quanto nos pontosrelacionados à interface de u2 como, por exemplo, variáveis de retorno de u2e variáveis globais. Os operadores foram agrupados de acordo com o localde aplicação, podendo ser tanto na unidade chamadora quanto na unidadechamada. Operadores do Grupo I, num total de 24, são aplicados em u2.Operadores do Grupo II são aplicados em u1, totalizando nove operadoresnesse grupo. A descrição desses operadores pode ser encontrada no trabalhode Delamaro [1997] e Delamaro et al. [2001a].

Os operadores de mutação de interface foram originalmente definidos para

43

Masiero, Lemos, Ferrari e Maldonado

o teste de programas C. Assim, Vincenzi [2004] analisou sua aplicabilidadepara o teste de programas OO escritos nas linguagens C++ e Java. De acordocom a análise, todos os operadores são aplicáveis para as duas linguagens,bastando redefinir os conjuntos de variáveis envolvidos na aplicação dos ope-radores.

Ainda no contexto de teste de integração, Ma et al. [2002] definiram umconjunto de operadores de mutação para o teste inter-classe. Esses operado-res foram projetados para tratar de defeitos específicos aos novos conceitos econstruções introduzidos pelo paradigma OO, entre eles encapsulamento, he-rança e polimorfismo. O conjunto, composto por 24 operadores, foi elaboradocom base em taxonomias de defeitos apresentadas nos trabalhos de Kim etal. [2000], Offutt et al. [2001] e Chevalley e Thévenod-Fosse [2003]. Além deapresentarem uma taxonomia de defeitos, Kim et al. e Chevalley e Thévenod-Fosse propuseram também operadores de mutação para modelar os defeitospresentes em suas taxonomias. Ressalta-se que os defeitos enfatizados porKim et al. contemplam defeitos de programas OO em geral, embora tenhamsido baseados na linguagem Java. Já os defeitos enfatizados por Chevalley eThévenod-Fosse referem-se exclusivamente a programas escritos em Java.

Esses operadores foram incluídos no trabalho de Ma et al., que estenderamo conjunto definindo novos operadores, além de redefinir alguns já existentes.Os 24 operadores resultantes foram agrupados de acordo com as caracterís-ticas e conceitos do paradigma OO e os defeitos relacionados, sendo eles:ocultamento de informação (um operador); herança (sete operadores); poli-morfismo (quatro operadores); sobrecarga de métodos (quatro operadores);características específicas de Java (quatro operadores); e enganos comuns deprogramação (quatro operadores). A descrição desses operadores pode serencontrada no trabalho de Ma et al. [2002].

Analisando-se a aplicabilidade desses grupos de operadores em progra-mas implementados em C++ e Java, Vincenzi [2004] constatou que apenas ogrupo de operadores que tratam de características específicas da linguagemJava não é aplicável para programas escritos em C++. O restante dos opera-dores é aplicável tanto a C++ quanto a Java.

1.3.5. Teste de Mutação de Programas OAConforme enfatizado na seção 1.2, além dos conceitos do teste de mu-

tação poderem ser aplicados em tipos variados de artefatos conceitualmenteexecutáveis de software como, por exemplo, código-fonte de programas e es-pecificações formais, as similaridades entre diferentes linguagens propiciam areutilização de operadores de mutação indistintamente entre as linguagens oucom pequenas alterações entre suas implementações.

A aplicação do teste de mutação de programas OA nos níveis de unidadee integração é discutida a seguir. A linguagem utilizada como base é a Aspe-ctJ, embora os conceitos possam ser aplicados a programas escritos em ou-tras linguagens OA.

44

Teste de Software OO e OA: Teoria e Prática

Teste de Mutação de Unidade OAEmbora a linguagem AspectJ introduza novas construções que implemen-

tam os conceitos relacionados à orientação a aspectos, as unidades de códigoexecutáveis implementadas nos aspectos (adendos, métodos e métodos inter-tipo declarados) são escritas em código Java nativo. Dessa forma, os operado-res de mutação de unidade de programas Java, apresentados na seção 1.3.4,podem ser diretamente aplicados em unidades de programas AspectJ.

Para efeito de ilustração, na Figura 1.11 é apresentado um exemplo deaplicação de um operador de mutação em um adendo escrito em AspectJ. Ocódigo utilizado nesse exemplo foi obtido do aspecto Timing , que compõe aaplicação de telefonia introduzida nas seções anteriores deste texto. O ope-rador utilizado é o operador OAAA, que realiza a troca de cada operador deatribuição aritmética pelos demais operadores e atribuição aritmética disponí-veis na linguagem. No caso da linguagem Java, os operadores de atribuiçãoaritmética disponíveis são “+=”, “-= ”, “* =”, “/= ” e “%=”. O código original doadendo é apresentado na Figura 1.11(a), e os mutantes gerados são apresen-tados na Figura 1.11(b) (somente a linha alterada é apresentada para cadamutante gerado).

1 public aspect Timing {2 ...3 after (Connection c) returning () :4 endTiming(c) {5 getTimer(c).stop();6 c.getCaller().totalConnectTime +=7 getTimer(c).getTime();8 c.getReceiver().totalConnectTime +=9 getTimer(c).getTime();

10 }11 }

linha 6:c.getCaller().totalConnectTime -=c.getCaller().totalConnectTime * =c.getCaller().totalConnectTime /=c.getCaller().totalConnectTime %=

linha 8:c.getReceiver().totalConnectTime -=c.getReceiver().totalConnectTime * =c.getReceiver().totalConnectTime /=c.getReceiver().totalConnectTime %=

(a) Adendo Original (b) Mutantes Gerados

Figura 1.11. Exemplo de mutação de um adendo escritoem AspectJ

Na Seção 1.4.4 é apresentado um exemplo de aplicação do teste de muta-ção de unidade em programas OA, inclusive com os casos de teste que podemser utilizados para matar os mutantes gerados e a identificação de possíveismutantes equivalentes.

Teste de Mutação de Integração OAEm relação ao teste de integração, novas questões estão envolvidas. Mais

especificamente, essas questões estão relacionadas às construções introduzi-das pelo POA, até então não presentes no teste de programas desenvolvidossob os paradigmas procedimental e OO.

Critérios definidos para o teste de integração, tanto para teste estrutural[Linnenkugel and Müllerburg 1990, Vilela et al. 1999] quanto para o teste de

45

Masiero, Lemos, Ferrari e Maldonado

mutação [Delamaro et al. 2001], enfatizam os pontos de comunicação entreduas unidades e as variáveis envolvidas nessa comunicação. As interfaces en-tre essas unidades, no caso de programas procedimentais ou OO, são bemdefinidas, e os pontos de comunicação (por exemplo, uma chamada a um mé-todo e o retorno após sua execução) são explícitos no código.

Já no caso de programas OA, os conceitos de quantificação e de inconsci-ência [Filman and Friedman 2000] possibilitam a execução dos comportamen-tos transversais (implementados em aspectos) em diversos pontos da execu-ção do programa, sem a necessidade da inserção de chamadas explícitas aesses comportamentos. O próprio fluxo de execução do programa pode sercompletamente alterado. A aplicação de um operador de mutação específicopara atuar nos elementos que definem um conjunto de junção pode aumen-tar ou restringir a abrangência desse conjunto, podendo implicitamente definirnovas interfaces entre as unidades do programa, ou remover uma ou mais in-terfaces anteriormente estabelecidas.

Nesse contexto, o teste de integração pode ser observado sob dois pontosde vista. O primeiro como sendo uma adaptação do critério Mutação de In-terface [Delamaro et al. 2001], de forma a enfatizar os pontos de comunicaçãoentre as unidades nos diversos tipos de interação apresentados na seção 1.3.1.A interface de um adendo, por exemplo, pode ser caracterizada pelos argumen-tos e objetos capturados pelo ponto de junção relacionado a esse adendo, e osoperadores definidos para aplicação do critério poderiam ser aplicados nessespontos, além dos pontos já definidos originalmente no critério.

O segundo, por sua vez, relaciona-se com outros conceitos e construçõesintroduzidos pela orientação a aspectos, por exemplo, conjuntos de junção eprecedência de aspectos. Existe ainda a necessidade de se definir um con-junto de operadores de mutação específico para atuar sobre esses elementos.Nessa linha, uma proposta inicial foi apresentada por Mortensen e Alexander[2004], envolvendo a aplicação de uma abordagem mista de teste estruturale de mutação para programas OA escritos em AspectJ, tentando mapear ostipos de defeitos enfatizados à taxonomia de defeitos proposta por Alexanderet al. [2004]. Especificamente em relação ao teste de mutação, são definidostrês operadores de mutação que atuam: na definição de conjuntos de junção;aumentando ou reduzindo sua abrangência; e na precedência de aspectos. Noentanto, somente uma definição de alto-nível dos operadores é apresentada,não entrando em detalhes de como esses operadores podem ser aplicados.

Ressalta-se ainda que essas são idéias iniciais, existindo a necessidade deuma formalização, de modo a possibilitar a aplicação sistemática do critério eo desenvolvimento de mecanismos de apoio automatizado.

A seguir são apresentados exemplos da aplicação dos critérios discutidosno decorrer deste texto. Para os critérios estruturais, são discutidas as ferra-mentas JaBUTi (OO) e JaBUTi/AJ (OA), incluindo uma breve apresentação deseus aspectos operacionais. Apresenta-se também uma aplicação prática doteste de mutação em programas OO e OA.

46

Teste de Software OO e OA: Teoria e Prática

1.4. Automatização e Exemplos de AplicaçãoA atividade de teste, se realizada manualmente, geralmente é propensa a

erros e limitada a aplicações de pequeno porte. Nesse contexto, ferramentasde teste podem auxiliar na automatização dessa tarefa, permitindo a aplicaçãoprática de critérios de teste, o teste de programas maiores e mais complexos, oapoio a estudos experimentais e a transferência das tecnologias de teste paraa indústria. Além disso as ferramentas de teste possibilitam a realização detestes de regressão, utilizados quando evoluções são feitas no programa e oscasos de teste armazenados são executados novamente para a validação daaplicação modificada [Domingues 2002].

Para o teste de programas OO em C++ e Java, existem várias ferramen-tas e, entre elas, podem ser citadas: C++ Test, JProbe Suite, JTest, Pano-rama C/C++, Panorama for Java e xSuds Toolsuite. A maioria delas apóiao teste estrutural e algumas delas apóiam o teste funcional, além de apre-sentarem algumas outras funcionalidades de análise do programa em teste[Domingues 2002].

Nesta seção são utilizadas as ferramentas JaBUTi e JaBUTi/AJ, construí-das para apoiar o teste estrutural de unidade de programas OO e OA, parademonstrar a automatização da atividade de teste utilizando critérios estrutu-rais. Em seguida apresenta-se a aplicação do teste de mutação de unidade emprogramas OO e OA com o auxílio de exemplos.

1.4.1. A Ferramenta JaBUTiA ferramenta JaBUTi (Java Bytecode Understanding and Testing)

[Vincenzi 2004, Vincenzi et al. 2005], desenvolvida no ICMC/USP em colabo-ração com a Fundação Eurípedes Soares da Rocha (UNIVEM) de Marília/SPe a Universidade do Texas em Dallas/USA, visa a ser um ambiente completopara o entendimento e teste de programas e componentes Java. A idéia bá-sica da ferramenta é viabilizar o teste de programas Java em nível de bytecode,possibilitando, com isso, não somente o teste de programas Java para os quaiso código-fonte esteja disponível, mas também o teste de componentes Java.

JaBUTi fornece ao testador diferentes critérios de teste estruturais para aanálise de cobertura, um conjunto de métricas estáticas para se avaliar a com-plexidade das classes que compõem do programa/componente, e implementa,ainda, algumas heurísticas de particionamento de programas que visam a au-xiliar a localização de defeitos. Neste texto, é dada ênfase à parte responsávelpela análise de cobertura. Mais informações sobre as demais funcionalidadesda ferramenta podem ser obtidas no trabalho de Vincenzi [2004].

Considerando o suporte à análise de cobertura de programas Java, a fer-ramenta implementa atualmente oito critérios de teste intra-métodos definidospor Vincenzi [2004] , sendo quatro critérios de Fluxo de Controle (Todos-Nósei,Todos-Nósed, Todas-Arestasei,Todas-Arestased) e quatro critérios de Fluxo deDados (Todos-Usosei, Todos-Usosed, Todos-Pot-Usosei e Todos-Pot-Usosed).

No que diz respeito a esses critérios, estudos demonstram que, para

47

Masiero, Lemos, Ferrari e Maldonado

Figura 1.12. Gerenciador de projetos de teste da JaBUTi

programas C, conjuntos de teste que determinam as maiores coberturastêm uma maior probabilidade de detectar defeitos no programa em teste[Wong and Mathur 1995]. O mesmo se aplica para Java sem perda de ge-neralidade. Uma vez que, em geral, existe um grande número de requisitosde teste para serem cobertos, uma característica interessante da ferramentaJaBUTi é a utilização de cores diferentes dando indicações ao testador parafacilitar a geração de casos de teste que satisfaçam um maior número de re-quisitos em menor tempo. As diferentes cores representam diferentes pesosque são associados aos requisitos de teste de cada critério. Informalmente,os pesos correspondem ao número de requisitos de teste que são cobertosquando um requisito de teste particular é satisfeito. Assim, cobrir os requisitosde teste de maior peso leva a um aumento na cobertura de forma mais rápida.

Para ilustrar os aspectos operacionais da JaBUTi , é utilizada a mesma apli-cação de simulação de telefonia apresentada ao longo deste texto. Em parti-cular é utilizada a classe Call , cujo código foi parcialmente listado na Seção1.3.2.

Considerando a utilização da ferramenta JaBUTi via interface gráfica, oprimeiro passo para conduzir uma atividade de teste é a criação de um projetode teste, o qual contém informações sobre as classes a serem testadas. Paraa criação do projeto de teste o testador deve, primeiramente, fornecer o nomede uma classe base a partir da qual as demais classes relacionadas serãoidentificadas. Fornecido o nome da classe base, a ferramenta exibe a janelado Gerenciador de Projeto (Project Manager), como ilustrado na Figura 1.12.Do lado esquerdo dessa janela encontra-se o conjunto completo das classesque foram identificadas a partir da classe base e que podem ser selecionadaspara serem testadas. No exemplo, foi escolhida a classe Call .

Pressionando o botão Ok, JaBUTi cria um novo projeto (Telecom.jbt noexemplo), constrói o grafo DU para cada método de cada classe a ser testada,deriva os requisitos de teste de cada critério, calcula o peso desses requisitos,e apresenta na tela o bytecode de uma das classes sendo testadas, comoilustrado na Figura 1.13(a). Além da visualização do bytecode, a ferramentaoferece ainda a visualização do código fonte (quando se encontra disponível) e

48

Teste de Software OO e OA: Teoria e Prática

do grafo DU de cada método (Figura 1.13(b)).

(a) Bytecode inicial

(b) Código-fonte e DU iniciais

Figura 1.13. Telas iniciais da JaBUTi

49

Masiero, Lemos, Ferrari e Maldonado

Uma vez que o conjunto de requisitos de teste de cada critério foi determi-nado, tais requisitos podem ser utilizados para avaliar a qualidade de um con-junto de teste existente e/ou para desenvolver novos casos de teste visando amelhorar a cobertura dos requisitos pelo conjunto de teste. O testador pode,por exemplo, decidir criar um conjunto de teste com base em critérios de testefuncionais ou mesmo gerar um conjunto de teste ad-hoc e avaliar a coberturadesse conjunto de teste em relação a cada um dos critérios de teste estruturaisda JaBUTi . Por outro lado, o testador pode visualizar o conjunto de requisitosde teste de cada critério gerado para cada um dos métodos das classes sendotestadas, verificar quais deles ainda não foram cobertos por algum caso deteste e então desenvolver um novo caso de teste que satisfaça tais requisitos.As Figuras 1.14(a) e 1.14(b) ilustram parte dos requisitos de teste do construtorda classe Call gerados pelos critérios Todos-Nósei e Todos-Usosei, respecti-vamente.

(a) Todos-Nósei (b) Todos-Usosei

Figura 1.14. Requisitos de teste para dois dos critérioslivres de exceção, para o construtor da classe Call

Ainda, como pode ser observado na Figura 1.14, a ferramenta permite aotestador ativar/desativar diferentes combinações de requisitos de teste, bemcomo marcar um determinado requisito de teste como não-executável quandonão existir um caso de teste capaz de cobri-lo.

A título de ilustração de um processo de teste para a aplicação de telefonia,pode-se utilizar os critérios para testar cada uma das classes envolvidas nosistema. Por exemplo, para a classe Call , cria-se conjuntos de teste paracobrir cada um dos requisitos de teste indicados pela ferramenta, focando-seem cada um dos métodos.

Na Figura 1.15(a) é mostrado o código do método includes da classeCall utilizado para verificar se um dado cliente está ou não incluído em uma

50

Teste de Software OO e OA: Teoria e Prática

(a) Código-fonte (b) DU

Figura 1.15. Código-fonte e DU do método includes daclasse Call, com os requisitos de teste referentes aocritério Todas-Arestas ei destacados

chamada. O atributo connections da classe Call é utilizado para manteruma lista dos clientes incluídos na chamada. Para saber se um dado cli-ente faz parte da chamada, basta fazer um laço sobre os elementos do vetor,verificando se o cliente corresponde a algum desses elementos. Na Figura1.15(a) a ferramenta destaca no código do método os requisitos referentesao critério Todas-Arestasei, depois de todos os nós do método terem sido co-bertos a partir de dois casos de teste (os dois primeiros mostrados na Figura1.16). Os casos de teste são construídos com o apoio do framework JUnit[Beck and Gamma 2006], podendo ser importados para execução na JaBUTi .

Para cobrir o requisito ainda não satisfeito com relação ao critério Todas-Arestasei, o testador pode verificar qual aresta ainda não foi coberta e, paraisso, pode utilizar o grafo DU (mostrado na Figura 1.15(b)). Observando essegrafo e analisando o código-fonte e o bytecode, percebe-se que a aresta quefalta é a 13–37, referente à condição em que a variável booleana result temvalor verdadeiro (ou seja, o cliente está incluído na chamada), e o vetor aindanão foi percorrido inteiramente. Assim, para cobri-la basta criar um caso deteste no qual o cliente faz parte da chamada e está localizado no início dovetor, com outros também conectados no restante do vetor. O terceiro caso deteste apresentado na Figura 1.16 cobre esta condição.

Ao executar esse caso de teste para exercitar tal aresta, percebe-se que oprograma não retorna, entrando em laço infinito. Com isso é detectado um de-feito no programa: como é atribuído o valor verdadeiro para a variável resultnas primeiras iterações do laço, nas próximas iterações, o teste lógico ‘ou’ éavaliado como verdadeiro e, por causa da avaliação curto-circuito utilizada emJava (ou seja, quando a primeira parte da expressão é avaliada como verda-deira, a segunda não chega a ser avaliada), o método nextElement que iterasobre os elementos de connections não é mais chamado. Assim, a condi-ção de saída do método, que é o término da iteração sobre o vetor, nunca ésatisfeita, fazendo com que o laço nunca termine.

Esse exemplo evidencia a utilidade da ferramenta de teste no auxílio para

51

Masiero, Lemos, Ferrari e Maldonado

public void testIncludesTrue() {Customer jim = new Customer("Jim", 650, "1111");Customer mik = new Customer("Mik", 500, "1112");Call c = new Call(jim, mik, false );c.pickup();assertTrue(c.includes(mik));

}

public void testIncludesFalse() {Customer jim = new Customer("Jim", 650, "1111");Customer mik = new Customer("Mik", 500, "1112");Customer luk = new Customer("Luke", 400, "1113");Call c = new Call(jim, mik, false );c.pickup();assertFalse(c.includes(luk));

}

//Cobrindo Todas-Arestas-eipublic void testIncludesEdge() {

Customer jim = new Customer("Jim", 650, "1111");Customer mik = new Customer("Mik", 500, "1112");Customer john = new Customer("John", 500, "1113");Customer cris = new Customer("Cris", 500, "1114");Customer carl = new Customer("Carl", 500, "1115");Customer luke = new Customer("Luke", 500, "1116");

Call c1 = new Call(jim, mik, false );c1.pickup();Call c2 = new Call(john, cris, false );c2.pickup();Call c3 = new Call(carl, luke, false );c3.pickup();c1.merge(c2);c1.merge(c3);assertTrue(c1.includes(jim));

}

Figura 1.16. Casos de teste para cobrir Todos-Nós ei eTodas-Arestas ei do método includes

encontrar defeitos no software. Outro ponto importante a ser notado é que aaplicação dos critérios seria muito complicada sem a utilização de uma ferra-menta como a JaBUTi , pois realizar tal atividade manualmente sem incorrerem erros é, em geral, impraticável.

Os critérios baseados em fluxo de dados não foram explorados nesta seçãodevido a limitações de espaço. Entretanto, em geral, tais critérios mostram-sebastante eficientes para auxiliar o entendimento e o teste dos sistemas, poissão mais difíceis de serem satisfeitos do que os critérios baseados em fluxo decontrole.

1.4.2. A Ferramenta JaBUTi/AJA ferramenta JaBUTi foi estendida para possibilitar a utilização dos critérios

estruturais orientados a aspectos, ou seja, os critérios Todos-Nós-Transversais,Todas-Arestas-Transversais e Todos-Usos-Transversais [Lemos 2005]. Nessaversão a ferramenta foi denominada JaBUTi/AJ.

Para o teste estrutural da aplicação de telefonia em sua versão OA, pri-meiramente deve-se criar um projeto na ferramenta JaBUTi/AJ, escolhendo osarquivos relevantes, inclusive os aspectos, similarmente ao que foi feito coma ferramenta JaBUTi . Nesta seção ênfase é dada aos critérios OA aborda-

52

Teste de Software OO e OA: Teoria e Prática

dos e, desse modo, os módulos interessantes a serem testados são: a classeCall e o aspecto Timing , pois eles são afetados por adendos. Na Figura 1.17são mostrados os elementos requeridos para o critério Todos-Nós-Transversais(All-Nodes-c), para todas as unidades sendo testadas. A classe Call aparececom cinco requisitos de teste, o que quer dizer que ela é afetada por adendosem cinco pontos; e o aspecto Timing aparece com dois elementos requeridos.É interessante notar que a informação dada por esse critério é valiosa não sópara o testador mas também para o desenvolvedor pois, com os elementosrequeridos, o testador/desenvolvedor sabe exatamente em quais classes/as-pectos os adendos estão definindo comportamento e também a quantidade deadendos afetando as classes/aspectos.

Figura 1.17. Elementos requeridos para o critério todos-nós-transversais para a aplicação de telefonia

Para testar a classe Call a partir do critério Todos-Nós-Transversais, é ne-cessário saber quais métodos estão sendo afetados pelos adendos. Para isso,o testador pode visualizar tanto o bytecode quanto o código-fonte e observarem quais pontos a ferramenta indica os requisitos de teste. Na Figura 1.18 émostrada a tela da ferramenta JaBUTi/AJ com o código-fonte da classe Call ,com os requisitos de teste referentes ao critério Todos-Nós-Transversais des-tacados no código. Na verdade, percebe-se que, nesse caso, os elementosrequeridos acabam sendo os pontos de junção afetados pelos adendos. Porexemplo, no caso do construtor de Call , deve haver algum adendo que definecomportamento nas chamadas aos construtores de LongDistance e Local .Isso pode ser conferido visualizando o grafo AODU do construtor de Call (Fi-gura 1.19(a)). Os nós transversais 33 e 78 indicam que um adendo posterior

53

Masiero, Lemos, Ferrari e Maldonado

do aspecto Billing afeta aqueles pontos (esse é o adendo responsável poratribuir a fatura pela ligação ao cliente chamador). A partir daí o critério Todos-Nós-Transversais requer que esses nós sejam cobertos, ou seja, que existamcasos de teste que os exercitem.

Figura 1.18. Código-fonte da classe Call com os elementosrequeridos do critério Todos-Nós-Transversaisem destaque

Analisando a lógica do construtor de Call , para cobrir os nós transversaissão necessários dois casos de teste: um que realize uma chamada local e ou-tro que realize uma chamada de longa distância. Como o adendo que atribuia fatura ao cliente chamador deve distinguir entre chamadas locais e de longadistância, aqui o testador já pode ter evidências de que o aspecto está imple-mentado corretamente, já que o adendo afeta o ponto onde uma chamada localé criada e também o ponto onde uma chamada de longa distância é criada. Apartir dos dois casos de teste, que testarão se o método realmente cria umachamada local quando os clientes possuem o mesmo código de área e umachamada de longa distância quando os clientes possuem códigos de área di-ferentes, é possível cobrir os dois nós transversais referentes às execuções doadendo para os dois casos.

Quanto ao método hangup , também afetado por adendos, a lógica é a se-

54

Teste de Software OO e OA: Teoria e Prática

guinte: quando a ligação é concluída, é necessário terminar todas as conexõesrelacionadas a ela, o que é feito por meio de um laço. Como o adendo do as-pecto de cronometragem deve parar o cronômetro toda vez que uma conexãoé terminada, ele é executado depois de cada chamada ao método drop . Jáno caso do aspecto de faturamento, este também deve afetar o mesmo ponto,pois cada vez que uma conexão é terminada, também é necessário calcular ocusto da chamada e atribuí-lo à fatura do cliente chamador. Na Figura 1.19(b)é mostrado o grafo AODU do método hangup , com as execuções dos adendosrepresentadas pelos nós transversais 11 e 32. Para cobrir esses nós trans-versais é necessário um caso de teste que inicie uma chamada e a termine.Quando o método hangup é chamado, os adendos são executados no términode cada conexão relacionada com a chamada.

Aqui o testador também pode ter evidências de que os aspectos estão im-plementados corretamente e, além disso, que estão interagindo de forma cor-reta já que o aspecto de faturamento é acionado após o aspecto de cronome-tragem. Nota-se que a precedência dos aspectos é importante nesse caso,pois se o aspecto de faturamento agisse antes do aspecto de cronometragem,o sistema falharia. Esse tipo de defeito faz parte do modelo de defeitos de-finido por Alexander [Alexander et al. 2004]. Também é importante notar queesse caso evidencia a eficiência da abordagem, no que diz respeito a auxiliarna descoberta desse tipo de defeito.

(a) Grafo AODU do construtor da classeCall

(b) Grafo AODU do método hangup

Figura 1.19. Grafos AODU do construtor e do método daclasse Call

55

Masiero, Lemos, Ferrari e Maldonado

O método pickup é utilizado para simular o momento em que o cliente tirao telefone do gancho e completa a ligação, sendo afetado pelo adendo poste-rior do aspecto Timing . Isso é feito para que o aspecto inicie o cronômetrotoda vez que uma conexão é completada, por meio do adendo. Para cobrir onó transversal é necessário apenas um caso de teste que execute o método,completando uma ligação e verificando que o cronômetro é iniciado.

No caso do aspecto Timing , os dois adendos posteriores que iniciam eterminam o cronômetro são afetados pelos adendos posteriores do aspectoTimerLog , para que seja registrado o tempo de início e parada do cronôme-tro. Nesses pontos podem ser observadas interações aspecto-aspecto, pois oaspecto TimerLog afeta o aspecto Timing . Para cobrir os nós transversais énecessário um caso de teste que inicie e termine uma ligação, sensibilizandoos adendos do aspecto Timing que iniciam e terminam o cronômetro e, porconseqüência, sensibilizando também o aspecto TimerLog .

A título de ilustração, três defeitos referentes à POA foram inseridos na apli-cação de telefonia: um relacionado com a precedência dos aspectos (foi tro-cada a precedência dos aspectos, ou seja Billing → Timing , por Timing →Billing ) e dois relacionados com as definições dos conjuntos de junção (maisespecificamente aos conjuntos de junção definidos em Timing , que identifi-cam o início e o fim das ligações).

Para detectar esses defeitos, o testador pode utilizar o grafo AODU do mé-todo hangup : com a alteração do conjunto de junção que define quando aschamadas terminam, o adendo de Billing que calcula a fatura deveria afetaresse ponto, mas percebe-se a falta do nó transversal. Com um caso de testeque faça asserções com relação à fatura, o defeito é revelado.

O segundo defeito também é relacionado com definições de conjuntos dejunção, porém agora sobre o conjunto de junção que define o início da cha-mada. Tal defeito pode ser detectado no teste do método pickup , no qualdeveria ser iniciado o cronômetro da chamada. Com um caso de teste queverifica o tempo da conexão o defeito é revelado.

O terceiro defeito refere-se à precedência dos aspectos e também podeser detectado no teste do método hangup , pois ainda assim a fatura não serácalculada corretamente, já que o cronômetro só é parado depois da execuçãodo adendo de fatura (e o cálculo da fatura depende da execução do adendo decronometragem). Esse defeito também pode ser evidenciado a partir da obser-vação do AODU, pois o nó transversal referente ao adendo do cálculo da faturaprecede o referente ao adendo da cronometragem (inverso ao que aparece naFigura 1.19(b), com o programa correto). Dois casos de teste escritos no JUnit,que cobrem os nós transversais e que auxiliam o testador a encontrar os trêsdefeitos, estão apresentados na Figura 1.20.

56

Teste de Software OO e OA: Teoria e Prática

public void testCoverCCNodesPickup() {Customer jim = new Customer("Jim", 650, "3361-1111");Customer mik = new Customer("Mik", 650, "3361-1112");

Call c1 = new Call(jim, mik, false );

c1.pickup();assertTrue(Timing.aspectOf().getTotalConnectTime(jim) == 0);

}

public void testCoverCCNodesCallHangup() {Customer jim = new Customer("Jim", 650, "3361-1111");Customer mik = new Customer("Mik", 650, "3361-1112");

Call c1 = new Call(jim, mik, false );

c1.pickup();wait(2.0);c1.hangup();assertTrue((Timing.aspectOf().getTotalConnectTime(jim) > 150) &&

(Timing.aspectOf().getTotalConnectTime(jim) < 250));assertTrue((Billing.aspectOf().getTotalCharge(jim) > 450) &&

(Billing.aspectOf().getTotalCharge(jim) < 750));}

Figura 1.20. Casos de teste que revelam os defeitos re-lacionados com a POA

1.4.3. Aplicação do Teste de Mutação de Unidade em ProgramasOO

Nesta seção é apresentado um exemplo de aplicação do teste de mutaçãode unidade de programas OO escritos em Java. Nesse exemplo é utilizada amesma aplicação de simulação de telefonia apresentada ao longo deste texto.Em particular, são consideradas somente as funcionalidades da versão OO daaplicação, cujo teste estrutural foi apresentado na seção 1.4.1 e no qual seidentificou um defeito no método includes da classe Call . Esse defeito foicorrigido na aplicação utilizada nesta seção.

Na Figura 1.21 destaca-se parte do código da classe Call . Em particular,apresenta-se a implementação do método construtor da classe e do métodomerge . Para efeito ilustrativo, alguns dos operadores de mutação de unidadeapresentados na Seção 1.3.4 são selecionados para serem aplicados nessesmétodos. A descrição desses operadores é apresentada na Tabela 1.3.

Tabela 1.3. Operadores selecionados para o exemplo

Operador DescriçãoSSDL Remove cada um dos comandos da unidade.STRI Troca o comando if(e) pelos comandos if(trap_on_true(e)) e

if(trap_on_false(e)) , interrompendo a execução do programa quandoa expressão testada for verdadeira ou falsa, respectivamente.

OCNG Insere operador de negação em predicados (comandos condicionais).SMTC Introduz, no início de cada laço, uma chamada à função

false_after_nth_loop_iteration(N) , que força o encerramentodo laço após N iterações.

Nas Figuras 1.22, 1.23, 1.24 e 1.25 são apresentados os mutantes gerados

57

Masiero, Lemos, Ferrari e Maldonado

1 public class Call {2 private Customer caller, receiver;3 private Vector connections = new Vector();45 public Call(Customer caller, Customer receiver,6 boolean iM) {7 this .caller = caller;8 this .receiver = receiver;9 Connection c;

10 if (receiver.localTo(caller)) {11 c = new Local(caller, receiver, iM);12 } else {13 c = new LongDistance(caller, receiver, iM);14 }15 connections.addElement(c);16 }1718 ...1920 public void merge(Call other){21 for (Enumeration e =22 other.connections.elements();23 e.hasMoreElements();){24 Connection conn =25 (Connection)e.nextElement();26 other.connections.removeElement(conn);27 connections.addElement(conn);28 }29 }30 }

Figura 1.21. Parte do código da classe Call da aplicaçãode telefonia

por cada um dos operadores da Tabela 1.3. Algumas observações sobre aaplicação dos operadores nesse exemplo:

• O símbolo “>” no início de uma linha indica a linha ou trecho de códigoque foi modificado;

• A aplicação de alguns operadores pode gerar mutantes que não compi-lam. Por exemplo, o operador SSDL, quando aplicado ao método cons-trutor, gerou três desses mutantes7. Dependendo da estratégia ado-tada pelo testador (e possivelmente implementada em uma ferramentade apoio), esses mutantes podem ou não serem gerados durante a apli-cação do respectivo operador de mutação ou, ainda, serem automatica-mente marcados como mortos durante a execução do teste.

• Os mutantes marcados com “eq” foram identificados como equivalentes.• Os métodos utilitários (por exemplo, trap_on_true(e) ) foram imple-

mentadas como métodos estáticos de uma classe auxiliar (Util ), con-forme se pode observar nos mutantes gerados.

• Para o operador SMTC, a função false_after_nth_loop_iter-ation(N) pode ser configurada com qualquer valor para N. Nesseexemplo, a função foi configurada com N=2.

Os casos de teste apresentados na Figura 1.26 foram implementados como objetivo de matar os mutantes dos métodos construtor e merge gerados com

7 Os mutantes não compiláveis não foram listados por questões de espaço.

58

Teste de Software OO e OA: Teoria e Prática

public Call(Customer caller, Customer receiver,boolean iM) {

>this .receiver = receiver;Connection c;if (receiver.localTo(caller)) {

c = new Local(caller, receiver, iM);} else {

c = new LongDistance(caller, receiver, iM);}connections.addElement(c);

}

public Call(Customer caller, Customer receiver,boolean iM) {

this .caller = caller;>

Connection c;if (receiver.localTo(caller)) {

c = new Local(caller, receiver, iM);} else {

c = new LongDistance(caller, receiver, iM);}connections.addElement(c);

}

M1eq M2eq

public Call(Customer caller, Customer receiver,boolean iM) {

this .caller = caller;this .receiver = receiver;Connection c;if (receiver.localTo(caller)) {

c = new Local(caller, receiver, iM);} else {

c = new LongDistance(caller, receiver, iM);}

>}

public void merge(Call other){>

}

M3 M4

public void merge(Call other){for (Enumeration e =

other.connections.elements();e.hasMoreElements();){

Connection conn =(Connection)e.nextElement();

other.connections.removeElement(conn);>

}}

public void merge(Call other){for (Enumeration e =

other.connections.elements();e.hasMoreElements();){

Connection conn =(Connection)e.nextElement();

>connections.addElement(conn);

}}

M5 M6

Figura 1.22. Mutantes gerados pelo operador SSDL

public Call(Customer caller, Customer receiver,boolean iM) {

this .caller = caller;this .receiver = receiver;Connection c;

> if (Util.trap_on_true(receiver.localTo(caller))) {

c = new Local(caller, receiver, iM);} else {

c = new LongDistance(caller, receiver, iM);}connections.addElement(c);

}

public Call(Customer caller, Customer receiver,boolean iM) {

this .caller = caller;this .receiver = receiver;Connection c;

> if (Util.trap_on_false(receiver.localTo(caller))) {

c = new Local(caller, receiver, iM);} else {

c = new LongDistance(caller, receiver, iM);}connections.addElement(c);

}

M7 M8

Figura 1.23. Mutantes gerados pelo operador STRI

a aplicação dos operadores apresentados na Tabela 1.3. Na Tabela 1.4 é apre-sentada a relação dos mutantes mortos por cada caso de teste. Como se pode

59

Masiero, Lemos, Ferrari e Maldonado

public Call(Customer caller, Customer receiver,boolean iM) {

this .caller = caller;this .receiver = receiver;Connection c;

> if (!receiver.localTo(caller)) {c = new Local(caller, receiver, iM);

} else {c = new LongDistance(caller, receiver, iM);

}connections.addElement(c);

}

public void merge(Call other){for (Enumeration e =

other.connections.elements();> !e.hasMoreElements();){

Connection conn =(Connection)e.nextElement();

other.connections.removeElement(conn);connections.addElement(conn);

}}

M9 M10

Figura 1.24. Mutantes gerados pelo operador OCNG

public void merge(Call other){for (Enumeration e = other.connections.elements();

e.hasMoreElements();){> if (Util.false_after_nth_loop_iteration(2)) {

Connection conn = (Connection)e.nextElement();other.connections.removeElement(conn);connections.addElement(conn);

}}

}

M11

Figura 1.25. Mutante gerado pelo operador SMTC

observar, um único caso de teste pode matar mais de um mutante.

Tabela 1.4. Casos de teste X mutantes OO mortosCaso de Teste Mutante

testCallConstruction M3 / M7testCallMerging01 M3 / M7 / M4 / M5 / M10testCallMerging02 M7 / M4 / M6 / M10

testCallInstantiation01 M7testCallInstantiation02 M8

testCallType() M7 / M8 / M9

Após construção e a execução dos seis casos de teste apresentados, oúnico mutante vivo foi o gerado pelo operador SMTC (mutante M11). Durantea construção de um caso de teste para matar esse mutante, observou-se umafalha na sua execução. Ao tentar realizar duas combinações (merging) con-secutivas de chamadas, observou-se no segundo merging que as conexõesda chamada recebida como parâmetro (duas, nesse caso de teste) não eramtodas transferidas para a chamada corrente. O caso de teste projetado é apre-sentado na Figura 1.27.

O código do método merge original é apresentado novamente na Figura1.28(a). Durante a depuração do código, detectou-se o defeito na linha 5. Du-

60

Teste de Software OO e OA: Teoria e Prática

public void testCallConstruction() {Customer jim = new Customer("Jim", 650,

"3361-1111");Customer mik = new Customer("Mik", 650,

"3361-1112");Call c1 = new Call(jim, mik, false );assertTrue(c1.includes(jim));

}

public void testCallInstantiation01() {Customer jim = new Customer("Jim", 650,

"3361-1111");Customer mik = new Customer("Mik", 650,

"3361-1112");try {

Call c1 = new Call(jim, mik, false );assertTrue( true );

}catch (RuntimeException e) {

assertTrue( false );}

}

public void testCallInstantiation02() {Customer jim = new Customer("Jim", 650,

"3361-1111");Customer kat = new Customer("Kat", 750,

"3361-1112");try {

Call c1 = new Call(jim, kat, false );assertTrue( true );

}catch (RuntimeException e) {assertTrue( false );

}}

public void testCallMerging01() {Customer jim = new Customer("Jim", 650,

"3361-1111");Customer mik = new Customer("Mik", 650,

"3361-1112");Customer joe = new Customer("Joe", 650,

"3361-1113");Call c1 = new Call(jim, mik, false );Call c2 = new Call(mik, joe, false );c1.merge(c2);assertTrue(c1.includes(joe));

}

public void testCallMerging02() {Customer jim = new Customer("Jim", 650,

"3361-1111");Customer mik = new Customer("Mik", 650,

"3361-1112");Customer joe = new Customer("Joe", 650,

"3361-1113");Call c1 = new Call(jim, mik, false );Call c2 = new Call(mik, joe, false );c1.merge(c2);assertFalse(c2.includes(mik));

}

public void testCallType() {Customer jim = new Customer("Jim", 650,

"3361-1112");Customer mik = new Customer("Mik", 650,

"3361-1113");ByteArrayOutputStream baos =

new ByteArrayOutputStream();PrintStream ps = new PrintStream(baos, true );System.setOut(ps);Call c1 = jim.call(mik, false );String s1 = baos.toString().trim();assertEquals(s1,

"[new local connection " +"from Jim(650) to Mik(650)]");

}

Figura 1.26. Casos de teste criados para matar mutantes OO

1 public void testCallMerging03() {2 Customer jim = new Customer("Jim", 650, "3361-1111");3 Customer mik = new Customer("Mik", 650, "3361-1112");4 Customer joe = new Customer("Joe", 650, "3361-1113");5 Call c1 = new Call(joe, mik, false );6 Call c2 = new Call(mik, jim, false );7 Call c3 = new Call(mik, joe, false );8 c1.merge(c2);9 c3.merge(c1);

10 assertTrue(c3.includes(jim));11 }

Figura 1.27. Caso de teste projetado para matar o mutante M11

rante a primeira iteração do laço, a conexão corrente é removida do vetor deconexões enumeradas. Com essa remoção, a conexão seguinte passa a cons-tar na posição 0 do vetor. Entretanto, durante a segunda iteração, o índicecriado para percorrer esse vetor já estava apontando para a segunda posiçãodo vetor, o que no estado corrente apontava para um elemento nulo. Portanto,nesse caso, uma conexão não era transferida para a chamada resultante dacombinação (objeto corrente). Na Figura 1.28(b) é apresentado uma possibili-

61

Masiero, Lemos, Ferrari e Maldonado

dade do método merge corrigido.

1 public void merge(Call other){2 for (Enumeration e =3 other.connections.elements();4 e.hasMoreElements(); ){5 Connection conn =6 (Connection)e.nextElement();7 other.connections.removeElement(conn);8 connections.addElement(conn);9 }

10 }

1 public void merge(Call other){2 for (Enumeration e =3 other.connections.elements();4 e.hasMoreElements(); ){5 Connection conn =6 (Connection)e.nextElement();7 connections.addElement(conn);8 }9 other.connections.clear();

10 }

(a) (b)

Figura 1.28. Método merge original e modificado

É importante destacar que durante a aplicação do teste estrutural de uni-dade na classe Call , todos os requisitos gerados para o método merge foramcobertos com um caso de teste. Entretanto, a aplicação do teste estrutural nãofoi suficiente para revelar o defeito encontrado nesse método. Destaca-se comisso a importância de se aplicar diferentes técnicas e critérios de teste de formacomplementar, objetivando-se a melhoria de qualidade do produto final.

Ressalta-se também que após a detecção e correção de um defeito no có-digo do artefato original, como ocorrido nesse exemplo, o processo do teste demutação deve passar novamente pelos quatro passos apresentados na seção1.2. Nesse caso, a geração dos mutantes pode gerar um conjunto de mutantessignificativamente diferente do conjunto originalmente gerado, podendo reque-rer novos casos de teste para matá-los. Além disso, novos defeitos poderãoser inseridos no código durante a correção dos defeitos já encontrados, reque-rendo dessa forma que tanto o programa corrigido quanto os mutantes geradosa partir dele sejam exercitados com o conjunto de teste.

Conforme discutido na Seção 1.2, é indispensável o uso de ferramentas deapoio para que a atividade de teste alcance a qualidade desejada. Em parti-cular, têm sido propostas e implementadas algumas ferramentas de apoio aoteste de mutação de programas OO escritos em Java que utilizam os operado-res de mutação discutidos nesta seção.

1.4.4. Aplicação do Teste de Mutação de Unidade em ProgramasOA

Em continuidade ao exemplo de aplicação do teste de mutação, nesta se-ção é apresentado um exemplo de sua aplicação no teste de unidade de pro-gramas OA escritos em AspectJ, agora considerando a versão OA da aplicaçãode simulação de telefonia. Ressalta-se que dois defeitos foram encontradosnessa aplicação durante o desenvolvimento dos exemplos de teste estruturale de mutação de unidade de programas OO apresentados nas seções 1.4.1 e1.4.3. Esses defeitos foram corrigidos na aplicação utilizada nesta seção.

Em relação aos operadores de mutação, os mesmos operadores utilizados

62

Teste de Software OO e OA: Teoria e Prática

na seção anterior serão aplicados, sendo eles SSDL, STRI, OCNG e SMTC. NaFigura 1.29 destaca-se parte do código do aspecto Billing . Em particular,destaca-se o adendo sobre o qual são aplicados os operadores de mutaçãoselecionados.

1 public aspect Billing {2 ...3 public static final long MOBILE_LD_RECEIVER_RATE = 5;4 ...56 after (Connection conn) returning () :7 Timing.endTiming(conn) {8 long time =9 Timing.aspectOf().getTimer(conn).getTime();

10 long rate = conn.callRate();11 long cost = rate * time;12 if (conn.isMobile()) {13 if (conn instanceof LongDistance) {14 long receiverCost =15 MOBILE_LD_RECEIVER_RATE* time;16 conn.getReceiver().addCharge(receiverCost);17 }18 }19 getPayer(conn).addCharge(cost);20 }21 ...22 }232425 public aspect Timing {26 ...2728 pointcut endTiming(Connection c): target (c)29 && call ( void Connection.drop());30 ...31 }

Figura 1.29. Parte do código dos aspectos Billing eTiming da aplicação de telefonia

Os casos de teste apresentados na Figura 1.30 foram implementados paramatar os mutantes dos métodos construtor e merge gerados nesse exemplo,que são mostrados nas Figuras 1.31, 1.32 e 1.33. Observa-se que o operadorSMTC não produz mutantes para o adendo selecionado.

Na Tabela 1.5 é apresentada a relação dos mutantes mortos por cada casode teste. Da mesma forma que no exemplo apresentado na seção anterior,pode-se observar que um único caso de teste pode matar mais de um mutante.Pode-se observar também que nesse exemplo não foram gerados mutantesque não compilam ou equivalentes.

Tabela 1.5. Casos de teste X mutantes OA mortosCaso de Teste Mutante

testMobileReceiverCharging Ma1 / Ma2 / Ma3 / Ma4 / Ma5 / Ma8 / Ma9testMobileCallCharging Ma4 / Ma5

testNotMobileCallCharging Ma6testLocalMobileCallCharging Ma4 / Ma7

63

Masiero, Lemos, Ferrari e Maldonado

public void testMobileReceiverCharging() {Customer jim = new Customer("Jim", 650,

"3361-1111");Customer mik = new Customer("Mik", 750,

"3361-1112");Call c1 = new Call(jim, mik, true );c1.pickup();wait(2.0);c1.hangup();double result =

Billing.aspectOf().getTotalCharge(mik);assertTrue(result > 0);

}

public void testMobileCallCharging() {Customer jim = new Customer("Jim", 650,

"3361-1111");Customer mik = new Customer("Mik", 750,

"3361-1112");try {

Call c1 = new Call(jim, mik, true );c1.pickup();c1.hangup();assertTrue( true );

}catch (RuntimeException e) {

assertTrue( false );}

}

public void testNotMobileCallCharging() {Customer jim = new Customer("Jim", 650,

"3361-1111");Customer mik = new Customer("Mik", 650,

"3361-1112");try {

Call c1 = new Call(jim, mik, false );c1.pickup();c1.hangup();assertTrue( true );

}catch (RuntimeException e) {assertTrue( false );

}}

public void testLocalMobileCallCharging() {Customer jim = new Customer("Jim", 650,

"3361-1111");Customer mik = new Customer("Mik", 650,

"3361-1112");try {

Call c1 = new Call(jim, mik, true );c1.pickup();c1.hangup();assertTrue( true );

}catch (RuntimeException e) {assertTrue( false );

}}

Figura 1.30. Casos de teste criados para matar mutantes OA

after (Connection conn) returning () :Timing.endTiming(conn) {

long time =Timing.aspectOf().getTimer(conn).getTime();

long rate = conn.callRate();long cost = rate * time;if (conn.isMobile()) {

if (conn instanceof LongDistance) {long receiverCost =

MOBILE_LD_RECEIVER_RATE* time;>

}}getPayer(conn).addCharge(cost);

}

after (Connection conn) returning () :Timing.endTiming(conn) {

long time =Timing.aspectOf().getTimer(conn).getTime();

long rate = conn.callRate();long cost = rate * time;if (conn.isMobile()) {

>}getPayer(conn).addCharge(cost);

}

Ma1 Ma2

after (Connection conn) returning () :Timing.endTiming(conn) {

long time =Timing.aspectOf().getTimer(conn).getTime();

long rate = conn.callRate();long cost = rate * time;

>getPayer(conn).addCharge(cost);

}

Ma3

Figura 1.31. Mutantes gerados pelo operador SSDL

Considerando a necessidade de apoio automatizado à atividade de teste,a ferramenta de apoio ao teste de mutação de unidades programas OO que

64

Teste de Software OO e OA: Teoria e Prática

after (Connection conn) returning () :Timing.endTiming(conn) {

long time =Timing.aspectOf().getTimer(conn).getTime();

long rate = conn.callRate();long cost = rate * time;

> if (Util.trap_on_true(conn.isMobile())) {if (conn instanceof LongDistance) {

long receiverCost =MOBILE_LD_RECEIVER_RATE* time;

conn.getReceiver().addCharge(receiverCost);}

}getPayer(conn).addCharge(cost);

}

after (Connection conn) returning () :Timing.endTiming(conn) {

long time =Timing.aspectOf().getTimer(conn).getTime();

long rate = conn.callRate();long cost = rate * time;if (conn.isMobile()) {

> if (Util.trap_on_true(conn instanceof LongDistance)){

long receiverCost =MOBILE_LD_RECEIVER_RATE* time;

conn.getReceiver().addCharge(receiverCost);}

}getPayer(conn).addCharge(cost);

}

Ma4 Ma5

after (Connection conn) returning () :Timing.endTiming(conn) {

long time =Timing.aspectOf().getTimer(conn).getTime();

long rate = conn.callRate();long cost = rate * time;

> if (Util.trap_on_false(conn.isMobile())) {if (conn instanceof LongDistance) {

long receiverCost =MOBILE_LD_RECEIVER_RATE* time;

conn.getReceiver().addCharge(receiverCost);}

}getPayer(conn).addCharge(cost);

}

after (Connection conn) returning () :Timing.endTiming(conn) {

long time =Timing.aspectOf().getTimer(conn).getTime();

long rate = conn.callRate();long cost = rate * time;if (conn.isMobile()) {

> if (Util.trap_on_false(conn instanceof LongDistance)){

long receiverCost =MOBILE_LD_RECEIVER_RATE* time;

conn.getReceiver().addCharge(receiverCost);}

}getPayer(conn).addCharge(cost);

}

Ma6 Ma7

Figura 1.32. Mutantes gerados pelo operador STRI

after (Connection conn) returning () :Timing.endTiming(conn) {

long time =Timing.aspectOf().getTimer(conn).getTime();

long rate = conn.callRate();long cost = rate * time;

> if (!conn.isMobile()) {if (conn instanceof LongDistance) {

long receiverCost =MOBILE_LD_RECEIVER_RATE* time;

conn.getReceiver().addCharge(receiverCost);}

}getPayer(conn).addCharge(cost);

}

after (Connection conn) returning () :Timing.endTiming(conn) {

long time =Timing.aspectOf().getTimer(conn).getTime();

long rate = conn.callRate();long cost = rate * time;if (conn.isMobile()) {

> if (!(conn instanceof LongDistance)) {long receiverCost =

MOBILE_LD_RECEIVER_RATE* time;conn.getReceiver().addCharge(receiverCost);

}}getPayer(conn).addCharge(cost);

}

Ma8 Ma9

Figura 1.33. Mutantes gerados pelo operador OCNG

está em desenvolvimento no grupo será estendida para apoiar também o testede mutação de unidades de programas OA escritos em AspectJ.

65

Masiero, Lemos, Ferrari e Maldonado

1.5. Conclusões e PerspectivasNeste texto foi apresentada uma visão geral da atividade de teste de soft-

ware, destacando sua importância dentro do processo de desenvolvimento e anecessidade de apoio automatizado. Foram enfatizadas as técnicas estruturale baseada em defeitos; em particular, foram apresentados a aplicação de cri-térios baseados em fluxo de controle, fluxo de dados e mutação no contextode teste de unidade de programas OO e OA. Questões relacionadas ao testede mutação na fase de integração também foram brevemente exploradas.Comas devidas adaptações, observou-se que esses critérios, embora originalmentepropostos para o teste de programas procedimentais, podem ser utilizados parao teste de software nos contextos de OO e OA tanto na fase de unidade quantona fase de integração.

Durante a aplicação dos critérios de teste nos exemplos apresentados, fo-ram identificadas falhas na execução dos testes, que ocorreram devido a doisdefeitos presentes nos programas originais. Nesses casos, podem-se obser-var evidências sobre a eficácia dos critérios em revelar defeitos presentes nosoftware e, ainda, sobre o caráter complementar das técnicas de teste, vistoque um dos defeitos foi identificado durante a utilização da técnica estrutural eo outro somente durante a aplicação do teste de mutação.

No que se refere a direções futuras, observa-se a necessidade de maisinvestigações no contexto de teste de software OO e OA. O paradigma OO,embora já consolidado no que diz respeito às demais atividades do ciclo de de-senvolvimento de software, ainda possui questões em aberto que impactam aatividade de teste. Poucos ainda são os trabalhos que propõem abordagens deteste direcionadas às características específicas introduzidas pelo paradigmacomo, por exemplo, encapsulamento e polimorfismo. Além disso, ainda nãoexiste um consenso sobre uma estratégia ideal de teste de integração e ferra-mentas de apoio ao teste de software OO em geral.

A POA, por sua vez, é uma abordagem relativamente recente, e o focoprincipal das pesquisas realizadas e em andamento está no estabelecimentode conceitos e em como implementá-los nas tecnologias de apoio. Nota-seque as mesmas necessidades encontradas para o teste de software OO estãopresentes no contexto de software OA, e algumas outras específicas como,por exemplo, o teste adequado de conjuntos de junção e o teste de integraçãoentre aspectos e classes.

Cabe destacar que a investigação dos problemas indicados, incluindo evo-luções e desenvolvimento de ferramentas de teste (como, por exemplo, evo-luções na ferramenta JaBUTi); e problemas relacionados com o teste de in-tegração de software OO e OA, são objetivos de trabalhos em andamento dealguns grupos de pesquisa, incluindo o grupo de Engenharia de Software doICMC/USP.

Para finalizar, ressalta-se que o conhecimento e as contribuições na áreade teste – divididos basicamente em conhecimento teórico, experimental e deferramentas de apoio – devem ser constantemente atualizados, assim como

66

Teste de Software OO e OA: Teoria e Prática

nas demais áreas. Nessa perspectiva, a organização de uma base histórica so-bre o custo e a eficácia das técnicas e critérios de teste, em diferentes domíniosde aplicação, em relação a diferentes classes de defeitos, certamente facilita-ria o planejamento de futuros desenvolvimentos de software. Facilitaria, ainda,o estabelecimento de estratégias de teste que explorem os aspectos comple-mentares das técnicas e critérios, viabilizando a detecção do maior número dedefeitos possível e com o menor custo, o que contribuiria para a liberação deprodutos de software de maior qualidade a um menor custo [Rocha et al. 2001].

1.6. AgradecimentosOs autores agradecem à Fundação de Amparo à Pesquisa do Estado de

São Paulo (FAPESP) pelo apoio financeiro e aos pesquisadores Ellen Fran-cine Barbosa, Auri Marcelo Rizzo Vincenzi e Márcio Eduardo Delamaro pelasdiversas contribuições neste trabalho. Referências[Acree 1980] Acree, A. T. (1980). On Mutation. PhD thesis, Scholl of Infor-

mation and Computer Science, Georgia Institute of Technology, Atlanta/GA -USA.

[Acree et al. 1979] Acree, A. T., Budd, T. A., DeMillo, R. A., Lipton, R. J., andSayward, F. G. (1979). Mutation analysis. Technical Report GIT-ICS-79/08,School of Information and Computer Science, Georgia Institute of Techno-logy, Atlanta/GA - USA.

[Adrion et al. 1982] Adrion, W. R., Branstad, M. A., and Cherniavsky, J. C.(1982). Validation, Verification, and Testing of Computer Software. ACMComputing Surveys, 14(2):159–192.

[Agrawal et al. 1989] Agrawal, H., DeMillo, R. A., Hathaway, R., Hsu, W., Hsu,W., Krauser, E. W., Martin, R. J., Mathur, A. P., and Spafford, E. H. (1989).Design of mutant operators for the C programming language. Technical Re-port SERC-TR41-P, Software Engineering Research Center, Purdue Univer-sity, West Lafayette/IN - USA.

[Alexander 2003] Alexander, R. (2003). Aspect-oriented programming: the realcosts? IEEE Software, 20(6):90–93.

[Alexander et al. 2004] Alexander, R. T., Bieman, J. M., and Andrews, A. A.(2004). Towards the systematic testing of aspect-oriented programs. Techni-cal report, Department of Computer Science, Colorado State University.

[Alexander and Offutt 2000] Alexander, R. T. and Offutt, A. J. (2000). Criteriafor testing polymorphic relationships. In Proceedings of the 11th InternationalSymposium on Software Reliability Engineering (ISSRE’2000), pages 15–23,Sao Jose/CA - USA. IEEE Computer Society.

[AspectJ Team 2003] AspectJ Team (2003). The AspectJ programming guide.Online. Disponível em http://www.eclipse.org/aspectj/doc/released/progguide/index.html - último acesso em 20/01/2006.

67

Masiero, Lemos, Ferrari e Maldonado

[Beck and Gamma 2006] Beck, K. and Gamma, E. (2006). JUnit, testing re-sources for extreme programming. Online. Disponível em http://www.junit.org/index.htm - último acesso em 02/04/2006.

[Binder 1999] Binder, R. V. (1999). Testing Object-Oriented Systems Models,Patterns, and Tools. Addison Wesley, 1st. edition.

[Booch 1994] Booch, G. (1994). Object-Oriented Analysis and Design with Ap-plications. Addison Wesley, 2nd. edition.

[Budd 1981] Budd, T. A. (1981). Mutation analysis: Ideas, example, problemsand prospects. Computer Program Testing North-Holand Publishing Com-pany.

[Chevalley and Thévenod-Fosse 2003] Chevalley, P. and Thévenod-Fosse, P.(2003). A mutation analysis tool for java programs. International Journal onSoftware Tools for Technology Transfer (STTT), 5(1):90–103.

[Cox and Novobilski 1991] Cox, B. J. and Novobilski, A. J. (1991). Object-Oriented Programming. Addison-Wesley, 2nd. edition.

[Delamaro 1997] Delamaro, M. E. (1997). Mutação de Interface: Um critériode adequação interprocedimental para o teste de integração. PhD thesis,IFSC/USP.

[Delamaro et al. 2001] Delamaro, M. E., Maldonado, J. C., and Mathur, A. P.(2001). Interface Mutation: An approach for integration testing. IEEE Tran-sactions os Software Engineering, 27(3):228–247.

[DeMillo 1978] DeMillo, R. A. (1978). Hints on test data selection: Help for thepracticing programmer. IEEE Computer, 11(4):34–43.

[Domingues 2002] Domingues, A. L. S. (2002). Avaliação de critérios e fer-ramentas de teste para programas OO. Master’s thesis, ICMC/USP, SãoCarlos/SP - Brasil.

[Elrad et al. 2001] Elrad, T., Filman, R. E., and Bader, A. (2001). Aspect-oriented programming: Introduction. Communications of the ACM,44(10):29–32.

[Filman and Friedman 2000] Filman, R. and Friedman, D. (2000). Aspect-oriented programming is quantification and obliviousness. In Workshop onAdvanced Separation of Concerns, OOPSLA 2000, pages 21–35, Minnea-polis - USA.

[Frankl and Weyuker 2000] Frankl, P. G. and Weyuker, E. J. (2000). Testingsoftware to detect and reduce risk. Journal of Systems and Software,53(3):275–286.

[Friedman 1975] Friedman, A. D. (1975). Logical Design of Digital Systems.Computer Science Press.

[Harrold 2000] Harrold, M. J. (2000). Testing: A roadmap. In 22th InternationalConference on Software Engineering - Future of SE Track, pages 61–72.

68

Teste de Software OO e OA: Teoria e Prática

ACM Press.

[Harrold and Rothermel 1994] Harrold, M. J. and Rothermel, G. (1994). Per-forming data flow testing on classes. In 2nd ACM SIGSOFT Symposium onFoundations of Software Engineering, pages 154–163, New York, NY. ACMPress.

[Howden 1986] Howden, W. E. (1986). Functional Program Testing and Analy-sis. McGraw-Hill, Inc., New York/NY - USA.

[IEEE 1990] IEEE (1990). IEEE standard glossary of software engineering ter-minology. Standard 610.12, Institute of Electric and Electronic Engineers.

[Kiczales et al. 2001] Kiczales, G., Hilsdale, E., Hugunin, J., Kersten, M., Palm,J., and Griswold, W. G. (2001). An overview of AspectJ. In ECOOP ’01:Proceedings of the 15th European Conference on Object-Oriented Program-ming, pages 327–353.

[Kiczales et al. 1997] Kiczales, G., Irwin, J., Lamping, J., Loingtier, J.-M., Lo-pes, C., Maeda, C., and Menhdhekar, A. (1997). Aspect-oriented program-ming. In Aksit, M. and Matsuoka, S., editors, Proceedings of the EuropeanConference on Object-Oriented Programming, volume 1241, pages 220–242, Berlin, Heidelberg, and New York. Springer-Verlag.

[Kiczales and Mezini 2005] Kiczales, G. and Mezini, M. (2005). Aspect-oriented programming and modular reasoning. In Proceedings of the 27th

International Conference on Software Engineering (ICSE’2005), pages 49–58. ACM Press.

[Kim et al. 2000] Kim, S., Clark, J., and McDermid, J. (2000). Class muta-tion: Mutation testing for object-oriented programs. In Proceedings of theFMES’2000.

[Laddad 2003] Laddad, R. (2003). AspectJ In Action. Manning Publications.

[Lemos 2005] Lemos, O. A. L. (2005). Teste de programas orientados a aspec-tos: Uma abordagem estrutural para AspectJ. Master’s thesis, ICMC/USP,São Carlos/SP - Brasil.

[Lemos et al. 2004] Lemos, O. A. L., Maldonado, J. C., and Masiero, P. C.(2004). Data flow integration testing criteria for aspect-oriented programs.In Anais do 1o Workshop Brasileiro de Desenvolvimento de Software Orien-tado a Aspectos (WASP’2004), Brasília/DF - Brasil.

[Lemos et al. 2005] Lemos, O. A. L., Maldonado, J. C., and Masiero, P. C.(2005). Structural unit testing of AspectJ programs. In Proceedings of the1st Workshop on Testing Aspect Oriented Programs – in conjunction withAOSD’2005, Chicago/IL, USA.

[Linnenkugel and Müllerburg 1990] Linnenkugel, U. and Müllerburg, M. (1990).Test data selection criteria for (software) integration testing. In First Interna-tional Conference on Systems Integration, pages 709–717, Morristown/NJ -

69

Masiero, Lemos, Ferrari e Maldonado

USA.

[Ma et al. 2002] Ma, Y. S., Kwon, Y. R., and Offutt, J. (2002). Inter-class muta-tion operators for java. In Proceedings of the 13th International Symposiumon Software Reliability Engineering (ISSRE’02), pages 352–366, Annapolis,MD. IEEE Computer Society Press.

[Maldonado 1991] Maldonado, J. C. (1991). Critérios Potenciais Usos: UmaContribuição ao Teste Estrutural de Software. PhD thesis, DCA/FEE/UNI-CAMP, Campinas, SP - Brasil.

[Mathur and Wong 1993] Mathur, A. P. and Wong, W. E. (1993). Evaluation ofthe cost of alternative mutation strategies. In Anais do 7o Simpósio Brasileirode Engenharia de Software (SBES’1993), pages 320–335, João Pessoa/PB- Brasil.

[McDaniel and McGregor 1994] McDaniel, R. and McGregor, J. D. (1994). Tes-ting polymorphic interactions between classes. Technical Report TR-94-103,Clemson University.

[Mortensen and Alexander 2004] Mortensen, M. and Alexander, R. T. (2004).Adequate testing of aspect-oriented programs. Technical Report CS 01-110,Department of Computer Science, Colorado State University.

[Myers et al. 2004] Myers, G. J., Sandler, C., Badgett, T., and Thomas, T. M.(2004). The Art of Software Testing. John Wiley & Sons, 2nd edition.

[Offutt et al. 1993] Offutt, A. J., Rothermel, G., and Zapf, C. (1993). An expe-rimental evaluation of selective mutation. In Proceedings of the 15th Inter-national Conference on Software Engineering (ICSE’1993), pages 100–107,Baltimore/MD - USA. IEEE Computer Society Press.

[Offutt et al. 2001] Offutt, J., Alexander, R., Wu, Y., Xiao, Q., and Hutchinson,C. (2001). A fault model for subtype inheritance and polymorphism. In Pro-ceedings of the 12th International Symposium on Software Reliability Engine-ering, pages 84–93, Hong Kong - China. IEEE Computer Society Press.

[Pressman 2000] Pressman, R. S. (2000). Software engineering - A Practitio-ner’s Approach. McGraw-Hill, 5th edition.

[Rapps and J.Weyuker 1982] Rapps, S. and J.Weyuker, E. (1982). Data flowanalysis techniques for program test data selection. In 6th International Con-ference on Software Engineering, pages 272–278, Tokio, Japan.

[Rapps and J.Weyuker 1985] Rapps, S. and J.Weyuker, E. (1985). Selectingsoftware test data using data flow information. IEEE Transactions on Soft-ware Engineering, 11(4):367–375.

[Rocha et al. 2001] Rocha, A. R. C., Maldonado, J. C., and Weber, K. C.(2001). Qualidade de Software. Prentice Hall.

[Simão and Maldonado 2000] Simão, A. S. and Maldonado, J. C. (2000). Mu-tation based test sequence generation for Petri Nets. In Proceedings of the

70

Teste de Software OO e OA: Teoria e Prática

3rd Workshop of Formal Methods, pages 68–79, João Pessoa/PB - Brasil.

[Sommerville 2001] Sommerville, I. (2001). Software Engineering. Addison-Wesley, 6th edition.

[Stroustrup 1986] Stroustrup, B. (1986). The C++ Programming Language.Addison-Wesley.

[Sun Microsystems 2006] Sun Microsystems (2006). Java Technology. Online.Disponível em http://java.sun.com/ - último acesso em 02/04/2006.

[Vilela et al. 1999] Vilela, P. R. S., Maldonado, J. C., and Jino, M. (1999). Dataflow based integration testing. In Anais do 13o Simpósio Brasileiro de Enge-nharia de Software (SBES’1999), Florianópolis/SC - Brasil.

[Vincenzi 2004] Vincenzi, A. M. R. (2004). Orientação a Objeto: Definição,Implementação e Análise de Recursos de Teste e Validação. PhD thesis,ICMC/USP, São Carlos, SP - Brasil.

[Vincenzi et al. 2005] Vincenzi, A. M. R., Maldonado, J. C., Wong, W. E., andDelamaro, M. E. (2005). Coverage testing of java programs and components.Science of Computer Programming, 56(1-2):211–230.

[Vincenzi et al. 2002] Vincenzi, A. M. R., Nakagawa, E. Y., Maldonado, J. C.,Delamaro, M. E., and Romero, R. A. R. (2002). Bayesian-learning basedguidelines to determine equivalent mutants. International Journal of SoftwareEngineering and Knowledge Engineering - IJSEKE, 12(6):675–689.

[Weyuker 1982] Weyuker, E. J. (1982). On testing non-testable programs.Computer Journal, 25(4):465–470.

[Weyuker 1996] Weyuker, E. J. (1996). Using failure cost information for testingand reliability assessment. ACM Transactions on Software Engineering andMethodology, 5(2):87–98.

[Wirfs-Brock et al. 1990] Wirfs-Brock, R., Wilkerson, B., and Wiener, L. (1990).Designing Object-Oriented Software. Prentice-Hall.

[Wong and Mathur 1995] Wong, W. E. and Mathur, A. P. (1995). Fault detectioneffectiveness of mutation and data flow testing. Software Quality Journal,4(1):69–83.

[Zhu et al. 1997] Zhu, H., Hall, P., and May, J. (1997). Software unit test cove-rage and adequacy. ACM Computing Surveys, 29(4):366–427.

71