Importando e exportando suas gems com Rubygems Snapshot 2

Posted by Roger Leite on dezembro 22, 2009

Final de ano está rendendo.
O rubygems_snapshot nasceu da necessidade de “migrar” as gems instaladas de uma máquina para outra, aliado ao rvm (veja este post-guia-rápido), permite mudar e/ou criar diferentes ambientes em minutos. Assim você pode fugir do famoso “gem hell”.

Veja como é difícil usar:

Instalação:

sudo gem install rubygems_snapshot

Para exportar as gems instaladas:

gem snapshot export projeto-exemplo.yml

Supondo que esteja em outra máquina, para importar as gems, use:

[sudo] gem snapshot import projeto-exemplo.yml

Afinal, o que tem de legal nisso?

Vamos supor que você acaba de entrar numa nova equipe e tem que montar o ambiente de desenvolvimento (por sinal, um ambiente complicado de configurar). O gem snapshot aliada ao rvm, foi feito para facilitar isto, vamos a um exemplo rápido:

Com o rvm, você pode criar um “novo ambiente”:

rvm use 1.8.7%projeto_exemplo
gem list

Deve retornar vazio.

gem install rubygems_snapshot
gem snapshot import projeto-exemplo.yml

Instalará as gems necessárias para o projeto e pronto!

ToDo:

Esta é uma versão bem básica, onde o “import” somente lê as gem e version e manda instalar sem requerir dependências. Está previsto de colocar um aviso no final das gems que deram erro, geralmente devido a dependências de “build nativos”, mas por enquanto estamos usando aqui na equipe com sucesso.

Como faço para criar um rubygems plugin também?

Bom, logo de cara posso te garantir que não é difícil (apesar da pouca documentação na internet), mas deixarei os detalhes para um outro post. Por enquanto, a minha recomendação é: clone o projeto, analise os dois rb do projeto :D e crie o seu!

Caso algum corajoso for usar, estou a disposição para ajudar, é só deixar um comentário aê!
Valeu e sucesso!

Ruby, Rubygems e $LOAD_PATH ou Como funciona o require de gems

Posted by Roger Leite on dezembro 17, 2009

Na madrugada passada, andei “brincando” com o fonte do Rubygems. Logo de cara posso te dizer que não consegui fazer o que queria, e pra amenizar o sentimento de “perda de tempo”, resolvi postar alguns truques aprendidos.

Baixei o fonte do rubygems, como faço pra rodá-lo sem alterar o meu sistema?

Foi a primeira pergunta que fiz. Percebi que com o google não iria encontrar a resposta, mas consegui uma dica importante: $LOAD_PATH.

$ irb
irb(main):001:0> $LOAD_PATH

No meu Ubuntu, obtive:

["/usr/local/lib/site_ruby/1.8", "/usr/local/lib/site_ruby/1.8/i486-linux", "/usr/local/lib/site_ruby/1.8/i386-linux", "/usr/local/lib/site_ruby", "/usr/lib/ruby/vendor_ruby/1.8", "/usr/lib/ruby/vendor_ruby/1.8/i486-linux", "/usr/lib/ruby/vendor_ruby", "/usr/lib/ruby/1.8", "/usr/lib/ruby/1.8/i486-linux", "/usr/lib/ruby/1.8/i386-linux", "."]
$ ls /usr/local/lib/site_ruby/1.8/

Exatamente nesta pasta que se encontra o rubygems.rb. Bingo!
Para rodar o fonte do rubygems, só é necessário adicionar ao $LOAD_PATH a pasta lib do projeto. Dado que estou na raiz do projeto rubygems baixado, execute:

~/rubygems$ ruby -I $PWD/lib ./bin/gem -v

O paramêtro -I permite adicionar diretório ao $LOAD_PATH. Simples e prático. Primeiro problema resolvido, comecei a programar.

Afinal, como funciona o “require de gems”?

Bom, já sabemos que o require “rubygems” fuciona pois encontra-se no $LOAD_PATH do ruby, no caso do meu Ubuntu em “/usr/local/lib/site_ruby/1.8″.

Basicamente (e muito), o Rubygems faz duas coisas no Kernel do Ruby.

  • Adiciona o metodo Kernel#gem.
  • Faz um Monkey Patch no Kernel#require

