Posts de ‘Tiago Motta’

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

Thursday, July 9th, 2009

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 'RAILS_ENV=test_conf_1 script/server -d -e test -p 3001'  sleep 1  system 'RAILS_ENV=test_conf_2 script/server -d -e test -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 1end

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.

[Tiago Motta] Resolvendo o "missing uri map" no mod_jk

Thursday, July 9th, 2009

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.

[Tiago Motta] DynamicStream garantindo compatibilidade com flash 9

Saturday, June 6th, 2009

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.

[Tiago Motta] Iptable para testar fallback para rtmpt no flash

Tuesday, June 2nd, 2009

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

[Tiago Motta] Padrões de URLs com expressões regulares no Fakeweb

Monday, May 11th, 2009

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.comsudo gem install timotta-fakeweb

[Tiago Motta] Undefined method request_uri com HTTParty

Monday, May 4th, 2009

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 :xmlend

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 :xmlend

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.

[Tiago Motta] Esperando o resultado de uma chamada assíncrona no Watir

Thursday, April 16th, 2009

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  endend

[Tiago Motta] Nomes de métodos e variáveis devem ser no idioma do cliente

Monday, March 16th, 2009

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_positivoamostras.each do |amostra|  puts amostra.produto.siglaend

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.

[Tiago Motta] Alias e functions para seu dia-a-dia com git

Thursday, March 12th, 2009

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'

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

[Tiago Motta] Duck typing e os testes de aceitação automáticos

Thursday, February 26th, 2009

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  endendclass Estoque < ActiveRecord::Base do  def remover(filme)    # ...  endend

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  endendclass Filme < ActiveRecord::Base do  def tags    # ...  endend

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  endendclass Filme < ActiveRecord::Base do  def tags_principais    # ...  endendclass Livro < ActiveRecord::Base do  def tags    # ...  endend

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.