Saturday, November 29, 2008

Inspecting log files with Log Factor 5

Almost every serious Java developer uses Log4j as part of their everyday tool set. Log4j is a very powerful, simple and flexible logging framework.

Most developers, myself included, uses a very small subset of Log4j features. Some nice framework capabilities remains hidden from almost everyone. For me the most useful of these hidden features is the Log Factor 5 AKA LF5. LF5 provides a special message formatter and a SWING based user interface for log files inspection. The user interface allows hierarchical category filtering and full text search. The LF5 formatter prepares log messages so they can be opened by LF5 GUI.

LF5 formatter configuration is quite simple. All you have to do is to add the following configuration to your Log4j configuration file:

log4j.rootCategory= INFO,A1

#Configure rolling file appender
log4j.appender.A1=org.apache.log4j.RollingFileAppender
log4j.appender.A1.File=log4j_lf5.log

#Configure pattern for LF5
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=[slf5s.start]%d{DATE}[slf5s.DATE]%n\
%p[slf5s.PRIORITY]%n%x[slf5s.NDC]%n%t[slf5s.THREAD]%n\
%c[slf5s.CATEGORY]%n%l[slf5s.LOCATION]%n%m[slf5s.MESSAGE]%n%n

The previous configuration creates a file appender. The message format is compatible with LF5. After running any Java application (that sends messages to Log4j) using this logging configuration, the file named log4j_lf5.log will be created on the application root directory.

To inspect the log file, open the LF5 GUI using the following command (make sure the log4j jar is available in the classpath):

$ java org.apache.log4j.lf5.StartLogFactor5

You can easily run the LF5 GUI from Eclipse using a run configuration pointing to the org.apache.log4j.lf5.StartLogFactor5 class:

Assuming everything works fine the LF5 GUI will open. Now open the log file using the Menu File|Open... and point to the file log4j_lf5.log. You will see something like the following figure:

Using the tree on left side is possible to filter the categories. It is also possible to choose the visible columns and apply filters by log level.

Using LF5 you can easily change the log scope (level and categories) using the same file as input and this is a great bonus. I hope you enjoy LF5 as much as I do.

Saturday, October 18, 2008

Modelando código testável

No mundo do desenvolvimento de software, testes unitários são aceitos por quase 100% dos profissionais, empresas e clientes como uma excelente ferramenta para o incremento da qualidade, manutenibilidade, confiabilidade e mais vários *dade que a literatura tanto gosta de enumerar. Paradoxalmente, quase ninguém aceita arcar com os custos associados à utilização deste tipo de teste quando percebe que eles não são tão triviais como são pintados pela maioria dos TDD Evangelists de plantão.
Em muitos casos, o código de um determinado teste unitário é mais extenso e/ou complexo que o código que está sendo testado. Infelizmente só nos damos conta disso quando começamos a bater cabeça. Como os projetos do mundo real nunca são tão simplórios quanto os exemplos Hello World like dos livros, e os prazos sempre são apertados, a boa intensão de criar aquela suite de testes mágica que vai verificar o código automaticamente de hora em hora, vai por água abaixo.
Outra importante causa de fracasso neste campo é que muita gente não modela o código de forma que o mesmo seja testável. Não é óbvio à primeira vista, mas nem todo código é testável. Usualmente, código bem modelado é testável, mas o desenvolvedor tem que pensar, o tempo todo, que aquela classe tem que ser testada isoladamente, livre de todas as dependências. Assim, o requisito mais importante para utilizar testes unitários como uma ferramenta efetiva, é a modelagem do código de forma que ele seja facilmente testável via frameworks como JUnit ou TestNG. Se o código não atende a este requisito, refatore-o ou esqueça os testes unitários.
Antes de dizer como modelar código testável, prefiro dizer o que torna uma classe difícil de testar:
  • Classes faz-tudo com milhares de linhas contendo métodos faz-tudo com centenas de linhas são muito difíceis de testar. Quando o código a ser testado possui uma grande quantidade de fluxos possíveis, é muito complicado configurar um código de teste que exercite todos estes fluxos. É importante que as classes tenham responsabilidades bem definidas e que elas deleguem outras responsabilidades para outras classes. Isso é modelagem OO tradicional mesmo, nada de novo.

  • Classes que referenciam outras classes (colaborators) diretamente, usando o operador new. É praticamente impossível substituir os colaborators por mocks.

  • Classes que dependem diretamente de recursos externos, tais como: conexões de rede, sistemas de arquivos, datasources, etc são uma pedra no sapato do autor de um teste unitário.