Kernel#gem

Permite “acionar” uma versão específica de gem. Note que este acionar, traduz-se para, adicionar a lib da gem no $LOAD_PATH. Segue um trecho do comentário do Kernel#gem:

##

# Use Kernel#gem to activate a specific version of +gem_name+.

#

# +version_requirements+ is a list of version requirements that the

# specified gem must match, most commonly “= example.version.number”.  See

# Gem::Requirement for how to specify a version requirement.

#

# If you will be activating the latest version of a gem, there is no need to

# call Kernel#gem, Kernel#require will do the right thing for you.

#

# Kernel#gem returns true if the gem was activated, otherwise false.  If the

# gem could not be found, didn’t match the version requirements, or a

# different version was already activated, an exception will be raised.
[...]

Kernel#require

No final do rubygems.rb encontramos:

if RUBY_VERSION < ‘1.9′ then

require ‘rubygems/custom_require’

end

Não consegui descobrir o que acontece com o ruby 1.9, mas no 1.8, o monkey patch executa os seguintes passos:

  • Chama o “original” require;
  • Em caso de LoadError;
    • Executa o “Gem.searcher.find(path)”;
    • Se true
      • Chama o activate (novamente traduz-se para adiciona a gem no $LOAD_PATH)
      • Executa o “original” require novamente;

Exemplos com o IRB

Para finalizar legal e comprovar tudo isso, fiz alguns testes:

$ gem list json

*** LOCAL GEMS ***

json (1.2.0, 1.1.9)

json_pure (1.2.0)

$ irb
irb(main):001:0> $LOAD_PATH

=> ["/usr/local/lib/site_ruby/1.8", "/usr/local/lib/site_ruby/1.8/i486-linux", "/usr/local/lib/site_ruby/1.8/i386-linux", "/usr/local/lib/site_ruby", "/usr/lib/ruby/vendor_ruby/1.8", "/usr/lib/ruby/vendor_ruby/1.8/i486-linux", "/usr/lib/ruby/vendor_ruby", "/usr/lib/ruby/1.8", "/usr/lib/ruby/1.8/i486-linux", "/usr/lib/ruby/1.8/i386-linux", "."]

irb(main):004:0> require "json"

LoadError: no such file to load — json

from (irb):4:in `require’

from (irb):4

from :0

irb(main):005:0> gem "json", "= 1.2.0"

NoMethodError: undefined method `gem’ for main:Object

from (irb):5
from :0

O require “json” por si só, carrega a versão mais atual da gem.

irb(main):006:0> require "rubygems"

=> true

irb(main):007:0> require "json"

=> true

irb(main):008:0> JSON::VERSION

=> “1.2.0″

irb(main):009:0> $LOAD_PATH

=> ["/usr/lib/ruby/gems/1.8/gems/gemcutter-0.1.8/lib", "/usr/lib/ruby/gems/1.8/gems/json-1.2.0/bin", "/usr/lib/ruby/gems/1.8/gems/json-1.2.0/ext/json/ext", "/usr/lib/ruby/gems/1.8/gems/json-1.2.0/ext", "/usr/lib/ruby/gems/1.8/gems/json-1.2.0/lib", "/usr/local/lib/site_ruby/1.8", "/usr/local/lib/site_ruby/1.8/i486-linux", "/usr/local/lib/site_ruby/1.8/i386-linux", "/usr/local/lib/site_ruby", "/usr/lib/ruby/vendor_ruby/1.8", "/usr/lib/ruby/vendor_ruby/1.8/i486-linux", "/usr/lib/ruby/vendor_ruby", "/usr/lib/ruby/1.8", "/usr/lib/ruby/1.8/i486-linux", "/usr/lib/ruby/1.8/i386-linux", "."]

irb(main):010:0> quit

Após o require “json”, as pastas foram adicionadas no $LOAD_PATH.

"/usr/lib/ruby/gems/1.8/gems/json-1.2.0/bin", "/usr/lib/ruby/gems/1.8/gems/json-1.2.0/ext/json/ext", "/usr/lib/ruby/gems/1.8/gems/json-1.2.0/ext", "/usr/lib/ruby/gems/1.8/gems/json-1.2.0/lib"

Agora olhe que interessante este último teste:

$ irb

irb(main):001:0> require "rubygems"

