quarta-feira, 25 de novembro de 2009

Utilizando asserts para testar layouts

Um dos problemas encontrados em nossos testes de aceitação é a impossibilidade de validar a aparência exata do resultado final de uma determinada ação do usuário. Conseguimos validar com o watir se determinada div possui um texto, se determinado link está presente, mas nada impede que eles estejam escondidos, por trás de outro div, ou com letras da mesma cor do fundo. É sempre útil, mas não necessáriamente exato, sendo sempre um ponto de falha. Ainda mais em um sistema como o que estamos trabalhando em que o visual para o usuário tem uma grande importância.

Eu e o quixadá, mestre do javascript e meu par de hoje, trabalhamos em uma correção de bug visual, e como costumamos trabalhar com desenvolvimento outside-in, chegamos ao dilema de como criar um teste para garantir que a falha existia. A solução encontrada foi inserir asserts dentro do código javascript, tal qual a funcionalidade de asserts do java. O código da função assert ficou parecido com o mostrado abaixo:

function assert(mensagem,valorDesejado,valorRecebido) {
if( valorDesejado != valorRecebido ) {
var html = '<div class="warning">' + mensagem + '</div>';
$('body').append(html);
throw mensagem;
}
}

Em nosso caso, tinhamos que ter a certeza de que após um clique do usuário a barra de rolagem do elemento mantinha-se da mesma forma que anteriormente. Então o código ficou parecido com o mostrado abaixo:

$.get( url, function ( responseHtml ) {
var scrollAnterior = $('#opcoes').scrollTop();
substituiOpcoes( responseHtml );
assert( 'Deveria manter scroll igual', scrollAnterior, $('#opcoes').scrollTop() );
});

No teste de aceitação verificamos então que o texto 'Deveria manter scroll igual' não deveria aparecer. O código do step do cucumber utilizando o watir pode ser visto abaixo:

Then /^a barra de rolagem deveria permanecer na mesma posição$/ do
@browser.text.should_not include('Deveria manter scroll igual')
end

Com o teste pronto e falhando, aí sim corrigimos a função javascript substituiOpcoes(html) de forma a manter o scroll anterior.

Não é uma solução perfeita. Estamos pesquisando uma melhor, como pode ser vista no post Testes de aceitação automáticos para Flash com T-Plan Robot do Anselmo. Mas enquanto isso podemos evitar alguns pontos de falha visuais que costumávamos ter que testar manualmente. Dado que nosso sistema possui 100% de cobertura de testes unitários, somados a 152 cenários de teste de aceitação que abragem 1568 passos, acho que estamos indo por um bom caminho.

segunda-feira, 9 de novembro de 2009

sexta-feira, 30 de outubro de 2009

API rest para OpenSocial do Orkut com ruby

A documentação da API rest do OpenSocial do Orkut detalha muito bem as opções e formatos de retorno disponíveis porém é um tanto vaga sobre como fazer a autenticação necessária para usá-la. Basicamente lá é explicado os parâmetros a serem enviados e que o protocolo é o OAuth. Então detalho aqui como obter por exemplo os dados de um usuário apartir desta API.

Em primeiro lugar é preciso obter a consumer key e consumer secret de sua aplicação. Isso é feito gerando um token aqui: https://www.google.com/gadgets/directory/verify. Você deve colocar esse token dentro da tag content do xml descritor de sua aplicação e depois fazer a validação provando que é dono da aplicação. Com isso o Google irá lhe informar seu consumer key e consumer secret. Guarde eles com carinho.

Depois, com a gem oauth instalada você deverá executar um código semelhante ao exibido abaixo, com a premissa de que as variaveis consumer_key e consumer_secret estão preenchidas com os correspondentes à sua aplicação. E que a variável id é o id do usuário do Orkut que você está querendo conhecer melhor.

  consumer = OAuth::Consumer.new( 
consumer_key,
consumer_secret,
:site => 'http://www.orkut.com',
:scheme => :query_string,
:http_method => :get
)

request = consumer.create_signed_request(:get,
"/social/rest/people/#{id}/@self?xoauth_requestor_id=#{id}")
res = Net::HTTP.start('www.orkut.com', 80) do
|h| h.request(request)
end

puts res.body

sábado, 1 de agosto de 2009

TralhaController notificando observadores por pattern

