• Nenhum resultado encontrado

Aplicando a Estratégia

3.3 O GERADOR DE CÓDIGO JMLE

(especificação ou implementação).

Para que as violações sejam encontradas, é necessário que as classes geradas que passam por um processo de testes possuam uma grande massa de dados com o intuito de se encontrar violações. Caso contrário, algumas violações podem não ser detectadas.

Outra ferramenta que executa a verificação é o jmle. Com esta ferramenta é realizada a tradução das anotações JML em códigos executáveis pelo compilador padrão Java. Esta ferramenta será detalhada na Seção 3.3.

As ferramentas que executam a verificação estática são responsáveis por verificar, através de uma análise estática do código, analisando assertivas, erros do Java como referências nulas, índices fora do limite do array. Essas ferramentas são capazes também de realizar tradução dos códigos Java em obrigações de prova para serem utilizados em provadores de teoremas.

3.3

O gerador de código jmle

Como foi visto na seção anterior, a utilização de especificações formais pode auxiliar o processo de desenvolvimento de sistemas. Porém, se formos capaz de executar estas especificações, o processo pode ficar mais fácil e prático. O jmle é capaz de traduzir as especificações JML em

constraint programs. O jmle faz parte das ferramentas JML e é totalmente implementado na

linguagem Java.

Esta execução de especificações formais permite que o especificador faça a validação do que foi especificado, tendo como resultado final uma especificação completa e válida. Esta especificação final pode ser utilizada de várias maneiras, entre elas: protótipos de aplicações e testes de oráculo [KW07].

O jmle utiliza constraint programming para realizar suas traduções de especificações JML para códigos binários. A utilização de constraint programming tem crescido muito ultima- mente. A evolução deste tipo de programação permitiu que algumas funcionalidades essenciais estivessem disponíveis como bibliotecas ou essas funcionalidades já estão embutidas em algu- mas linguagens. Para a utilização de constraint programming na linguagem Java foi criado o

Java Constraint Kit (JACK).

Constraint programming é um paradigma de programação que permite ao programador im-

plementar relações entre as variáveis e restringir, através de constraints, os valores das variáveis envolvidas ao invés de utilizar um código complicado para garantir as relações e valores das variáveis. A Figura 3.13, mostra como poderia ser criada uma constraint utilizando o JCK. Para representação da figura foi usada a funçãoleq(menor ou igual a).

1 { leq(X,Y) && leq(Y,X) } <=> { X = Y } 2 { leq(X,Y) && leq(Y, Z) } ==> ( leq(X,Z) }

Figura 3.13 Exemplo de constraint programming

jmle é uma adaptação de JMLc. Ao contrário de JMLc, jmle gera um programa executável (bytecode Java) a partir de especificações JML, suportado por bibliotecas do Java Constraint

3.3 O GERADOR DE CÓDIGO JMLE 31

Kit1 (JCK) [AKSS02]. Isto é, jmle é baseado em programação por restrições (constraint pro-

gramming) [Hen09]. Desta forma, jmle basicamente traduz predicados de JML em constraints,

os quais também são predicados. Isto é o que vem tornando constraint programming cada vez mais popular. Ou seja, pela diminuição do gap entre especificação e implementação, torna-se possível obter programas mais confiáveis e gerados de forma automática. Diferentemente de JMLc, jmle ignora totalmente o código Java, se houver algum fornecido pelo desenvolvedor, e somente compila o que está na especificação JML. Como resultado final tem:

1. Anotações JML compiladas para classes Java;

2. Propriedades das especificações se tornam propriedades da classe;

3. Cada método da especificação JML é compilado para um método da classe Java.

A abstração fornecida pelo JML não é afetada com a execução destas especificações. Ao contrário, com a programação por restrições é capaz de se produzir códigos com um alto nível de abstração para serem executados.

A linha 1 da Figura 3.13 demonstra uma regra assimétrica que define que se X é menor ou igual a Y e que Y é menor ou igual a X, então X tem que ser igual a Y. A linha 2 exemplifica uma regra transitiva que define que se X é menor ou igual a Y e que Y é menor ou igual a Z, então X é menor ou igual a Z [AKSS02].

É através deste tipo de relacionamento entre variáveis e constraints que libera o programa- dor de realizar de maneira explícita a relação entre as variáveis, deixando assim o código mais simples e de fácil entendimento.

JCK é uma biblioteca de constraints Java e pode ser dividida em:

• JCHR (Java Constraint Handling Rules) que é uma linguagem de alto nível utilizada para a construção de resolvedores de constraints;

• VisualCHR que é utilizada para visualizar a propagação e simplificação das constraints definidas no JCHR;

• JASE (Java Abstract Search Engine) que é uma ferramenta de busca;

Por causa das bibliotecas do JCK, os programas gerados com jmle contêm uma enorme quantidade de classes internas (inner classes) que são utilizadas para o empacotamento de códigos binários que serão utilizados após a resolução das restrições. Para o armazenamento destas classes internas, o compilador gera classes cujos nomes iniciam com $, seguidos de uma seqüência de dígitos. Ou seja, nomes nada intuitivos. E são desta forma porque seguem a filosofia de MDD (Model Driven Development) [RB08], onde o desenvolvedor deveria apenas lidar com modelos e não com códigos gerados automaticamente (visão caixa-preta).

Em um pouco mais de detalhes, o processo de tradução que o jmle utiliza é o seguinte: jmle traduz os elementos de JML em elementos das bibliotecas do JCK. O trabalho é iniciado pela pré-condição de cada método, pois, se não for possível traduzir a pré-condição, não há

1Java Constraint Kit é um framework utilizado para criar implementações Java a partir de resolvedores de

3.4 JARTEGE 32