=> true

irb(main):002:0> $LOAD_PATH

=> ["/usr/lib/ruby/gems/1.8/gems/gemcutter-0.1.8/lib", "/usr/local/lib/site_ruby/1.8", "/usr/local/lib/site_ruby/1.8/i486-linux", "/usr/local/lib/site_ruby/1.8/i386-linux", "/usr/local/lib/site_ruby", "/usr/lib/ruby/vendor_ruby/1.8", "/usr/lib/ruby/vendor_ruby/1.8/i486-linux", "/usr/lib/ruby/vendor_ruby", "/usr/lib/ruby/1.8", "/usr/lib/ruby/1.8/i486-linux", "/usr/lib/ruby/1.8/i386-linux", "."]

irb(main):003:0> gem "json", "= 1.1.9"

=> true

irb(main):004:0> $LOAD_PATH

=> ["/usr/lib/ruby/gems/1.8/gems/gemcutter-0.1.8/lib", "/usr/lib/ruby/gems/1.8/gems/json-1.1.9/bin", "/usr/lib/ruby/gems/1.8/gems/json-1.1.9/ext/json/ext", "/usr/lib/ruby/gems/1.8/gems/json-1.1.9/ext", "/usr/lib/ruby/gems/1.8/gems/json-1.1.9/lib", "/usr/local/lib/site_ruby/1.8", "/usr/local/lib/site_ruby/1.8/i486-linux", "/usr/local/lib/site_ruby/1.8/i386-linux", "/usr/local/lib/site_ruby", "/usr/lib/ruby/vendor_ruby/1.8", "/usr/lib/ruby/vendor_ruby/1.8/i486-linux", "/usr/lib/ruby/vendor_ruby", "/usr/lib/ruby/1.8", "/usr/lib/ruby/1.8/i486-linux", "/usr/lib/ruby/1.8/i386-linux", "."]

irb(main):005:0> JSON

NameError: uninitialized constant JSON
from (irb):5

irb(main):006:0> require "json"

=> true

irb(main):007:0> JSON::VERSION

=> “1.1.9″

irb(main):008:0> quit

Note que após o gem “json”, “= 1.1.9″ … a versao 1.1.9 foi adicionada no $LOAD_PATH mas não foi carregada. Ao executar o require “json”, como este já estava no $LOAD_PATH, a versão 1.1.9 é usada.

Espero que com estas explicações, você use com mais segurança o rubygems.

Paginação no Rails com will_paginate e Ajax de modo fácil 1

Posted by Rodrigo Panachi on julho 13, 2009

Paginação é um recurso simples e indispensável em qualquer aplicação séria. Em se tratando de Rails, a solução mais popular é a gem WillPaginate que basicamente adiciona o método “paginate_” aos models do ActiveRecord e fornece um helper para renderização dos links da paginação nas views.

Instalando a gem:

sudo gem install will_paginate

Para utilizar na aplicação, adicione no final do config/environment.rb:

require 'will_paginate'

Altere o controller para utilizar paginação:

def index
  @posts = Post.paginate :all, :page => params[:page], :per_page => 10
end

E adicione os links da paginação na view:

<%= will_paginate @posts %>

Pronto! Ao clicar nos links da paginação o parâmetro “page” será incluído automaticamente na requisição.

Legal, mas cadê o “ajax”?

Por padrão o WillPaginate não se preocupa com isso. O próprio desenvolvedor recomenda usar javascript para interceptar o “click” dos links e renderizar o resultado na mesma página.

Outra alternativa seria estender a classe responsável por renderizar os links da paginação para utilizar requisições com ajax.

Inclua em app/helpers o arquivo ajax_will_paginate.rb com o código:

class AjaxWillPaginate < WillPaginate::LinkRenderer
  def prepare(collection, options, template)
    @update = options[:update]
    super
  end
  protected
  def page_link(page, text, attributes = {})
    @template.link_to_remote(text, {
      :url     => url_for(page),
      :method  => :get,
      :update => @update
    })
  end
end

Então adicione no final do arquivo config/environment.rb:

  WillPaginate::ViewHelpers.pagination_options[:renderer] = 'AjaxWillPaginate'

E altere a chamada do helper na view para:

<%= will_paginate @posts, :update => 'div_principal' %>