O exemplo a seguir mostra uma classe que depende que um arquivo com um certo nome exista no sistema de arquivos, o que força o pobre desenvolvedor do teste unitário a garantir que o arquivo em questão exista no mesmo diretório, exatamente com o mesmo nome usado pelo código alvo do teste:
public class ImportadorDadosVendas{
  public void importar(){
    File arquivoVendas = new File(obterNomeArquivoVendas());
    //--- Processar arquivo linha a linha, bla, bla
  }
  private String obterNomeArquivoVendas(){
    //
  }  
}
É muito simples refatorar o código acima de modo a torná-lo mais amigável a um teste unitário. Como o sistema de arquivos é um recurso externo, temos que garantir que qualquer acesso a ele seja feito indiretamente, através de uma classe auxiliar. Neste exemplo vou usar o Spring para ilustrar a aplicação da inversão de controle (IoC) neste tipo de situação. O código a seguir ilustra esta refatoração:
/**
 * Interface que define um servico de acesso indireto ao filesysten
 **/
public interface ProvedorArquivos{
  File getReferenciaArquivo(String nomeArquivo);
}

/**
 * Implementação padrão da interface ProvedorArquivos
 **/
public class ProvedorArquivosImpl implements ProvedorArquivos{
  public File getReferenciaArquivo(String nomeArquivo){
    return new File(nomeArquivo);
  }  
}

/**
 * Serviço original refatorado
 **/
public class ImportadorDadosVendas{
  private ProvedorArquivos provedorArquivos;
  public void importar(){
    //--- Referência ao arquivo passa a ser fornecida pelo objeto provedorArquivos
    File arquivoVendas = provedorArquivos.getReferenciaArquivo(obterNomeArquivoVendas());

    //--- Processar arquivo linha a linha, bla, bla
  }
  private String obterNomeArquivoVendas(){
    //
  }

  /**
   * IMPORTANTE: Este método é chamado pelo Spring, que injeta o colaborator
   * O teste unitário pode chamar este método para fornecer um mock
   **/
  public setProvedorArquivos(ProvedorArquivos provedorArquivos){
    this.provedorArquivos = provedorArquivos;
  }
}
Para o Spring, temos a seguinte configuração:

  
  
  

  
  
    
    
    
  

A classe ImportadorDadosVendas agora usa uma implementação da interface ProvedorArquivos para recuperar uma referência ao arquivo que será importado. Note também que a instância do provedor de arquivos não é criada diretamente a partir do código da classe ImportadorDadosVendas, mas é fornecida externamente pelo Spring via um setter. Esta é uma mudança crucial: as dependências não são criadas localmente, mas fornecidas externamente.
Quando a classe ImportadorDadosVendas for executada normalmente, será fornecida uma instância de ProvedorArquivosImpl via setter. Porém, quando for executada por um teste unitário, será fornecida uma implementação alternativa da interface ProvedorArquivos, com comportamento previsível dentro do contexto do teste:
public class ImportadorDadosVendas extends Testcase{
  public void testImportar(){
    ImportadorDadosVendas importador = new ImportadorDadosVenda();
    ProvedorArquivos provedorArquivos = new ProvedorArquivosMock();
    importador.setProvedorArquivos(provedorArquivos);
    importador.importar();
    //--- Asserts a partir daqui
  }