TralhaController é uma biblioteca javascript que permite que aplicações web que utilizam requisições assíncronas ofereçam URLs para cada contexto de navegação do usuário. Para saber mais sobre ela basta ler o artigo Mantendo contexto usando ajax que o Bruno Carvalho escreveu recentemente.

Porém uma coisa me incomodava no uso dessa classe. Para cada observador registrado eu precisava dentro do método update verificar se ele precisava agir de acordo com a URL. Isso acaba por tornar o código mais confuso pois obrigava-nos a misturar validações e lógica de negócio. Veja um exemplo abaixo:

var observadorBusca = { update:function(url) {
  if( url.indexOf("#busca=")!=-1 ) {
    //Executa a busca
  }
} };
var observadorLink = { update:function(url) {
  if( url.indexOf("#link=")!=-1 ) {
    //Executa o link clicado
  }
} };
TralhaController.addObserver(observadorBusca);
TralhaController.addObserver(observadorLink);

Para tentar melhorar isso, pus a mão na massa e alterei o TralhaController permitindo registrar observadores que só fossem notificados diante de URLs que tivessem o padrão desejado. Utilizando então a última versão da biblioteca o código acima poderia então ser alterado para ficar da seguinte forma:

var observadorBusca = { update:function(url) { /* Executa a busca */ } };
var observadorLink = { update:function(url) { /* Executa o link clicado */ } };

TralhaController.addObserver("\\#busca\=.*", observadorBusca);
TralhaController.addObserver("\\#link\=.*", observadorLink);

Bem mais simples certo? É claro que há aqueles que não são muito fãs de expressões regulares e vão achar a primeira forma melhor. Para esses não há problema pois a forma antiga continua funcionando. Observadores registrados sem padrões continuam sendo notificados a toda e qualquer modificação de URL.

sexta-feira, 31 de julho de 2009

Roda: Eu também já recriei

Uma das discussões mais quentes do momento, e também uma das mais antigas, é sobre a mania que nós desenvolvedores temos de reinventar a roda. Eu mesmo já reinventei diversas vezes e hoje olho para trás e vejo quanto tempo poderia ter poupado ao usar alguma ferramenta já pronta, ou apenas ter adaptado uma que não se encaixava perfeitamente aos meus requisitos.

Há claro excessões, em que seu requisito é tão mínimo que reimplementar uma solução é mais rápido do que aprender uma já existente no mercado. Como foi o caso de um sistema que estávamos desenvolvendo em PHP e precisávamos de um framework apenas para separar a lógica da renderização de templates por tema. Validamos rapidamente diversos frameworks MVC e percebemos que era mais fácil criar em meia hora a seguinte classe:

class Renderizador {
function Renderizador($tema=null) {
$this->_dir = dirname(__FILE__) . "/templates/" . $this->tema;
}
public function put($nome,$obj) {
$this->$nome = $obj;
}
public function show($template) {
include( $this->_dir . "/" . $template . ".php");
}
}

A utilização dela era mais simples ainda. No exemplo abaixo as variáveis inseridas no renderizador ficam disponiveis para o template listagem.php através de $this, que nada mais é do que um arquivo php comum.

$renderizador = new Renderizador('cinza');
$renderizador->put('lista',[1,2,3,4]);
$renderizador->show('listagem');

Simples, sem precisar aprender nenhum framework ou linguagem de marcação nova. Em duas horas o time já estava trabalhando com a lógica separada da apresentação. Contudo essa é a exceção.

Em geral já recriei diversas rodas desnecessariamente desperdiçando muito tempo. Framework de mapeamento objeto relacional, CMS, blog e até mesmo o TimerTask do Java são alguns dos exemplos. Esse último me rendeu uma longa discussão com o Phillip Calçado na época em que ele trabalhava aqui. Felizmente ele foi persistente em me fazer desistir daquela implementação.

De qualquer forma, o importante é a lição aprendida. Hoje tento usar ao máximo ferramentas já disponíveis, e quando alguma não me atende perfeitamente, ao invés de começá-la do zero eu a altero e contribuo, como foi o caso de uma recente alteração que precisei fazer no fakeweb.

Outros membros da equipe também já vem de longe com esta mesma filosofia e estão constantemente contribuindo como Guilherme Cirne ao contribuir com o WillPaginate, o Anselmo também contribuindo com o Fakeweb e o Bruno Carvalho contribuindo com o TralhaController e o ExceptionNotifier.