Informe na opção :update o Id de um objeto html que contenha todo o conteúdo da paginação que será substituído nas requisições seguintes.

É importante lembrar que esta solução altera o comportamento de todos os helpers de paginação da aplicação, por isso deve ser utilizada com cautela. Outras soluções parecidas podem ser encontradas aqui.

Agendando tarefas em aplicações Rails com rufus-scheduler 1

Posted by Rodrigo Panachi on maio 31, 2009

Rufus é um conjunto de gems utilizado para Workflow e BPM. O rufus-scheduler é a gem responsável pelo agendamento e execução de tarefas (jobs). Se você programa em Java e conhece o Quartz não vai ter dificuldade em utilizá-la.

Instalação:

sudo gem install rufus-scheduler

Utilização:

require 'rubygems'
require 'rufus/scheduler'

scheduler = Rufus::Scheduler.start_new

scheduler.every '5m' do
  puts 'Executando a cada 5 minutos'
end

scheduler.schedule '0 18 * * *' do
  puts 'Executando todos os dias as 18h'
end

Simples assim! Consulte a documentação oficial ou contribua com o código.

jQuery DataTables, GitHub API e links da semana 2

Posted by Equipe 1up4dev on maio 04, 2009

jQuery DataTables

Nós somos fãs de jQuery pela sua simplicidade e poder de extensão através de plugins. Falando nisso, este plugin torna qualquer tabela <table/> em um “grid” ordenável, pesquisavel e paginável automagicamente.

Para usar, basta incluir o plugin na página após o jQuery e executar o script:

$(document).ready(function() {
    $("id-tabela").dataTable();
}

No site oficial é possível consultar a documentação, exemplos e a tradução para pt-br.

GitHub API, version 2

Os caras do GitHub não são fracos não… já faz um tempinho, mas antes tarde do que nunca, anunciaram em seu blog a versão 2 do GitHub API. Ela provê acesso à “Repository, User, Commit, Object and Network” e futuramente ao Gist também. Como é a primeira release e ainda estão trabalhando nela, o próprio pessoal do Github pede ajuda a “desbravadores” e que abram tickets, caso encontrem algo. Documentação você encontra em develop.github.com.

Links da semana

8 características de User Interfaces (UI) de sucesso

Nifty Generators para Ruby on Rails

JRuby on Rails no Google App Engine

Grid com ordenação e paginação animados

Guia de Ruby do Why e Autospec-notification

Posted by Equipe 1up4dev on abril 27, 2009

O comovente guia de Ruby do Why

Este livro é sensacional e demonstra exatamente o espírito do Ruby: papo de programador.

O livro é bem divertido. As tirinhas das raposas são ótimas. Várias pessoas contribuíram com a tradução para pt-br que está disponível no Github.

Leitura obrigatória. Chunky bacon!

Autospec-notification

O Autospec é um script gerado pelo RSpec que utiliza o Autotest para rodar os testes automaticamente a cada alteração no código.

Unindo o útil ao agradável, foi criado o Autospec-notification, que exibe as notificações do autospec no desktop.

Para instalar, comece pela gem ZenTest:

sudo gem install ZenTest

No linux, instale o Libnotify:

sudo apt-get install libnotify-bin

Agora instale a gem do autotest-notification:

sudo gem install carlosbrando-autotest-notification --source=http://gems.github.com

Ative o autotest-notification e rode o autospec no seu projeto:

an-install
script/autospec

O código está no Github. Escreva seus testes e divirta-se!

TPW – Testando sistemas legados: classes Utils 2

Posted by Rodrigo Panachi on março 03, 2009

Aproveitando o gancho do post anterior sobre manipulação de dependências, decidi dedicar um post apenas sobre este tema, pois acredito ser de grande ajuda para todos desenvolvedores que precisam manter código legado.

Em projetos legados é comum encontrarmos classes Util (aka Helpers) espalhadas por todo o código, fazendo desde coisas simples como formatar datas ou números, até coisas mágicas como cache de objetos, operações com reflection, escrita de logs, etc. Mesmo que tenham sua “utilidade”, são um terror quando falamos de testes! Além de ser um forte indício de um design fraco, as chamadas a seus métodos, geralmente estáticos, geram dependências nas classes que a utilizam.

Este é a estrutura comum (bem simplificada) de uma classe Util com métodos estáticos:

public class MagicUtil {
    public static String getConstanteSecreta() {
        return "VALOR_SECRETO_AMBIENTE";
    }
}

Neste caso, uma boa estratégia para os testes seria encapsular a chamada da classe Util em um método protected, para que seja sobrescrito na classe de teste, assumindo o comportamento desejado. Porém, se a classe Util for largamente referenciada no projeto (o que é comum) seria preciso refatorar todas a classes que a utilizam para escrever um teste completo.

A estratégia proposta é refatorar a classe Util, aplicando o padrão Singleton e transformando os métodos estáticos em métodos de instância, porém mantendo as assinaturas estáticas, que devem referenciar os métodos da instância. Uma vez que a Util pode ser instanciada (mesmo que internamente), é possível manipular seu comportamento através da injeção de um objeto Mock, por exemplo:

public class MagicUtil {
    private static MagicUtil instance = new MagicUtil();
    protected static MagicUtil getInstance() {
        return instance;
    }
    protected static void setInstance(MagicUtil obj) {
        instance = obj;
    }
    public String getConstanteSecretaInstancia() {
        return "VALOR_SECRETO_AMBIENTE";
    }
    public static String getConstanteSecreta() {
        return getInstance().getConstanteSecretaInstancia();
    }
}

Assim, a Util continua com o mesmo comportamento e seu contrato foi mantido. Agora, no seu teste, basta escrever o mock (estendendo a classe e sobrescrevendo os métodos) e injetá-lo na instância interna da Util:

public class ClasseTest {
    @Test
    public void testaMetodoDependenteDeMagicUtil() {
        new MagicUtil() {
            {
                setInstance(this);
            }
            public String getConstanteSecretaInstancia() {
                return "MEU_VALOR_MOCK";
            }
        };
        Assert.assertEquals("MEU_VALOR_MOCK", MagicUtil.getConstanteSecreta());
    }
}

Neste caso a classe anônima (que estende a Util) passa sua própria instância (this) para o método protegido setInstance(). Note que a chamada do método (estático) da Util continua igual ao da classe original, sem o refactoring.

Nos projetos que preciso manter, esta estratégia tem sido muito útil para resolver os problemas das “teias” de Utils. Porém é um recurso paliativo e não deve ser utilizado como o “padrão”. O ideal é sempre evitar classes Utils, lembrando que uma classe deve sempre ter comportamentos bem definidos e o nome já deve indicar sua responsabilidade.

TPW – Testando sistemas legados: manipulando dependências 3

Posted by Rodrigo Panachi on fevereiro 19, 2009

Pela definição de Michael Feather, código legado é código sem testes! Não importa se o código foi escrito semana passada ou alguns anos atrás. Qualquer manutenção será de difícil entendimento por outra pessoa e não haverá garantias de seu funcionamento. Uma vez que não há “controle”, é mais difícil rastrear as alterações; pior do que uma nova funcionalidade que não funciona, é uma funcionalidade antiga que começa a falhar. Este é um risco que um desenvolvedor não pode correr!

Não altere código legado até que seja possível testá-lo

Um dos problemas mais comuns em sistemas legados é a interdependência de classes, ou seja, o alto acoplamento, que sempre está ligado com a baixa coesão. Se estes termos são difíceis de entender, pense em acoplamento como sendo o grau com que as classes referenciam umas as outras e coesão o quanto uma classe está focada em realizar suas responsabilidades.

Para que seja possível testar o comportamento de uma classe “acoplada”, o comportamento de suas dependências precisa ser simulado. Isto normalmente é feito através de objetos falsos, ou mocks, que são injetados na instância da classe em questão. Este padrão é conhecido como Inversão de Controle e Injeção de dependência, onde o controle sobre as dependências da classe são delegados à outro objeto, ou normalmente um container de objetos, responsável por injetar as dependências nas instâncias das classes. Simples, não?! Mas isso será detalhado em outro post…

Voltando para os testes, no post anterior começamos a organizar o projeto automatizando o build e centralizando a execução dos testes para evitar que fiquem “soltos” pelo código. Agora vamos nos concentrar em escrever os casos de teste, refatorando o necessário para lidar com as dependências das classes.

Partindo da premissa que o projeto não possuí nenhum framework de inversão de controle, utilizaremos um certo “padrão” que permite manipular as dependências de uma classe por meio de herança, sem alterar seu comportamento original. A idéia é resolver as dependências da classe através de getters protegidos, que podem ser sobrescritos em uma classe filha no momento do teste. Isso permite que, na classe estendida, o método sobrescrito retorne um objeto mock, por exemplo, com o comportamento esperado para o teste.

Vamos tomar como exemplo, uma classe simples com algumas dependências e responsável por encapsular algumas regras de negócio referentes à Estoque.

public class EstoqueLogic {
    public boolean verificaDisponibilidade(Produto produto, Integer quantidade) {
        EstoqueDAO dao = new EstoqueDAO();
        Estoque estoque = dao.localizaProduto(produto.getCodigo());
        return estoque.getQuantidade() >= quantidade;
    }
}

Da forma como esta classe foi escrita, é impossível testar a regra de disponibilidade independentemente, pois depende do objeto EstoqueDAO para localizar as informações necessárias para o método. Mas com um pequeno refactoring, a responsabilidade de resolver a dependência EstoqueDAO passa a ser responsabilidade da própria classe Estoque:

public class EstoqueLogic {
    public boolean verificaDisponibilidade(Produto produto, Integer quantidade) {
        EstoqueDAO dao = getEstoqueDAO();
        Estoque estoque = dao.localizaProduto(produto.getCodigo());
        return estoque.getQuantidade() >= quantidade;
    }
    protected EstoqueDAO getEstoqueDAO() {
        return new EstoqueDAO();
    }
}

Desta forma, é possível “injetar” um objeto que simule a dependência do EstoqueDAO estendendo a classe e sobrescrevendo o método getEstoqueDAO() para retornar a instância desejada. O teste ficaria mais ou menos assim:

public class EstoqueLogicTest {

    public void testVerificandoDisponibilidadeDeUmProduto() {

        //Criando o objeto EstoqueDAO mock, simulando o comportamento desejado
        final EstoqueDAO estoqueDAOMock = new EstoqueDAO() {
            @Override
            public Estoque localizaProduto(String codigo) {
                Produto produto = new Produto();
                produto.setCodigo(codigo);
                Estoque estoque = new Estoque();
                estoque.setProduto(produto);
                estoque.setQuantidade(5);
                return estoque;
            }
        };

        //Sobrescrevendo o método getEstoqueDAO para retornar o Mock
        EstoqueLogic logic = new EstoqueLogic() {
            @Override
            protected EstoqueDAO getEstoqueDAO() {
                return estoqueDAOMock;
            }
        };

        //Definindo o teste e executando
        Produto produto = new Produto();
        produto.setCodigo("123456");        

        boolean estaDisponivel = logic.verificaDisponibilidade(produto, 10);
        assertTrue(estaDisponivel);

        boolean naoEstaDisponivel = logic.verificaDisponibilidade(produto, 2);
        assertTrue(naoEstaDisponivel);
    }
}

O método getEstoqueDAO() da classe EstoqueLogic foi sobrescrito para retornar o objeto estoqueDAOMock com as informações necessárias para o teste, ou seja, o comportamento das dependências foi simulado, possibilitando que o teste ficasse concentrado apenas da classe Estoque.

Elimine as dependências e teste onde os bugs estão!

Este padrão fornece apenas uma maneira de lidar com as dependências das classes para escrever testes. A dica aqui é manter o foco: defina apenas testes para as funcionalidades que estiver alterando e que exercitem pontos críticos e/ou regras de negócio. Então, refatore apenas as classes necessárias para simular e validar o fluxo destes testes, nem que seja apenas seus “contratos“. Não há necessidade de escrever testes muito granulares nem alterar todas as classes de um sistema legado.

No caso de classes com muitas dependências, o melhor é refatorá-la, separar as responsabilidades e testá-las individualmente. Para classes com dependências de Utils e/ou muitas chamadas à métodos estáticos, uma estratégia parecida pode ser utilizada. Mas estes são assuntos para os próximos posts. Acompanhem!

TPW – Testando sistemas legados: automatizando o build 2

Posted by Rodrigo Panachi on fevereiro 11, 2009

Imagine o cenário: você caiu de para-quedas naquele projeto que todo mundo na empresa fez gambiarra deu manutenção e agora precisa implementar uma nova funcionalidade. Mesmo que tenha alguma documentação, vai ser inútil neste caso. Então você começa a vasculhar o código e encontra dezenas de classes com nomes parecidos, vários arquivos XML’s dos inúmeros frameworks utilizados anteriormente, TO’s, VO’s, Actions, Utils… ou seja, um lugar cheio de janelas quebradas.

O mais fácil neste caso seria fazer seu trabalho, quebrando mais algumas janelas caso necessário, e cair fora o quanto antes. Mas você, desenvolvedor ágil, sabe que além de ser falta de profissionalismo, você corre o risco de cair novamente no mesmo projeto. Então aproveite a oportunidade para fazer uma pequena faxina e preparar o projeto para escrever os testes das novas funcionalidades que irá desenvolver, garantido assim a qualidade, pelo menos, do seu trabalho.

Durante minha carreira fui obrigado tive a oportunidade de dar manutenção em diversos projetos “frankenstein” de onde adquiri certa experiência para poder “organizar a casa” e trabalhar decentemente no código. É claro que não estou falando em escrever testes para o projeto inteiro, mas pelo menos garantir a qualidade do código que desenvolvi ou irei desenvolver.

Há um certo padrão que todo software de qualidade deve seguir. Além dos padrões e boas práticas como organização do código em pacotes vs. responsabilidade, uma coisa essencial para o projeto é sua construção, ou seja, a forma que o software é “entregue”. Acredito que este seja o primeiro passo para começar a organizar a bagunça de um projeto.

A construção do projeto deve ser automática e desempenhada por uma ferramenta de build como o Ant ou Maven, com tarefas (tasks) e objetivos bem definidos. Normalmente, um build do projeto deve:

  1. Preparar o código e dependências
  2. Compilar os arquivos fontes
  3. Executar os testes
  4. Empacotar a distribuição

No exemplo abaixo usarei o Ant, que é a ferramenta de build (para Java) mais popular do mercado e muito simples de utilizar. O importante neste processo é ser pragmático: não perca o foco! Você poderia (e futuramente deveria) utilizar o Maven, mas adequá-lo a um sistema legado não é tão simples quanto parece.

Tendo os objetivos do build definidos, basta escrever o script do Ant. Isso deve ser feito no arquivo build.xml, no root do projeto. As tarefas são definidas pelas tags <target> e podem ser dependentes para garantir a sequencia de execução. Em cada target são definidas as ações executadas, como rodar o compilador (javac), copiar um arquivo, rodar os testes, etc. Uma vez que as tarefas estão configuradas, basta executar o build pelo próprio IDE ou linha de comando.

<project name="frank" default="package">
    <path id="classpath">
        <pathelement location="build/bin"/>
        <fileset dir="src">
            <include name="*.java"/>
        </fileset>
        <fileset dir="lib">
            <include name="*.jar"/>
        </fileset>
    </path>
    <target name="clean">
         <delete dir="build"/>
          <mkdir dir="build"/>
    </target>
    <target name="compile" depends="clean">
        <javac srcdir="src" destdir="build">
            <classpath refid="classpath"/>
        </javac>
    </target>
    <target name="test" depends="compile">
        <junit>
            <classpath refid="classpath"/>
            <batchtest>
                <fileset dir="build" includes="*Test"/>
            </batchtest>
        </junit>
    </target>
    <target name="package" depends="compile, test">
        <war destfile="frank.war" webxml="web/WEB-INF/web.xml">
        <classes dir="build"/>
    </target>
</project>

Neste script estão definidas as tarefas necessárias para executar um build, conforme citei anteriormente. Por enquanto isto será o mínimo necessário para dar suporte as próximas etapas da sua missão. Mesmo que o projeto já tenha um build em Ant aproveite para revisá-lo. Tarefas bem simples e bem definidas são mais fáceis de entender e manter.

Com o build implementado, você não terá mais que se preocupar com o empacotamento do projeto. Agora comece a direcionar seus esforços para escrever os testes. Deixe que o Ant se encarregará de executá-los para você.

No próximo post vou apresentar alguns padrões e práticas de refactoring utilizados para possibilitar escrever testes que dependem de classes legadas do projeto.