  public static class ProvedorArquivosMock implements ProvedorArquivos{
    public File getReferenciaArquivo(String novoArquivo){
      File arquivoParaTeste = File.createTempFile("testeImportacao.dat");
      //--- Popular arquivo temporario com dados para o teste
    }
  }
}
Esta abordagem pode ser aplicada a qualquer dependência da classe a ser testada. O uso de objetos “falsos”, conhecidos como mocks e stubs é uma das técnicas mais importantes para a implementação de testes unitários. Note que a referência à instância do provedor de arquivos é feita através de uma interface. Isso esconde a verdadeira implementação e facilita a substituição da classe real por um mock. Existem diversas libs de suporte à criação de mocks e stubs. Entre elas, as mais usadas são EasyMock e JMock.
O uso de frameworks de inversão de controle (AKA IoC) como Spring ou Google Guice traz uma grande contribuição para a testabilidade do código. Estes frameworks funcionam como super factories de objetos, sendo responsáveis tanto pela criação dos objetos, quanto dos relacionamentos entre eles. A chave está justamente no gerenciamento das dependências. Uma vez que as dependências são injetadas de forma transparente, via setters, o código do teste unitário pode substituir estas dependências por mocks usando os mesmos setters que normalmente seriam invocados pelo container de IoC.
Está fora do escopo deste post abordar o funcionamento dos containers IoC, mas quero enfatizar que eles são a principal ferramenta no caminho para a criação de código bem estruturado, pouco acoplado e portanto testável.
Criar suites de testes efetivas não é uma tarefa simples, mas pode ser facilitada:
  • Procure dividir a complexidade em classes menores com responsabilidade bem definida.

  • Sempre acesse colaborators através de interfaces.

  • Use frameworks de IoC como o Spring para tornar o mecanismo de injeção de dependências mais fácil de ser substituído pelo setup do código de teste.

  • Isole o acesso a recursos externos como bancos de dados e sistemas de arquivos. Referências a estes recursos devem ser fornecidas por interfaces injetadas via IoC.

Friday, August 29, 2008

Integrando Aplicações com Apache Camel

Foi-se o tempo em que a informatização de processos das organizações era feita em ilhas departamentais, quando sistemas isolados cuidavam dos diferentes aspectos do negócio. Hoje as empresas esperam que o software automatize todos os processos de negócio de forma integrada e ortogonal, esperam que a mesma informação não seja duplicada em bancos de dados diferentes, esperam que as regras de negócio não sejam duplicadas em diferentes aplicações. Infelizmente elas continuam esperando, pois ainda não é uma tarefa simples atender a todos estes requisitos.
Um passo importante nesta direção foi o advento dos Web Services, que permitem que aplicações diferentes, escritas usando os mais diversos ambientes de desenvolvimento, possam interoperar através da publicação de regras de negócio na forma de interfaces baseadas em padrões bem definidos como WSDL e SOAP. A utilização de web services permite que a salada de sistemas que normalmente povoam os data centers das empresas conversem de forma menos traumática que os famigerados métodos tão conhecidos como transferências de dados via arquivos texto ou escritas em bancos de dados alheios. Porém, implementar fluxos de negócio complexos usando web services é bem mais difícil que parece à primeira vista. As principais dificuldades relacionadas ao uso de web services são:
  • As diversas implementações de WSDL e SOAP não seguem as especificações à risca, o que muitas vezes faz com que certos web services não sejam acessíveis a todas as aplicações.
  • Em alguns casos, um web service deve executar de forma assíncrona do ponto de vista do cliente que o invocou, mas mesmo assim deve existir uma garantia de que o mesmo será executado.
  • Se uma aplicação que publica um conjunto de web services sai do ar, isso afetará todas as aplicações que dependem destes web services. Seria interessante que web services pudessem ser invocados mesmo quando as aplicações que os publicam estivessem indisponíveis.
  • Web services são semanticamente próximos a chamadas de métodos, mas as trocas de informações entre sistemas, muitas vezes, são mais parecidas com eventos ou trocas de mensagens, que podem ser implementadas através de um gerenciador de filas baseado em JMS, por exemplo.
  • Os dados que trafegam entre aplicações diferentes podem necessitar de algum tipo de tradução. Em alguns casos precisam ser mesclados a partir de fontes diferentes, ou uma mensagem pode ser dividida em várias mensagens, que podem ser enviadas para destinatários.