O importante é aproveitar aquilo que a comunidade já lhe oferece pronto e testado. Economiza tempo seu, e do próximo desenvolvedor, que dará manutenção a algo que é padrão, com muita discussão em fóruns e muitas soluções documentadas.

sábado, 18 de julho de 2009

Dica rápida de charset para projetos rails com mysql

Se você está trabalhando em um projeto ruby on rails com banco mysql, e o charset definido tanto no servidor como no database.yml é UTF-8, tome o cuidado para que inserções no banco por conta de terceiros também seja feito neste charset.

Um simples insert feito na mão utilizando o cliente padrão do mysql pode te atrapalhar bastante. E o erro ficará bem difícil de encontrar pois este mesmo client exibirá o texto corretamente e só o rails entregará o dado incorreto, fazendo você acreditar que o problema está no ruby.

Para evitar isso, sempre que fizer inserts na mão e de massa de dados execute como primeira linha o seguinte comando:

charset utf8;

quinta-feira, 9 de julho de 2009

Globo Vídeos: Quem viu este vídeo também assistiu

É com muito orgulho que anuncio que acaba de sair para degustação uma nova funcionalidade do Globo Vídeos. Foi uma alteração que durou apenas um dia de desenvolvimento envolvendo o time inteiro e que ficará no ar por um tempo ainda em versão de teste. Trata-se de uma forma de ofertar vídeos baseada nas preferências de nossos usuários. Chamamos a funcionalidade de "Quem viu este vídeo também assistiu".



Como ainda está em versão "beta", para visualizar a alteração é preciso ter o addon Grease Monkey instalado no seu Firefox, e instalar o script que habilita a funcionalidade beta. Depois basta acessar o Globo Vídeos para poder aproveitar a nova facilidade disponível. E se possível, nos dê feedback dizendo o que achou e se encontrou algum problema na utilização. A opinião de vocês é muito importante para que a gente possa melhorar.

Teste de aceitação automático com múltiplas configurações no rails

Dado que uma aplicação possua duas maneiras ou mais de funcionar de acordo com alguns parâmetros de inicialização, como proceder com os testes de aceitação automáticos sem ter que iniciar e parar o servidor entre cada cenário ou grupo de cenários?

A solução simples, mas bem funcional que adotamos, foi ter uma task rake para inicializar vários servidores no ambiente local, cada um com um environment e uma porta diferente. Assim, ao testar uma determinada funcionalidade com determinada configuração basta acessar o servidor naquela porta.

A task rake que poderia até mesmo ser um shell script, ficou mais ou menos como mostrado abaixo. Créditos ao Tiago PacMan, mestre em shell, que fez a linha que finaliza os servidores.


desc 'Inicia servidores para teste de aceitação'
task "server:test" do
system 'script/server -d -e test_conf_1 -p 3001'
sleep 1
system 'script/server -d -e test_conf_2 -p 3002'
sleep 1
system 'tail -f log/test.log'
system "ps aux | awk '/3001/{print $2}' | xargs kill -9"
sleep 1
system "ps aux | awk '/3002/{print $2}' | xargs kill -9"
sleep 1
end

Repare que para este caso fizemos uma modificação no environments.rb para unificar os logs de todas os ambientes de teste.

Já no ambiente de integração contínua, onde utilizamos apache com passenger, foi mais simples ainda, bastando definir para diferentes VirtualHosts RailsEnvs diferentes.

Resolvendo o "missing uri map" no mod_jk

Esses dias instalei o Ubuntu 9 em meu desktop e desde então tenho preparado o ambiente de desenvolvimento de diversas aplicações legadas. Uma delas necessita do apache conectando no tomcat pelo mod_jk. Após fazer a instalação de ambos e ter configurado os pontos de montagem do módulo, tudo de acordo com minha instalação antiga, deparei-me no log com o seguinte erro:

jk_translate::mod_jk.c (3038): missing uri map for tiago.motta:/MeuPathAqui

Descobri em uma grande thread de IRC que o problema ocorre quando o apache possui algum VirtualHost configurado, e que para resolver isso basta habilitar a configuração "JkMountCopy On" no VirtualHost correspondente.

sábado, 6 de junho de 2009

DynamicStream garantindo compatibilidade com flash 9

Ao implementar o DynamicStream em seu flash, teoricamente pararia de funcionar em clientes com a versão 9, pois algumas das classes e métodos necessários para seu uso só estão disponíveis apartir da versão 10.