motivo para tentar traduzir a pós-condição. O processo de tradução consiste basicamente em encontrar uma combinação de elementos das bibliotecas do JCK que seja equivalente ao trecho de especificação (pré-condição, pós-condição ou invariante) de JML. Se isto for possível, um repositório de constraints, usado para armazenar a constraint resultante, será diferente de vazio. Caso contrário uma exceção é levantada relatando o impedimento da tradução.

Para que o jmle gere corretamente o código especificado é necessário que a especificação esteja bem detalhada para que seja possível que o tradutor alcance as pós-condições a partir das pré-condições. Caso o jmle não consiga realizar a tradução, o código produzido irá lançar uma exceção informando que o compilador não possui informações suficientes para realizar a tradução.

Para cada tipo primitivo do Java e das classes que implementam oJMLCollection, o jmle considera como um domínio de constraints. Ese domínio de constraints é utilizado para realizar um mapeamento entre os resolvedores de constraints criados pelo jmle e os tipos primi- tivos. Através deste mapeamento, utilizando regras do JCK, é possível melhorar o desempenho do código criado pelo jmle em relação a outros resolvedores que utilizam linguagens de cons-

traint programming [KW07].

O processo de tradução é bastante semelhante ao que foi demonstrado na Figura 3.12. Po- rém, o jmle utiliza a árvore gerada pelo parser e traduz as asserções JML em código do JCK. O resultado desta tradução é executado pelo compilador MultiJava que cria os bytecodes que podem ser executados no compilador Java tradicional.

O jmle possui algumas características para tentar controlar possíveis erros nas especifica- ções. Por exemplo, caso uma especificação não forneça dados necessários para que o jmle consiga construir um pós-estado do objeto, será lançada uma exceção indicando esta falta de informação.

Com a utilização do jmle, a geração do código se torna mais rápida e pode ser utilizado para aumentar a velocidade na criação de protótipos ou até mesmo para a criação de stubs. Porém, como o jmle utiliza um repositório de constraints, a sua execução se torna mais lenta do que um código gerado por um humano, isto porque, é necessária uma resolução de todas estas constraints.

A utilização do jmle também pode ser utilizada para validar as especificações que foram modeladas. Através do código gerado pelo jmle é possível debugar o que foi especificado e verificar se o que foi especificado atende aos requisitos do sistema.

O jmle não é capaz de realizar a tradução de todas as anotações JML e funcionalidades disponíveis no Java 1.5 [KW07].

Como podemos observar, o processo de tradução realizado pelo jmle não é tão simples e, portanto pode conter erros. Como seu objetivo é gerar código de forma rápida e de acordo com a especificação, torna-se imprescindível saber se tal código é confiável.

3.4

Jartege

O Jartege [Ori05] é um framework para geração automática de casos de testes de classes Java que possuam anotações JML. Os casos de testes gerados pelo Jartege são compostos de JUnit com um conjunto de dados aleatórios para a execução.

3.4 JARTEGE 33

A utilização dos testes aleatórios é algo bem contestado na academia, isso por causa da capacidade de geração de testes que englobem todo o escopo do que está sendo testado. Porém, a utilização de testes aleatórios possui uma série de vantagens:

• Os testes aleatórios são baratos para realizar e implementar. Através desse tipo de teste é possível a realização de se testar as classes com um grande conjunto de dados;

• Através dos testes aleatórios podem ser encontrados um número de bugs a um baixo custo;

• Com os testes aleatórios é possível uma detecção de falhas de maneira precoce, isto porque, através desse tipo de teste, os erros são detectados antes da utilização do sistema; • Eles podem ser utilizados como uma primeira etapa para verificar a confiabilidade do

programa.

A criação dos testes unitários pelo Jartege utiliza as especificações JML para ajudar na geração dos testes de duas maneiras [Ori05]:

1. Para diminuir as sequências de testes que contém alguma chamada que viole as entradas de pré-condições;

2. A especificação também é utilizada como teste de oráculo: o teste detecta um erro quando uma asserção (invariantes, pós-condições e pré-condições) são violadas. Este tipo de erro provavelmente é ocasionado por alguma falha na especificação ou implementação. Os sistemas atuais são desenvolvidos utilizando a orientação a objetos. Com isso é necessá- rio que o gerador de testes unitários seja capaz de gerar os casos com as integrações necessárias entre as classes dependentes. O Jartege cria objetos se necessário. Caso o objeto dependente possua alguma especificação JML, a criação do seu construtor irá obedecer as pré-condições definidas.

O Jartege permite que, antes da criação dos testes unitários, algumas propriedades sejam configuradas. Com a configuração destas propriedades, o desenvolvedor pode gerar casos de testes unitários mais interessantes e coesos. Estas configurações são realizadas através de parâ- metros na construção dos testes. A Figura 3.14 mostra como é o processo necessário, utilizando o Jartege, para a criação dos casos de teste.

Para se produzir os testes unitários através do Jartege, é necessário que seja criada uma classe denominada de ClassTester. É nesta classe que todas as propriedades de configuração são parametrizadas. Esta parametrização pode ser interessante quando, por exemplo, quere- mos fazer testes de stress em determinados métodos. Com o Jartege nós podemos mudar a probabilidade de criação de uma classe por exemplo.

Durante a criação dos testes unitários, o Jartege permite que sejam alterados os pesos das classes ou métodos. Esses pesos significam que a criação de um teste unitário de um deter- minado objeto influencie na sua criação. Por exemplo, o peso para a classe principal pode ser maior do que o peso para as classes dependentes. Isto indica que durante a criação das chama- das dos métodos, os métodos da classe principal têm maior importância do que os métodos das

3.4 JARTEGE 34

Documentos relacionados