A atual abordagem para solucionar parte destes problemas é a utilização de um Enterprise Service Bus, ou ESB. Um ESB é um componente de software capaz de atuar como um ponto central para a publicação de serviços baseados em diversos protocolos, além de auxiliar no roteamento e tradução de mensagens. Em arquiteturas orientadas a serviços, conhecidas como SOA, o ESB é o barramento onde os serviços corporativos são pendurados, permitindo a troca de mensagens entre as aplicações, sejam elas internas ou externas à organização. A implantação de um ESB é um dos requisitos para a implantação de uma arquitetura SOA.
Existem várias implementações de ESB, sendo a maioria de alto custo e fornecidas por grandes empresas como IBM e SAP. Porém algumas implementações open source vêm sendo desenvolvidas e ganhando maturidade à medida que sua adoção aumenta. Entre estas implementações, destacam-se: Apache Servicemix, Mule ESB, JBoss ESB e Apache Camel. Neste post vou me concentrar no Camel em função da sua simplicidade quando comparado aos demais.
Uma visão geral do Camel
Os próprios desenvolvedores admitem que o Camel não é um ESB completo, como mostra a resposta do FAQ à pergunta "Is Camel an ESB?":
Typically vendors claim practically everything is an ESB these days, so the answer is probably yes
However our view is that an ESB is more of a container of integration components, so we view Apache ServiceMix to be a true ESB based around JBI to provide a standards based integration platform of components.
We view Camel as being a rule based routing & mediation engine which can be used inside a full blown ESB, a message broker or a web services smart client. Though if you want to, you could consider that Camel is a small, lightweight embeddable ESB since it can provide many of the common ESB services like smart routing, transformation, mediation, monitoring, orchestration etc.
Independente de ser um ESB verdadeiro ou não, o importante é que o Camel é extremamente útil na implementação dos patterns típicos de integração de aplicações. O melhor é que isso pode ser feito com grande simplicidade, que é o maior diferencial em relação às demais soluções open source.
O Camel é construído tendo como base o Spring Framework, que fornece um ambiente de execução leve e de fácil configuração. Todos os componentes que formam o Camel são criados e gerenciados via Spring, o que traz todos os benefícios da inversão de controle, como baixo acoplamento, extensibilidade e facilidade de automação de testes. O Camel é composto pelos seguintes elementos:
CamelContext
Representa uma instância do Camel e é uma extensão do ApplicationContext do Spring. Os demais elementos da arquitetura são pendurados embaixo deste objeto.
Component
São plugins que enriquecem o Camel em termos de funcionalidades. Components são factories de endpoints (ver próximo tópico) que são os blocos elementares das rotas que podem ser criadas pelo desenvolvedor. O Camel fornece um rico conjunto de componentes, que permitem integração com diversor protocolos e ambientes diferentes, tais como JMS, JDBC, HTTP, File Systems, etc.
Route
O Camel pode ser definido como um roteador de mensagens. Routes são usados para definir a estrutura das rotas pelas quais irão fluir as mensagens.
Endpoint
Quando trafegam pelas rotas, as mensagens passam por pontos intermediários chamados de Endpoints. Os Endpoints são criados por componentes e são responsáveis pelo processamento das mensagens. O conceito de Endpoint implementado pelo Camel pode ser visto aqui. Os endpoints são declarados através de URL's. Uma URL serve como um identificador único de um endpoint dentro de uma rota, além disso ela informa ao CamelContext qual componente será responsável pela criação do endpoint.
Exchange
As mensagens são empacotadas em Exchanges. Estes objetos são usados pelos endpoints para a transferência das mensagens ao longo da rota.
Resolvendo problemas comuns de Integração
O Camel pode ser usado para criar proxies de web services. A vantagem desta abordagem é a centralização os endereços dos web services da organização em um único ponto. Além disso, é possível usar os recursos de roteamento para aplicar filtros ou enviar as mensagens para mais de um destinatário, entre outras milhares de coisas que podem ser feitas dentro do Camel. O caso mais simples é mostrado na figura a seguir:

As requisições são recebidas por um web service publicado pelo Camel. Estas mensagens são redirecionadas para a implementação real do web service. O código Java para implementar esta rota no Camel é o seguinte:
from("jetty:http://my.company/service").to("http://some.company/service");
Ok, ok. Concordo que isso não é tão incrível, mas é só o começo. Usando os recursos de roteamento e a rica coleção de componentes do Camel, é possível adicionar recursos bem interessantes a esta rota. Como por exemplo:
- Logar todas as mensagens em um arquivo texto:
from("jetty:http://my.company/service")
.to(
  "file:/logs/service.log",
  "http://some.company/service"
);

- Rotear a mensagem para diferentes endereços. Neste caso o Camel funcionaria como um balanceador de carga:
from("jetty:http://my.company/service")
  loadBalance().roundRobin().to(
    "http://some.company/service",
    "http://other.company/service"
);

- Rotear a mensagem para diferentes endereços de acordo com o conteúdo da mensagem. Como o formato de requisições SOAP é XML, é possível usar o filtro XPath do Camel usando o conteúdo da própria mensagem como entrada:
from("jetty:http://my.company/service").choice()
  .when().xpath("//cidade = 'Belo Horizonte' ")
    .to("http://sudeste/service")
  .when().xpath("//cidade = 'Salvador' ")
    .to("http://nordeste/service")
);

Outro cenário comum, mas difícil de ser implementado sem um software de mediação como o Camel, é a entrega garantida de mensagens. Imagine um sistema com alto requisito de disponibilidade que precisa enviar dados para um sistema externo que é parado diariamente , durante uma hora, para manutenção. Como nosso sistema 24x7 pode depender de outro sistema 23x7? O Camel fornece uma solução simples para este problema através do componente JMS. Claro que é possível usar JMS sem o Camel, mas se pensarmos em uma organização com um enorme parque de sistemas, nem todos eles são escritos em Java e conseguem usar JMS, ou só conseguem fazer isso usando conectores complexos. Usando a solução proposta a seguir, qualquer sistema que tenha integração com o sistema 23x7, só precisa invocar um web service publicado pelo Camel:
from("jetty:http://my.company/service").to("jms:filaEntrega");
from("jms:filaEntrega").to("http://some.company/service");

Caso o serviço saia do ar, o Camel intercepta o erro de conexão e re-submete a requisição automaticamente. Tanto a quantidade de tentativas quanto o intervalo entre elas pode ser configurado. Mesmo que o total de tentativas seja esgotado, é possível direcionar a mensagem para um dead letter channel, que é um endpoint especial que recebe mensagens não entregues, que podem ser tratadas mais tarde. A exemplo abaixo acrescenta tratamento de erro à rota anterior. Mensagens não entregues após 5 tentativas serão encaminhadas à fila "errosServico":
errorHandler(deadLetterChannel("jms:errosServico")
  .maximumRedeliveries(5).useExponentialBackOff());
from("jetty:http://my.company/service").to("jms:filaEntrega");
from("jms:filaEntrega").to("http://some.company/service");

Conclusão
Os exemplos que mostrei aqui são simples, mas acredito que mostram o enorme potencial do Apache Camel como mediador de mensagens.
Existem muitos casos de integração que podem ser tratados através do Camel. Uma lista bem completa baseada no best seller Enterprise Integration Patterns de Gregor Hohpe e Bobby Woolf pode ser encontrada aqui. Espero voltar a falar desse assunto em breve.

