Passo 5: Repetir
2.7 Programação Orientada a Aspectos
2.7.2 Aspectj
Aspectj é uma extensão orientada a aspectos da linguagem Java. É a linguagem orien-
tada a aspectos mais popular, além de ser a primeira a ser proposta. Já foi empregada
em vários estudos sobre orientação a aspectos. Neste trabalho utilizamos Aspectj para
monitorar as exceções que trafegam no decorrer da execução do sistema e forçar o seu
lançamento quando o desenvolvedor considerar isso necessário.
Sintaxe básica do Aspectj
POINTCUTS: Pointcuts são construções de AspectJ que permitem a seleção de um con-
junto de pontos de junção. Pointcuts também permitem que o contexto relativo aos
pontos de junção selecionados seja exposto para os advice associados a eles. Com a uti-
lização de pontos de junção, é possível obter argumentos de métodos, exceções lançadas
no ponto de junção e objetos usados como alvo para chamadas de métodos. Os pontos
de junção selecionados podem ser combinados através dos operadores de intersecção de
conjuntos (&&), união de conjuntos (||) ou negação(!). Para a definição dos pointcuts,
podemos utilizar, por exemplo, os seguintes designadores da tabela
2.7.2. Podemos uti-
lizar wildcards para representar tipos de retorno, nomes ou parâmetros associados a um
conjunto de pontos de junção selecionado. Os seguintes caracteres definem esses wild-
cards: o ” * ” indica qualquer tipo de elemento, ” .. ” aparece em listas de parâmetros e
indica qualquer seqüência independentemente do tipo de objeto, e ”+ ” é utilizado depois
do nome de um tipo para indicar que os seus subtipos também estão sendo selecionados.
Alguns exemplos de pointcuts são apresentados a seguir.
pointcut callexecutionGeneral(): handler(Throwable+)
&& !within(br.cin.ufpe.junite.*);
2.7. PROGRAMAÇÃO ORIENTADA A ASPECTOS
Categoria do Ponto de Junção
Sintaxe
Execução de método
execution(MethodSignature)
Chamada a método
call(MethodSignature)
Execução do construtor
execution(ConstructorSignature)
Chamada ao construtor
call(ConstructorSignature)
Inicialização da classe
staticinitialization(TypeSignature)
Acesso ao atributo
get(FieldSignature)
Atribuição a um atributo
set(FieldSignature)
Execução de tratador de
exceções handler(TypeSignature)
Inicialização de objetos
initialization(ConstructorSignature)
Pré-inicialização de Objetos
preinitialization(ConstructorSignature)
Execução do advice
adviceexecution()
Tabela 2.1 Desiginadores de ponto de junção [45] pg.74
pointcut tc(): execution( void
atunes.kernel.utils.PictureExporter.doExport(..));
No primeiro exemplo, o pointcut intercepta o tratamento de qualquer exceção do tipo
Throwable ou subtipo que não esteja em nenhuma classe do pacote
br.cin.ufpe.junite. Esse pointcut também captura o argumento passado para
o método (args(s)), neste caso a exceção tratada, permitindo que seja exposto posterior-
mente aos advice. No segundo exemplo, são capturadas chamadas ao método doExport
() da classe PictureExporter que não devolve nenhum resultado e tem zero ou
mais parâmetro de qualquer tipo.
Advice
Advice são estruturas que permitem que o código seja associado a um conjunto de pontos
de junção selecionados. O Advice pode usar informação contextual relativa aos pontos
de junção selecionados, se esta for exposta pelos pointcuts. AspectJ define três tipos de
advice: before, after e around. A Figura2.5fornece exemplo de uso de cada um deles.
Neste trabalho, são empregados apenas advice do tipo around.
Advice around: Este tipo de advice é executado no lugar do ponto de junção selecio-
nado. Pode capturar ou executar o ponto de junção inúmeras vezes com possibilidade de
passar diferentes argumentos em cada uma dessas execuções. Dentro do advice usamos
a instrução proceed() para executar o ponto de junção originalmente selecionado. As
linhas 23 a 30 exemplificam o uso do advice around para tratar exceções. A chamada
2.7. PROGRAMAÇÃO ORIENTADA A ASPECTOS
1 - pointcut myPointcut(): execution(void Class.method());
2 -
3 - before(): myPointcut() {
4 -
//Antes da execução de method()
5 -
//faz alguma coisa.
6 - }
7 -
8 - after(): myPointcut() {
9 -
//Depois da execução de method()
10-
//faz alguma coisa.
11- }
12
13- after()returning: myPointcut() {
14-
//ao retornar sem erros da execução
15-
//realiza alguma ação
16- }
17-
18- after() throwing : myPointcut(){
19-
//Realiza alguma ação após method()
20-
//lançar uma uma exceção de qualquer tipo
21- }
22-
23- void around(): myPointcut(){
24-
//Substitui a execução de method()
25-
try {
26-
f = proceed(); //Executa o corpo original de method()
27-
} catch (IOException e) {
28-
...
29-
}
30- }
Figura 2.5 Exemplo de advice em AspectJ
que representa a condição do ponto de junção é capturada pelo pointcut myPointcut
e executada através do comando proceed(). Se levantar alguma exceção, o fluxo de
controle segue para o bloco catch, após isto, retorna para o ponto posterior à captura
do ponto de junção.
Intertype declarations Além de modificar o comportamento de um sistema em
tempo de execução pelo uso de pointcuts e advice, um aspecto pode alterar a estrutura
estática dos módulos do sistema através das declarações intertipo (Intertype Declarati-
2.7. PROGRAMAÇÃO ORIENTADA A ASPECTOS
tes, construtores, exception softening entre outros) que afetam a estrutura e hierarquia de
outros tipos [52]. Como este trabalho tem como foco o tratamento de exceções, vamos
nos ater ao comando declare soft.
O comando declare soft faz com que o compilador Java não realize as verifica-
ções que normalmente faria, com relação a exceções, no conjunto de pontos de junção
selecionado. Ele envolve a exceção selecionada com uma exceção não verificada do tipo
SoftException. A sintaxe é apresentada na linha abaixo.
declare soft : Type : Pointcut ;
Os trechos de código abaixo apresentam exemplos do emprego dessa construção:
declare soft : IOException : execution(Classe.metodoX(..));
declare soft : XException : call(Classe.new(..));
No primeiro exemplo, a exceção IOException não precisa ser tratada dentro da
execução do método metodoX() da classe Classe pois, através deste comando, o
compilador Java não irá verificá-la. Algum outro ponto do programa deverá tratar uma
exceção do tipo SoftException. No segundo exemplo temos uma construção similar.
A exceção XException não precisa ser tratada na chamada ao construtor da classe
Classe pelo mesmo motivo.
3
Abordagem Proposta
Este capítulo tem por objetivo descrever a abordagem proposta. Esta permite o teste do
comportamento excepcional possibilitando especificar e verificar os fluxos excepcionais
percorridos a partir dos pontos onde são lançados até o seu destino. Também apresenta-
mos um exemplo ilustrativo para apontar algumas limitações no uso de casos de testes
elaborados através do JUnit referentes à detecção de erros de tratamento de exceções.
3.1 Motivação
Considere um sistema de informação dividido em três camadas (figura
3.1). A camada
de Dados, responsável pela lógica e comunicação com o banco de dados; Negócio, res-
ponsável pela logica da aplicação e GUI responsável pela lógica de apresentação. Neste
sistema, as exceções levantadas pelo DAO (Data Access Objects), na camada de dados,
são subtipos de DAOException e representam problemas relacionados à tentativa de
acesso ao banco de dados. Estas exceções devem ser repassadas e tratadas pelo servlet
na camada acima (GUI).
Em alguns casos o JUnit silencia o acontecimento de uma exceção. O seguinte caso
de teste ilustra este problema:
public void testDAOEHPolicy (){
Servlet s1 = new Servlet();
s1.service();
}
Se uma exceção do tipo DAOException for lançada no DAO mas não for repas-
sadas até o servlet, pode-se ter a sensação que a exceção foi adequadamente tratada
pelo Servlet. No entanto a exceção pode ter sido acidentalmente capturada por um
3.1. MOTIVAÇÃO
Figura 3.1 Arquitetura em camadas de um sistema de informação
elemento intermediário na cadeia de propagação desta exceção como por exemplo um
método da fachada.
Para dar suporte a criação de casos de testes para a verificação da ocorrência de uma
exceção o JUnit define a anotação@Test(expected=Exception.class1.
O caso de teste abaixo define que na cadeia de chamadas do método service()
deverá ocorrer o lançamento de uma exceção do tipo DAOException e está deverá ser
propagada até o caso de teste.
@Test(expected=DAOException.class)
public void testDAOEHPolicy (){
Servlet s1 = new Servlet();
s1.service();
}
Este teste faz chamada ao método service() do Servlet. O teste espera a ocorrên-
cia de uma exceção do tipo DAOException e caso não ocorra o teste falha.
3.1. MOTIVAÇÃO
Esta abordagem também é limitada porque pode esconder erros sutis. Por exemplo,
se um componente sinaliza uma exceção que representa um erro de programação mas
essa exceção é capturada por um tratador genérico. Podemos exemplificar esta situação
com exemplo abaixo:
public void update(Entity entity) throws DAOException {
try {
...
//neste momento entity é nulo
//gerando NullPointerException
EntityData data = entity.getData()
...
} catch (Exception e) {
throw new DAOException();
}
}
Neste trecho de código, se o objeto passado como parâmetro (entity) for nulo, na
chamada ao método getData() é obtida uma exceção do tipo NullPointerException.
Esta, por sua vez, é capturada pelo tratador genérico e uma exceção do tipo DAOException
é propagada à cadeia de chamadas do método update. O teste elaborado para este tre-
cho de código utilizando o JUnit irá passar e o erro de programação será mascarado.
Observamos neste estudo através dos sistemas avaliados (capítulo
4) erros desta natu-
reza.
O JUnit não fornece uma maneira de verificar se um elemento intermediário errone-
amente lida com a exceção. Isso é compreensível, uma vez o JUnit visa apoiar testes
de unidade. Porém, a propagação global de exceção de forma inesperada pode ser uma
grande fonte de erros [57,55]. Este problema é particularmente visível para linguagens
como Java e C#, onde exceções são objetos que podem ser capturados por seus superti-
pos [55], isto é, um tratador de exceção para um tipo de T captura exceções de qualquer
tipo T!que é um subtipo de T .
JUnit é o padrão para testes automatizados de software e foi estendido em alguns
cenários para lidar com escopo mais amplo (por exemplo, testes de integração) [27,56].
Para este trabalho utilizamos o JUnit em um contexto mais amplo. Isso não impede que
testes de unidades sejam criados paralelamente aos testes do comportamento excepcio-
nal proposto neste trabalho.
Acreditamos que o tratamento de exceções deve ser levado em conta em todas as
fases de desenvolvimento de software [26] e que TDD e métodos ágeis proporcionam
3.1. MOTIVAÇÃO
um melhor suporte que um processo baseado em documentação. Consideramos que a
abordagem de testes no JUnit deve ser empregada em outros contextos, não somente
para testes de unidade do comportamento normal da aplicação. Tendo em vista tais limi-
tações, verificamos a necessidade de uma abordagem para facilitar a definição, criação,
execução e verificação de casos de testes referente ao comportamento excepcional. Os
requisitos fundamentais para essa abordagem são:
• Permitir ao desenvolvedor definir os fluxos excepcionais percorridos por uma exce-
ção e compará-los com o fluxo excepcional obtido pela execução do sistema. Este
fluxo pode ser definido com todos os nós na cadeia de propagação das exceções,
ou parcialmente, onde elementos podem ser omitidos. Fluxos excepcionais que
divergem da especificação são considerados erros de implementação referentes ao
comportamento excepcional da aplicação.
• Os testes devem ser especificados de modo similar aos testes convencionais do
JUnit adicionando as definições dos fluxos excepcionais.
• Devido ao fato dos testes estarem sempre atualizados (caso contrário os testes não
passam), estes podem ser utilizados como a própria documentação no que se refere
aos fluxos excepcionais excluindo a necessidade de manter mais um artefato.
No documento
Uma Abordagem Leve para Testar o Comportamento Excepcional
(páginas 42-49)