Para evitar isso a Adobe disponibilizou um artigo mostrando como integrar o DynamicStream em um player antigo, para que só utilize o recurso novo quando o plugin do cliente estiver na versão 10, garantindo assim a compatibilidade. Este artigo possui até mesmo um exemplo, que embora funcione sem problemas em flash 9, quando integrado em outros projetos não funciona.

O problema é um bug na classe de referência que a Adobe oferece em diversos de seus artigos sobre essa funcionalidade. O erro fica evidente quando utilizamos um flash player debugger de versão 9 sobre o swf gerado, até mesmo os de exemplo da empresa.

verifyError: Error #1053: Illegal override of play2 in DynamicStream.

Esse erro ocorre porque a classe DynamicStream de referência sobrepõe o método play2 de NetStream apenas para anulá-lo. Com isso o plugin fica obrigado a verificar a existência do método play2 em NetStream e acaba dando o erro, pois tal método só está disponível apartir da versão 10.

Removendo esse método da classe DynamicStream de referência, tudo passa a funcionar, pois o acesso aos recursos existentes nas versões maiores ficam restritos ao conteudo dos métodos, que só serão avaliados em tempo de execução.

Não fosse o Bruno FMS me dar a dica do flash player debugger, perderia mais um bom tempo testando diversos artíficios para tentar fazer o código rodar em flash 9.

terça-feira, 2 de junho de 2009

Iptable para testar fallback para rtmpt no flash

Para testar se o fallback do seu flash player está funcionando corretamente, e acessando o flash media server pela porta 80 com rtmpt, basta configurar seu iptables para rejeitar ou deletar os pacotes da porta 1935, que é a padrão do FMS:

sudo iptables -A OUTPUT -p tcp --dport 1935 -j REJECT

ou

sudo iptables -A OUTPUT -p tcp --dport 1935 -j DROP

Sem esquecer de ao finalizar seus testes limpar o iptables para continuar seus testes:

sudo iptables -F

segunda-feira, 11 de maio de 2009

Padrões de URLs com expressões regulares no Fakeweb

Ao usarmos o Fakeweb aqui onde trabalho encontramos a necessidade de registrar grupos de URLs de acordo com um padrão. Inspirado no Anselmo Alves, colega de minha equipe que corrigiu um bug nesta mesma gem, alterei-a de forma a permitir o registro de URLs utilizando regex.

Com essa alteração, se por exemplo você quiser que todas as chamadas a um determinado host sejam respondidas com uma determinada string, basta registrar como o mostrado abaixo:

FakeWeb.register_uri(:get, /programandosemcafeina\.blogspot\.com/, :string => "Meu blog")

O principal desafio no desenvolvimento dessa nova funcionalidade foi não quebrar compatibilidade com o uso anterior. Dessa forma, os registros de URLs absolutas passaram a ter uma prioridade maior independente da ordem em que forem registradas. Ou seja, os retornos de acordo com as expressões regulares só são executados se a URL absoluta solicitada não estiver registrada. Veja o exemplo abaixo:

FakeWeb.register_uri(:get, "http://programandosemcafeina.blogspot.com/search/label/rails", :string => "Label Rails")
FakeWeb.register_uri(:get, /programandosemcafeina\.blogspot\.com/, :string => "Meu blog")
FakeWeb.register_uri(:get, "http://programandosemcafeina.blogspot.com/search/label/ruby", :string => "Label Ruby")

Net::HTTP.get(URI.parse("http://programandosemcafeina.blogspot.com/search/label/rails"))
=> "Label Rails"

Net::HTTP.get(URI.parse("http://programandosemcafeina.blogspot.com/search/label/ruby"))
=> "Label Ruby"

Net::HTTP.get(URI.parse("http://programandosemcafeina.blogspot.com/search/label/rmagick"))
=> "Meu blog"

Já solicitei ao Chris Kampmeier a integração do meu fork, mas enquanto ela não é feita, você pode utilizar a gem gerada diretamente do meu repositório no Github. Basta intalar da seguinte forma:

gem sources -a http://gems.github.com
sudo gem install timotta-fakeweb

segunda-feira, 4 de maio de 2009

Undefined method request_uri com HTTParty

Dica rápida para quem está utilizando HTTParty: Se por um acaso você se deparar com o seguinde erro:

undefined method `request_uri' for #<URI::Generic:0xb03919c>
/mnt/apps/filmes/vendor/gems/httparty-0.3.1/lib/httparty/request.rb:56:in `setup_raw_request'
/mnt/apps/filmes/vendor/gems/httparty-0.3.1/lib/httparty/request.rb:39:in `perform'
/mnt/apps/filmes/vendor/gems/httparty-0.3.1/lib/httparty.rb:153:in `perform_request'
/mnt/apps/filmes/vendor/gems/httparty-0.3.1/lib/httparty.rb:119:in `get'
/mnt/apps/filmes/app/models/Filme.rb:25:in 'com_tags'

Não se desespere, URI:Generic realmente não possui o método request_uri. O problema é na incialização do HTTParty. Se base_uri estiver nulo qualquer requisição utilizando a classe obterá esse erro. Teste você mesmo algo como:

class A
include HTTParty
base_uri nil
format :xml
end

A.get('/search/label/ruby')

Ao corrigir a inicialização de base_uri o erro deixa de acontecer:

class A
include HTTParty
base_uri 'http://programandosemcafeina.blogspot.com'
format :xml
end

A.get('/search/label/ruby')

O problema é passível de ocorrer principalmente se base_uri for configurável de acordo com o ambiente em que a aplicação estiver rodando.

quinta-feira, 16 de abril de 2009

Esperando o resultado de uma chamada assíncrona no Watir

Em alguns casos quando uma determinada requisição assíncrona demora para retornar, o teste de aceitação implementado com Watir pode falhar. Principalmente quando os testes estão rodando em alguma ferramenta de integração contínua.

Para evitar essa falha irritante, o usual é colocar alguns sleeps após as ações que disparam as chamadas ajax. Contudo, isso não garante que o teste não vá falhar, especialmente se o sleep for baixo. Em contrapartida se forem colocado muitos sleeps altos poderá haver uma demora muito grande para rodar todos os testes.

Uma solução possível é esperar que um determinado elemento html seja inserido ou removido da página para então continuar executando os testes. Para isso implementei um método genérico que recebe um bloco de verificação. Veja o exemplo de uso dele:

espera { @browser.text.include? 'Ajax retornado' }

O código do método é bem simples e está descrito abaixo. Ele recebe um bloco assumindo que quando este retornar verdadeiro significa que a espera deve terminar, caso contrário ele continuará esperando e verificando, com um timeout de 30 segundos.

def espera
Timeout::timeout(30) do
while not yield
sleep 1
end
end
end

segunda-feira, 16 de março de 2009

Nomes de métodos e variáveis devem ser no idioma do cliente

Recentemente Carlos Brando escreveu um excelente artigo em seu blog sobre nomes de métodos e variáveis. Dentre as diversas dicas, uma eu não concordei, comentei lá sobre isso, e o argumento de resposta não me foi convincente.

O ponto de discordância dizia respeito a necessidade de evitar código bilingue. Segundo ele, já que a maioria das linguagens de programação são em inglês, nada mais correto do que escrever o nome das classes e metódos também em inglês. Para começar vou contar uma pequena historinha:

Experiência própria: Laboratório geológico

Há um bom tempo atrás trabalhei em uma empresa que desenvolvia um sistema para um laboratório geológico. Um dia, um dos desenvolvedores do time ligou para tirar duvidas com o cliente e fez a seguinte pergunta "Quando um sample alcança um result no segundo stage em determinado job, ele pode parar os próximos stages ou deve seguir até a conclusão de todos".

O rapaz na outra ponta ficou completamente confuso. Era novo no laboratório, conhecia toda cadeia de análises de amostras mas não conhecia os estranhos termos que a equipe de desenvolvimento vira e mexe falava. Sample, result e stage não eram palavras de domínio do laboratório, e sim os seus similares em português. Mas como o sistema estava sendo desenvolvido com os termos em inglês, constantemente os membros do time de desenvolvimento, eu incluso, os citava, atrapalhando a comunicação e o entendimento dos problemas.

Eis então a pergunta: Será que um código que mistura termos em inglês e português causa mais problemas que os ruídos na comunicação com o cliente? Pode-se alegar que o código nem será mostrado ao usuário, mas na hora em que o desenvolvedor precisa se comunicar com o cliente, na mente dele ele não está trabalhando com amostras, ele está trabalhando com samples.

Mas o mais interessante é que dentre o domínio do laboratório geológico, um item era em inglês, o Job. Eles falavam amostras, estágio, resultados, mas falavam Job. Isso então nos leva a uma conclusão.

O idioma como ferramenta

A grande questão é que o idioma é apenas uma ferramenta para a comunicação. O primeiro item do manifesto ágil fala "Indivíduos e interação entre eles mais que processos e ferramentas". No caso, acredito que a interação entre os individuos, no caso a comunicação, é mais importante do que a ferramenta, que no caso é o idioma. Portanto eu prefiro priorizar a comunicação independente do idioma. Se meu cliente possuir termos em seu negócio em francês e outros em alemão, eu prefiro utilizá-los no meu sistema para que ao conversar com ele eu não me confunda com os termos usados na programação.

Um contra-argumento muito usado é dizer que utilizar termos em português em uma linguagem de programação em inglês torna o código sujo. Mas será que isso é realmente verdade?

Código limpo depende do idioma?

Antes de discutir é preciso definir o que é um código limpo. Em minha concepção código limpo é aquele fácil de entender. As técnicas apresentadas pelo Carlos Brando são realmente importantes. Mas será que o seguinte código é complicado de entender?

amostras = Amostra.obter_amostras_com_resultado_positivo
amostras.each do |amostra|
puts amostra.produto.sigla
end

Será que o fato de utilizarmos inglês misturado com o português tornou mais dificil o seu entendimento? Eu não só creio que não, como acredito que por conta das expressões utilizadas pelo cliente serem idênticas ao do programa, o entendimento fica mais fácil ainda.

Dizer o contrário é o mesmo que defender o uso de arcabouço e chamada de retorno ao invés de framework e callback, em trabalhos acadêmicos somente por uma questão de purismo idiomático. É nesse público que políticos como o deputado Aldo Rebelo se mira, tentando aprovar projetos de lei perigosos.

Mas e o mercado internacional?

Outro ponto levantado para defender o uso de termos diferentes dos do domínio do cliente é a possibilidade de venda do software ao exterior em algum remoto dia. Ao meu entender isso é um pouco de síndrome do mapa de calor. Um problema comum em empresas de desenvolvimento que não seguem o manifesto ágil. Este tipo de argumento futurista era muito utilizado para defender que aplicações fossem escritas em Java. Coisas do tipo "E se um dia quisermos colocar esse software numa geladeira".

O interessante é que na resposta que recebi, foi citado que para não haver confusão nas conversas com o cliente poderíamos fazer as especificações em português. Mas oras, quando um software é vendido, as especificações vão juntas. Se for vendido para o exterior teríamos o mesmo problema. E pior ainda, as especificações, que dizem exatamente o que o sistema faz estariam em uma lingua diferente da do cliente.

O fato é que a possibilidade de se vender um sistema ao exterior em algum futuro remoto não deve se tornar um empecilho na comunicação com o cliente local, que é quem vai gerar receita mais brevemente. Se um dia realmente a venda for uma possibilidade real faz-se um refactory.

Só para constar, a desculpa para desenvolver o sistema exemplificado no ínicio do post em inglês foi exatamente esta. Hoje, 6 anos depois o foco da empresa mudou totalmente, nenhum outro sistema de laboratório foi vendido ou criado. Somente este continua em manutenção. Dos outros que desenvolvi em Java nenhum precisou trocar de sistema operacional ou mesmo ser instalado em geladeiras.

Enfim a conclusão

O que defendo não é programar em um idioma e não em outro. Defendo que deve-se programar o mais próximo da realidade. Se o seu cliente tiver todos os termos do seu negócio em inglês, ótimo, programe em inglês. Fora isso, vejo apenas dois outros motivos para basear sua programação no idioma britânico: O software que você está desenvolvendo é uma biblioteca ou framework open-source; Ou a maioria dos membros da sua equipe só fala inglês.

quinta-feira, 12 de março de 2009

Alias e functions para seu dia-a-dia com git

Embora o Git seja uma poderosa ferramenta de controle de versão, a quantidade de passos que é necessário para executar alguns procedimentos básicos pode dificultar um pouco o dia-a-dia do desenvolvedor. Principalmente se o projeto em questão não requer todas as funcionalidades que o git oferece para compartilhar código entre grandes equipes espalhadas.

Felizmente é possível criar alguns alias e functions para facilitar o seu uso. Dessa forma podemos utilizar o git de maneira mais simples, e quando houver a necessidade de usar um dos seus recursos avançados, basta não usar esses atalhos. Segue abaixo os que tenho utilizado:

gitshazam

Adiciona todos os arquivos criados, modificados, deletados e depois comita para o repositório local com a mensagem informada.

alias gitshazam='git add -u && git add . && git commit -m'

Exemplo de uso:

gitshazam 'Minha mensagem de commit'
git push

gitcoleradodragao

Sincroniza com o repositório remoto as informações dos branchs existentes e os exibe, mostrando também em que branch você está desenvolvendo atualmente.

alias gitcoleradodragao='git fetch && git branch -a -v'

gitpodediamante

Cria um branch local com o nome informado e o associa ao branch de mesmo nome no repositório remoto.

function gitpodediamante() { git checkout --track -b $1 origin/$1; }

Exemplo de uso:

gitpodediamante 'historia-22'

gitmeteorodepegasus

Baixa as ultimas atualizações no master e no branch indicado no parâmetro, e posteriormente faz o merge do master dentro deste branch.

function gitmeteorodepegasus() { git checkout master;git pull;git checkout "$1"; git merge master; }

Exemplo de uso:

gitmeteorodepegasus 'historia-22'


Agradecimentos especiais à galera da minha equipe que tem me ajudado bastante com o git e aguentado minhas reclamações.

quinta-feira, 26 de fevereiro de 2009

Duck typing e os testes de aceitação automáticos

Uma discussão frequente em minha equipe é sobre maneiras de garantir que nossos sistemas estejam funcionando adequadamente por meio de testes automáticos. Em nossos ultimos projetos temos conseguido alcançar 100% de cobertura de testes unitários, e nos sistemas legados temos aumentado a cobertura aos poucos.

No entanto, estamos cientes de que nem 100% de cobertura de testes unitários garantem a ausência de bugs. Isso nos remete aos testes de aceitação automáticos, não só por causa do funcionamento das telas e fluxos de navegação, mas também dos possíveis problemas que um refactoring com um pouco menos de atenção pode causar em uma linguagem baseada em duck typing.

Darei um exemplo em ruby para ilustrar um bug que pode ser introduzido sem que seus testes unitários percebam. Digamos que você tenha as seguintes classes:

class Filme < ActiveRecord::Base do
def alugar
self.estoque.remover self
end
end
class Estoque < ActiveRecord::Base do
def remover(filme)
# ...
end
end

No seu spec que verifica o método alugar da classe Filme você colocaria um mock mais ou menos como mostrado abaixo:

@estoque = mock_model(Estoque)
@estoque.should_receive(:remover).with(@filme).once

Um dia você percebe que o nome do método remover não está explicando muito bem o significado, e resolve fazer um refactoring renomeando-o para remover_fita_do_filme.

Você altera os testes da classe Estoque, renomeia o método e logo depois recebe uma ligação de uma empresa de telefonia te oferecendo serviços excepcionais que você nunca precisou. Puto da vida você desliga o telefone, roda os testes e todos passam! Serviço feito!

Perceberam o problema? Sem um teste de aceitação automático ou no mínimo um teste manual, você não perceberia que a classe Filme continua referenciando o método antigo, remover, da classe Estoque. Esse tipo de problema não passaria em uma linguagem que não segue duck typing, pois a etapa de verificação de código da compilação serviria como uma espécie de teste unitário de tipos e nomes.

Um problema mais evidente seria um método que recebe um objeto que precisa ter determinado método, ou seja implementar determinada interface. Veja o exemplo abaixo, que mostra que ao alugar um item, no caso um Filme, adiciona-se todas as tags dele às tags preferenciais do cliente:

class Cliente < ActiveRecord::Base do
def alugar(item)
#...
adiciona_tags_preferenciais item.tags
end
end
class Filme < ActiveRecord::Base do
def tags
# ...
end
end

Se mudarmos o nome do método tags em Filme e também corrigirmos o uso de dele no método alugar da classe Cliente tudo funcionará bem. Mas, como usamos duck typing não temos como garantir que outros objetos, digamos alugáveis, tenham sido alterados. Se por exemplo esta locadora também alugas livros, teríamos um erro evidente:

class Cliente < ActiveRecord::Base do
def alugar(item)
#...
adiciona_tags_preferenciais item.tags_principais
end
end
class Filme < ActiveRecord::Base do
def tags_principais
# ...
end
end
class Livro < ActiveRecord::Base do
def tags
# ...
end
end

Esse tipo de erro só seria pego num teste manual se lembrássemos de testar o aluguel de livros também. Por isso é tão importante o teste de aceitação automático em uma linguagem com duck typing. Em Java por exemplo, que a interface precisa ser explícita, o erro seria pego no que poderíamos considerar o teste unitário que a compilação executa. As classes Filme e Livro implementariam a interface Alugavel por exemplo:

public interface Alugavel {
List tagsPrincipais();
}

Mas é claro que o uso de uma liguagem menos dinâmica não remove a necessidade de testes de aceitação automáticos. O valor desse tipo de testes é independente da linguagem, e sua implementação é importantíssima para garantir fluxos de navegação e integração entre os diversos componentes do sistema.

quinta-feira, 19 de fevereiro de 2009

Plugin do rails para copiar erros de um model para outro

Em um projeto pessoal precisei desenvolver uma maneira de copiar os erros de um model para outro. Como é uma funcionalidade que outrora já havia precisado, aproveitei para criar um plugin e disponibilizá-lo para quem mais tiver esse mesmo problema.

O plugin está disponível no GitHub pelo endereço http://github.com/timotta/copy_errors_from/tree/master. Para instalar no seu projeto basta rodar a seguinte linha:

script/plugin install git://github.com/timotta/copy_errors_from.git

Após instalar todos os seus models terão o método copy_errors_from, que pode ser utilizado como mostrado abaixo:

> filme = Filme.new :titulo => 'Corra que a polícia vem aí'
> ator = Ator.new
> filme.atores.push ator
> filme.save #return false
> filme.errors.entries #return []
> ator.errors.entries #return [['nome','Não pode ser vazio']]
> filme.copy_errors_from ator
> filme.errors.entries #return [['ator_nome','Não pode ser vazio']]
> filme.errors.on(:ator_nome) #return 'Não pode ser vazia'

sexta-feira, 23 de janeiro de 2009

Cuidado ao cachear named_scope no Rails

Deve-se tomar cuidado ao cachear o acesso a um named_scope pois ele tem um carregamento de forma preguiçosa, o famoso lazy load. Assim, o código abaixo gravaria no cache não os atores femininos, mas sim todos os atores.

class Filme
#...
def atores_femininos_cacheado
Rails.cache.fetch("#{self.id}:atores-principais") { self.atores.femininos }
end

class Ator
#...
named_scope :femininos, :conditions => { :sexo => 'F' }

Se você observar o log, verá que o rails executará a query sem as condições definidas na classe Ator para o named_scope :femininos. E que após ser cacheado, será essa a query que deixará de ser executada.

A query para buscar atores femininos do named_scope só será realmente executada quando algum método do objeto retornado pelo método .atores.femininos for chamado. Veja no exemplo abaixo:

@filme = Filme.find_by_id 10
filme.atores_femininos_cacheado.last

Ou seja, o cacheamento não está servindo ao propósito definido. Uma solução para isso é cachear diretamente o resultado de um find, como pode ser visto abaixo:

class Filme
#...
def atores_femininos_cacheado
Rails.cache.fetch("#{self.id}:atores-principais") do
Ator.find_all_by_filme_id self, :conditions => { :sexo => 'F' }
end
end

quarta-feira, 21 de janeiro de 2009

PGError com Rails no Heroku

Essa dica é para quem utiliza o Heroku Garden para desenvolver suas aplicações rails. Ao rodar os specs diretamente da interface web do Heroku pode ser que um dia você se depare com o seguinte erro:

PGError: ERROR: relation "filmes" does not exist
: SELECT a.attname, format_type(a.atttypid, a.atttypmod),
d.adsrc, a.attnotnull
FROM pg_attribute a LEFT JOIN pg_attrdef d
ON a.attrelid = d.adrelid AND a.attnum = d.adnum
WHERE a.attrelid = 'filmes'::regclass
AND a.attnum > 0 AND NOT a.attisdropped
ORDER BY a.attnum

O erro acontece porque o link para rodar as migrations, que aparece na interface do heroku, não as executa no ambiente de testes. Portanto, é preciso abrir a tela do rake e executar as seguintes linhas antes de rodar seus specs.

db:migrate
db:test:prepare

UPDATE

Descobri outro problema do Heroku. O schema.rb é criado e alterado no diretório tmp. Dessa forma para que o PGError não dê novamente em outra atualização do banco, você precisa copiá-lo para o diretório db e então no console do rake rodar:

db:test:clone