Saturday, July 12, 2008

Automação de testes com Canoo WebTest - Parte I

A maioria dos profissionais envolvidos em desenvolvimento de aplicações reconhece a importância da automação de testes, alguns tentam implantá-los, mas muitas vezes este esforço é malsucedido. As causas para o fracasso são várias:
  • Inexistência de roteiros de testes baseados nos requisitos.
  • Esforço para confecção e manutenção dos testes não previsto no cronograma do projeto.
  • Boa parte dos processos de desenvolvimento utilizados não contemplam automação de testes.
  • Dificuldade em convencer aos patrocinadores do projeto da importância dos testes automatizados.
  • Alto custo e limitações das ferramentas de teste.
Neste artigo, vou assumir que o leitor está convencido da utilidade da implantação de testes funcionais automatizados. A seguir vou mostrar que custo e limitação não é mais uma desculpa para não implementar e manter artefatos de automação para automação de testes funcionais para aplicações baseados em Web.

Porque Canoo WebTest?

Ao buscar uma ferramenta para automação de testes, elaborei a seguinte lista de requisitos:
  • Ser de baixo custo, de preferência gratuita.
  • Fácil de instalar e usar.
  • Facilmente "plugável" em scripts de build, de modo a permitir a execução automática dos testes em servidores de integração contínua.
  • Os scripts devem ser escritos em uma linguagem de programação rica, que facilite o uso de massas de dados externas e acesso ao banco de dados para preparação e validação dos resultados dos testes.
Entre as ferramentas avaliadas, o Canoo WebTest atendeu a boa parte destes requisitos:
  • Custo de aquisição zero.
  • Muito simples quando comparada a outras ferramentas.
  • É executada a partir de uma task do ANT, que é a ferramenta de build padrão para aplicações escritas em Java.
  • A linguagem padrão para os scripts é XML, que é muito limitada. Porém é possível criar scripts em Groovy, que é uma linguagem com recursos muito interessantes para esta finalidade. Por não ser a linguagem padrão, é necessário um esforço razoável para configurar o Canoo para utilizar o Groovy.

Instalando o Canoo WebTest

  1. Baixar a distribuição do Canoo WebTest aqui.
  2. Descompactar o arquivo em um diretório de sua escolha. Vou assumir o diretório c:\softs\webtest.
  3. Criar as variáveis de ambiente:
    • WEBTEST_HOME = c:\softs\webtest
    • JAVA_HOME = [diretório onde está instalado o JDK]
  4. Garantir que os diretórios %WEBTEST_HOME%\bin e %JAVA_HOME%\bin estão na lista do PATH do sistema.
  5. Para verificar se a instalação foi bem sucedida, crie um diretório qualquer (vou assumir c:\mytests) e entre com seguinte comando no command prompt:
    $ webtest -f %WEBTEST_HOME%\webtest.xml wt.createProject testeCanoo Buildfile: WebTest\webtest.xml wt.init: wt.createProject: [echo] [echo] Your WebTest project [testeCanoo] has been successfully created with some demo [echo] tests to get started. [echo] You can now just go to [c:\mytests\testCanoo] and run ant. [echo] BUILD SUCCESSFUL
  6. Se a execução do comando anterior gerar uma saída semelhante a esta acima, execute os comandos a seguir: $ cd testeCanoo $ ant
  7. Se após a execução do comando anterior, o arquivo c:\mytests\testeCanoo\results\webTestResults.html existir, o Canoo WebTest está funcionando corretamente em seu sistema. O conteúdo deste arquivo deve ser semelhante à figura abaixo:

Próximo post

No próximo post, vou mostrar como usar um plugin para Firefox como ferramenta de geração de scripts de teste. Também vou explorar os steps básicos que podem ser usadas na composição destes scripts através de exemplos.