Pular para o conteúdo
dezembro 28, 2010 / cassiomarques

Meu Ambiente de Desenvolvimento

Alguém começou com a idéia de descrever seu ambiente de desenvolvimento e passar a bola para outros desenvolvedores, criando uma sequência de posts sobre o assunto. O Cairo Noleto descreveu o ambiente dele e passou a bola para mim e mais alguns outros. Aqui vai minha participação.

Máquinas / Sistemas Operacionais

Uso um MacBook Pro 13″ como minha máquina principal para desenvolver. Tenho outro notebook com Ubuntu que no momento está fora de combate, com o HD danificado. Quando estou com ambas as máquinas funcionando uso o Sinergy para compartilhar teclado e mouse entre as duas máquinas, funciona muito bem. Quando estou em casa mantenho o Mac ligado o tempo todo a um monitor externo de 22″, deixando no monitor do Mac somente um terminal aberto com algum log que precise acompanhar ou coisas do tipo.

Terminal

Inicia automaticamente quando ligo a máquina. Tentei usar o zsh mas acabei voltando pro bash. Talvez tenha sido falta de paciência para configurar melhor as coisas e parar para aprender de verdade. Ou não. :)

Mantenho sempre pelo menos 3 abas abertas. Se estou trabalhando em um projeto Rails, por exemplo, mantenho uma aba só para os logs, outra rodando uma sessão do tmux dividida ao meio, um lado para rake tasks, rodar testes, etc, e o outro lado com o console do Rails. A terceira aba deixo para uso geral, independente do projeto. Às vezes posso ter mais uma outra aba aberta conectada a algum servidor via ssh. E como citado acima, às vezes mantenho outra janela do terminal aberta no monitor do notebook.

Editor

Passo 100% do tempo com o MacVim aberto. Já era usuário do Vim antes de usar Mac e comecei a usar bem antes da nova moda :) Não cheguei a usar TextMate porque quando usava Linux já me virava bem com o Vim e não vi porque aprender outro editor quando fui pro Mac. Tenho estudado Objective-C e estou tentando usar o XCode só para interagir com o projeto e criar as telas, mas tento editar o código sempre pelo Vim também. Sempre que tento usar outro editor, digito como se estivesse no Vim e só faço besteira. Esse foi um dos motivos que me fez usar o Eclim para desenvolver em Java, quando preciso, o que tem sido raro.
Tenho usado o PeepOpen para abrir arquivos no MacVim e tem sido legal. Mas em aplicações Rails ainda prefiro bem mais usar os atalhos do Rails.vim.

Browser

Mantenho o Chrome 100% do tempo aberto e uso como browser principal enquanto estou desenvolvendo. Só abro outros browsers para testar o resultado final das coisas (e fecho o mais rápido possível). Preciso me educar melhor e tentar manter menos abas abertas ao mesmo tempo, mas quando tenho 20 abas abertas, considero que estou me controlando :)

Outros softwares

  • Tunnelblick: Cliente opensource para VPNs que usem o OpenVPN. Preciso estar conectado o dia todo à rede de um dos meus clientes e esse programinha funciona perfeitamente bem.
  • Adium e Skype: Indispensáveis para quem trabalha remotamente como eu e precisa fazer reuniões com a equipe, falar com os clientes, etc
  • iTunes: Aberto 100% do tempo.
  • Git: Porque eu versiono tudo que posso, não só projetos mas também documentos pessoais

Música

Ouço música 100% do tempo quando estou trabalhando. Além de ser viciado (quem não gosta de música é louco), por trabalhar em casa e ter N distrações que podem me atrapalhar, a música ajuda a me manter concentrado e manter o foco.

É isso. Acho que meu ambiente é bastante simples, nada que fuja do normal acredito eu. Passo a bola agora pro @jtadeulopes, pro @thiagopradi e pro @qmx.

novembro 1, 2010 / cassiomarques

Usando filter-objects para organizar melhor o códigos dos controllers

O recurso de filtros do Rails é bem útil. É difícil encontrar controllers em apps Rails que não possuam pelo menos um filtro sendo executado. Às vezes esses filtros são simples, como os filtros para verificar permissões ou se o usuário está autenticado. Entretanto muitas vezes a lógica envolvida em um filtro é complexa. Nestes casos é fácil perder o controle e começar a escrever filtros extremamente longos e complexos, poluindo nossos controllers. Além de bagunçado, começa a ficar bem difícil testar esses filtros.

Imagine o seguinte controller:

class FoosController < ApplicationController
  before_filter :do_something_complex

  protected
  def do_something_complex
    # dezenas de linhas ...
  end
end

Esse é o esquema padrão de escrever filtros em Rails. Mas pode ficar ainda pior…

class FoosController < ApplicationController
  before_filter :do_something_complex

  protected
  def do_something_complex
    check_whatever
    destroy_the_world
  end

  def check_whatever
    # dezenas de linhas ....
  end

  def destroy_the_world
    # dezenas de linhas...
  end
end

Haja código bagunçando nosso controller!

Filtros como objetos

O Rails permite o uso de classes como filtros, para tirar a lógica do controller. Algo mais ou menos assim:

class FoosController < ApplicationController
  before_filter MyAwesomeFilter  # continua aceitando as opções :only e :exclude
end

class MyAwesomeFilter
  def self.filter(controller)
    # ...
  end
end

Já é melhor do que entupir nosso controller com código que não deveria estar ali, mas ainda não é o ideal, visto que usa métodos de classe, transformando nosso filtro em nada mais do que um namespace para funções procedurais. O fato é que o before_filter aceita qualquer objeto que responda ao método filter(controller) (lembre-se que em Ruby uma classe é também um objeto!). Podemos melhorar o design fazendo algo como:

class FoosController < ApplicationController
  before_filter MyAwesomeFilter.new 
end

class MyAwesomeFilter
  def filter(controller)
    # sua lógica, mas seja bacana e refatore!
  end
end

Bem melhor! Mas ainda temos um problema. Nosso filtro é uma classe comum e não possui acesso direto à nada disponível dentro do nosso controller, pois o escopo aqui é outro. A prática comum é usar a instância do controller que o método filter recebe:

class MyAwesomeFilter
  def filter(controller)
    SomeObject.run controller.params[:foobar]  
    controller.flash[:notice] = "Some message"
    controller.redirect_to some_path
  end
end

Estamos delegando um monte de chamadas para o nosso controller, mas acho que não é necessário escrever controller tantas vezes. Vamos delegar da maneira Ruby:

require 'forwardable'
class MyAwesomeFilter
  extend Forwardable

  def_delegators :@controller, :redirect_to, :render, :params, :flash # .. e qualquer outro método que você queira 

  def filter(controller)
    @controller = controller
    flash[:notice] = "Some message"
    redirect_to some_path
  end
end

Com o module Forwardable podemos declarar quais chamadas serão delegadas e para quem. Ele é nativo do Ruby, dê uma olhada na documentação.

Mas e se formos criar outros filtros? Vamos ter que declarar os delegators novamente? Não!

require 'forwardable'
class MyBaseFilter
  extend Forwardable

  def_delegators :@controller, :redirect_to, :render, :params, :flash

  def filter(controller)
    @controller = controller
  end
end

class MyAwesomeFilter < MyBaseFilter
  def filter(controller)
    super(controller)

    # sua lógica de filtro
  end
end

Com essa estratégia, você pode criar outros delegators usados somente em um filtro específico. Esses delegators serão somados aos já declarados na classe base:

class MyAwesomeFilter < MyBaseFilter
  def_delegators :@controller, :foo, :bar
end

Onde salvar esses arquivos com os filtros? Eu gosto de criar uma pasta em app/controllers/filters e colocar os filtros ali. Para facilitar as coisas, adicione essa nova pasta ao autoload do Rails:

module YourApp
  class Application < Rails::Application
    config.autoload_paths << "#{Rails.root}/app/controllers/filters"
  end
end

Espero que com isso você possa organizar um pouco melhor seus controllers, além de poder testar melhor os filtros.

setembro 26, 2010 / cassiomarques

Sobre as razões para testar e porque você pode estar fazendo tudo errado

Há bastante tempo tenho uma certa paranóia com testes automatizados. É uma coisa que foi acontecendo gradativamente. Hoje posso dizer que não consigo mais desenvolver sem testes. Quem acompanha este blog sabe que a nos últimos meses a grande maioria dos posts foram sobre este assunto. É algo que eu gosto de praticar e debater.
Mas conforme o tempo foi passando certos conceitos foram amadurecendo na minha cabeça. Conceitos que anteriormente eu via como “sagrados” passaram a perder parte da importância, assim como aspectos que ainda eram um pouco obscuros começaram a se mostrar mais claros. Como resultado desse processo, eu passei a ver os testes sob uma perspectiva diferente. Talvez diferente do que parte da literatura existente sobre o tema diz, talvez uma visão só minha.

Porque testar?

Todas as idéias sobre as quais tenho pensado giram em torno de uma questão que superficialmente parece simples, mas que não é: Por que testar? As respostas podem variar um pouco, mas boa parte delas vai girar em torno de conceitos já extensivamente debatidos em palestras, livros e blogs. Sobre os motivos já existentes, vou listar as minhas razões para testar.

Segurança durante o desenvolvimento e evolução das aplicações

É importante testar para ter segurança durante o desenvolvimento, tendo garantias um pouco melhores (mas não certeza absoluta, isso é impossível) de que novas alterações no código não vão quebrar o que já está funcionando. Isso é notoriamente importante em equipes grandes, onde os desenvolvedores podem não conhecer a fundo toda a base de código. Por outro lado, mesmo se o projeto seja desenvolvido por um único programador, é dificil prever onde efeitos colaterais de uma alteração vão aparecer.
Outra situação semelhante onde possuir uma boa suíte de testes se mostra uma grande vantagem é no momento de realizar atualizações nas versões das bibliotecas e frameworks utilizados. Como em diversos upgrades não há garantia de compatibilidade com versões anteriores, os testes ajudam a indicar pontos que necessitam de ajustes.

Nos colocar no lugar de quem vai usar o código que estamos escrevendo

Muitas vezes sabemos o que precisamos fazer, mas não temos certeza absoluta de como chegar lá. Um caso clássico é quando precisamos criar alguns componentes no código e disponibilizar isso através de alguma interface pública. Da mesma forma com que gostamos de usar bibliotecas e componentes que expõe interfaces claras, simples e eficientes, assim devem ser as interfaces que criamos. Acontece que é dificil imaginar qual seria a melhor forma de chamar um método ou quais parâmetros esses métodos deveriam receber. Qual é a melhor forma de instanciar objetos? Será que essa parte deve aceitar um bloco? E aqui, deve ir um Hash ou um Array? Não dá para saber quais seriam as melhores opções se não nos colocarmos no lugar de quem vai usar o código. Esse alguém pode vir a ser você mesmo, seus companheiros de equipes ou no caso de OSS, um desconhecido. E é ai que o TDD mostra todo seu poder: Escrevendo testes antes, sem código algum para a implementação dos seus componentes, você pode fazer diversos experimentos sobre como devem ser as APIs. Quando a coisa parecer natural de usar (pois é exatamente isso que você estará fazendo ao escrever os testes), implemente. Então, aos poucos, evolua seu design e refatore tudo.

Segurança para refatorar

Para refatorar o código é imprescindível termos uma boa cobertura de testes automatizados. Se não tivermos testes, o que prevalece é sempre a frase “Pra que mexer no que está funcionando?”. Porque mexer? Porque nem tudo que funciona presta e por que devemos manter a casa em ordem. Precisamos facilitar a evolução do código. Mas para isso precisamos de coragem. Neste caso nossa coragem é a nossa suíte de testes.

Criação de documentação executável

Para mim muitas vezes ajuda muito olhar os testes de um projeto para entender as regras de negócio. Não estou falando de entender como a coisa foi implementada e porque funciona, mas sim de entender o que a aplicação faz. Isso faz muito sentido quando se começa a mexer em um projeto novo, os testes ajudam a compreender melhor as motivações por trás do código. Não acredito 100% na utilização de testes para substituir totalmente documentação escrita, mas os testes evoluem de forma muito mais natural do que documentação escrita (quando esta existe).

Nos certificarmos de que todas as peças se encaixam

Na formas de modelagem de software utilizados atualmente construímos aplicações em camadas. Temos níveis de abstração distintos e todos eles devem funcionar em conjunto, mas podem ser devidamente isolados. Enquanto isolados, utilizamos testes unitários para especificar cada comportamento de forma independente. Porém isso não é o suficiente, nem do ponto de vista técnico e nem do ponto de vista do usuário, pois:

  • O que funciona muito bem de forma separada pode não funcionar tão bem assim em conjunto.
  • Precisamos saber se a interação do usuário com a aplicação atende ao que foi especificado.

Aqui entram os testes de integração. Coisas como o nosso velho conhecido Cucumber, que testam nossa aplicação de ponta à ponta, interagindo com nossas views (ou até mesmo com nossas CLIs se estivermos desenvolvendo aplicações para linha de comando) e passando por todas as camadas que a compõe. Só assim para termos uma garantia um pouco melhor de que todas as peças funcionam em conjunto.

Você está testando pelas razões corretas? Qual o impacto dos testes no seu dia a dia? É positivo?

E então chegamos ao cerne deste post: Você testa porque enxerga em tal prática algo que realmente traz benecífios ao projeto? Ou você testa somente porque os fodões também testam e você tem que dançar conforma a música?

Escrever os testes depois de ter escrito a implementação

Esse é o sintoma mais claro de que você escreve testes por obrigação e não porque eles te ajudam de alguma forma. Claro que em alguns casos escrever os testes depois fazem sentido, como quando você quer entender melhor como uma parte do código que você não conhece (e que não possui testes) funciona ou antes de tentar refatorar alguma parte do código que não possui cobertura de testes. Mas se você sempre escreve os testes depois, você está testando pelas razões erradas.
Quando você escreve o teste após ter feito a implementação você não está especificando o comportamento esperado, você está apenas forçando o teste a passar. Isso faz com que você vá sempre pelo caminho mais simples, o que na grande maioria das vezes não prevê as condições de contorno do seu problema. Em outras palavras, você só vai testar as condições onde tudo dá certo naturalmente. Você vai achar que está coberto, mas na verdade bugs vão te pegar pelo caminho, tal qual teriam pego caso você não houvesse escrito teste algum.

Escrever grandes blocos de setup para seus testes e achar que isso é natural

Quando você escreve testes, seus blocos setup ou before são muito longos? Saiba que isso pode indicar que seu design está errado. Confesso que passei por isso diveras vezes e fui relapso. Paguei o preço mais tarde, com designs que apresentavam alto acoplamento e dificil manutenção. Quando você precisa escrever linhas e mais linhas de código apenas para montar um cenário no qual uma característica de um objeto possa ser testada, isso indica que seu objeto pode possuir alguns dos seguintes problemas:

  • Alta dependência de objetos de outras classes.
  • Conhecimento excessivo de detalhes de implementação dos objetos de outras classes.
  • Interfaces frágeis, onde pequenas alterações de código fazem com que grandes porções da aplicação precisem ser reescritas.

O problema se encontra em ver isso acontecer e acreditar que é normal, que é apenas um efeito colateral da prática de escrever os testes. Não é. Isso indica que seu design deve ser melhorado e se você ignora esse sinal, está testando pelos motivos errados. Os testes não servem apenas para que você fique feliz quando tudo está verde e se sinta preocupado quando algo fica vermelho. Eles estão ali como guias. Testes são poderosos, eles podem prever o futuro. Eles mostrm que seu código está embolado e que as coisas poderiam estar melhor organizadas. Não ignore esse sinal, reescreva, refatore.
Algo que colabora para que isso seja interpretado como um resultado natural da escrita de testes é a lenda da correlação entre alto índice de LOC de teste versus LOC de implementação e uma provável alta cobertura dos testes. Indices assim não são necessariamente sinais de boa cobertura de testes, porque você pode estar escrevendo muito código de setup, quando na verdade seus testes deveriam ser longos por testarem diversos cenários e condições diferentes, exercitando sua implementação.

Ser bonzinho com o código

Quando estiver escrevendo testes, você deve ser um carrasc@. Deve preferencialmente esquecer que quem vai implementar aquilo é você mesm@. Somente assim você vai pensar em escrever testes para todos (ou o mais próximo disso) casos possíveis. Só assim, usando o chapéu de Q&A e massacrando seu código com as possibilidades mais absurdas e maldosas você vai ter certeza de que tem uma boa cobertura e de que sua implemetação é suficientemente robusta. Não existe algo como “isso nunca vai acontecer, é pouquissímo provável!”. Vai acontecer sim e vai estourar no seu colo. Por isso, teste cada caso, não tenha pena de si mesmo ou do seu código.

Não ser crítico

Atualmente ferramentas para testes automatizados são extremamente numerosas. É dificil escolher uma para usar. Mas ainda mais dificil é ser capaz de enxergar as limitações de uma determinada ferramenta e sair da zona de conforto e aprender algo novo quando aquilo que se conhece não atende nossas necessidades. Sou testemunha disso e nos últimos meses quebrei a cara por pelo menos duas vezes por ser assim, mais especificamente em Ruby.

Primeiro, finalmente percebi que o Rspec não é a solução para tudo, apesar de ser extremamente poderoso, bem documentado e amplamente utilizado. O Rspec é mais uma dependência e o bom senso tem me mostrado que quanto menos dependências em seus projetos você tiver, mais feliz você será a longo prazo. O que estou começando a fazer é usar o Rspec apenas para aplicações. Gems e plugins estou começando a testar com as ferramentas nativas do Ruby (Test::Unit e agora no Ruby 1.9 o Minitest). Uma gem a menos para baixar, menos dependência externa e maior facilidade para quem quiser ajudar e implementar novas funcionalidades.

Percebi também que nada é perfeito. Após praticamente dois anos escrevendo cenários do Cucumber diariamente, notei que essa prática não é nem um pouco produtiva e é necessário algo que justifique todo esse trabalho. A idéia do Cucumber é simplesmemte fantástica, mas ela espera um cenário que em muitas vezes é praticamente utópico: A participação ativa do cliente durante o desenvolvimento. Não vou discutir aqui sobre as implicações disso nas práticas ágeis de desenvolvimento, mas a questão é que o mundo real nem sempre é belo como os livros contam. Muitas vezes você vai estar tendo um trabalho enorme para criar algo que poderia agregar valor para o cliente mas que ele nunca vai olhar.
Após assistir à palestra do Daniel Lopes sobre o Steak, a coisa acabou por fazer sentido para mim de uma vez por todas. Ainda precisamos escrever testes de integração (e na minha opinião esses são os testes mais importantes no fim das contas), mas se esses cenários não forem escritos pelo cliente e revistos por ele posteriormente, passar por todas as etapas necessárias para testar tal cenário com o Cucumber pode não ser vantajoso. Nestes casos, o testes devem voltar a ter seu papel inicial: ajudar o desenvolvedor. Não precisamos de mais uma DSL, já sabemos Ruby e isso já é o suficiente para que possamos ler nossos testes.

A lição é que devemos ser críticos e todos os dias analisarmos o que estamos fazendo. Será que essa é a melhor forma de fazer isso? Você pode perceber que não e quanto mais cedo isso acontecer, melhor.

agosto 2, 2010 / cassiomarques

How to test autocomplete text fields with Cucumber + Capybara + Selenium

A quick tip about how to test that cool jQuery autocomplete text box that you have in your app.

To test autocomplete text fields you will need to use some driver capable of running JavaScript. With Capybara, the mos common JavaScript drivers are Selenium (through Selenium Webdriver) and Culerity. I choose Selenium, since it works better for me. The idea is pretty simple:

  1. Type a few characters inside the field. This should trigger the autocomplete’s search
  2. Select the desired option
  3. Check the result

This can be easily accomplished with the following Cucumber feature:


When I fill in "Programmers" with "Dijkstra"
And I select the option containing "Edsger Dijkstra" in the autocomplete
And I press "Search"
Then I should see "He's watching."

The step for the autocomplete selection could be implemented like

When(/^I select the option containing "([^\"]*)" in the autocomplete$/) do |text|
  locate("li:contains('#{text}')").click
end

Here I took advantage of the HTML structure generated by the jQuery autocomplete plugin, which displays results in a list, where each item is a <li> element. Note that this solution may not work with other autocomplete implementations.

The above solution is very simplistic. Using other CSS selectors in the step implementation it would be possible to better filter the found elements.

With Capybara, all you have to do is put a @selenium tag right above the scenario in your .feature file, to force Capybara to use the Selenium driver to run it.

agosto 2, 2010 / cassiomarques

Como testar autocomplete com Cucumber + Capybara + Selenium

Uma dica rápida sobre como escrever testes funcionais para aquele autocomplete bacana que você colocou na sua aplicação.

Para testar campos de texto que possuam autocomplete você vai precisar usar algum driver que seja capaz de executar JavaScript. Com o Capybara, os dois mais comuns são o Selenium (através do Selenium Webdriver) e o Culerity. No meu caso o Selenium funcionou melhor. A idéia é simples:

  1. Digite alguns caracteres no campo, o que deve disparar a busca do autocomplete
  2. Selecione a opção desejada
  3. Verifique o resultado obtido

Isso pode facilmente ser obtido com a seguinte feature:


Quando eu preencho "Programadores" com "Dijkstra"
E seleciono a opção que contém "Edsger Dijkstra" no autocomplete
E pressiono "Pesquisar"
Então eu devo ver "Ele está observando você."

E o step para a seleção da opção do autocomplete pode ser implementado assim:

Quando(/^seleciono a opção que contém "([^\"]*)" no autocomplete$/) do |text|
  locate("li:contains('#{text}')").click
end

Aqui eu me aproveito da estrutura do HTML gerado pelo plugin de autocomplete do jQuery, que gera os resultados como uma lista, onde cada item está em um elemento <li>. Note que essa solução pode não funcionar com outros tipos de autocomplete.

A solução acima é bastante simplista, visto que ela poderia não funcionar caso houvesse mais de um autocomplete na mesma página. Usando outros seletores CSS na implementação do step é possível filtrar um pouco mais os elementos encontrados na página.

Com o Capybara, basta colocar a tag @selenium sobre o cenário em seu arquivo .feature para forçar que a execução ocorra usando este driver.

julho 12, 2010 / cassiomarques

Generic tests with Rspec

Suppose you have two classes that mix-in the same module. This will make both classes to have similar behaviour. If that’s the case, there is a great change that their tests are also very similar and, probably, there will be some code duplication. When writing tests, I do not always follow the DRY principles, since I believe tests have to be as clear and simple as possible and being too smart to eliminate duplication can make the code obfuscated. But in certain cases it’s worth to care with our code and clean up our tests.

I’ll use a very simple example to make things easier. Imagine we have two classes, Line e Text. Both represent graphical elements that have to be drawn in a cartesian plane. These elements must have a rotation angle relative to the horizontal axis. A possible implementation for the tests would be the following:

describe Line do
  describe "#rotation=" do
    it "sets the line's rotation" do
      line = Line.new
      line.rotation = 90
      line.instance_variable_get(:@rotation).should == 90
    end
  end

  describe "#rotation" do
    it "retrieves the line's rotation" do
      line = Line.new 
      line.rotation = 90
      line.rotation.should == 90
    end
  end
end

describe Text do
  describe "#rotation=" do
    it "sets the text's rotation" do
      text = Text.new
      text.rotation = 90
      text.instance_variable_get(:@rotation).should == 90
    end
  end

  describe "#rotation" do
    it "retrieves the text's rotation" do
      text = Text.new
      text.rotation = 90
      text.rotation.should == 90
    end
  end
end

We have a lot of duplication here. It’s possible to make things better extracting the objects initialization code to outside the it blocks:

describe Line do
  let(:line) { Line.new }

  describe "#rotation=" do
    it "sets the line's rotation" do
      line.rotation = 90
      line.instance_variable_get(:@rotation).should == 90
    end
  end

  describe "#rotation" do
    it "retrieves the line's rotation" do
      line.rotation = 90
      line.rotation.should == 90
    end
  end
end

describe Text do
  let(:text) { Text.new }

  describe "#rotation=" do
    it "sets the text's rotation" do
      text.rotation = 90
      text.instance_variable_get(:@rotation).should == 90
    end
  end

  describe "#rotation" do
    it "retrieves the text's rotation" do
      text.rotation = 90
      text.rotation.should == 90
    end
  end
end

It’s better, but we still have too much duplicated code. What we can do from here is to use Rspec’s shared_examples. A shared_example creates a specs group that can be shared by many other tests. It has to be used with the it_should_behave_like method:

shared_examples_for 'a graphic element' do
  describe "#rotation=" do
    it "sets the element's rotation" do
      element.rotation = 90
      element.instance_variable_get(:@rotation).should == 90
    end
  end

  describe "#rotation" do
    it "retrieves the element's rotation" do
      element.rotation = 90
      element.rotation.should == 90
    end
  end
end

describe Line do
  let(:element) { Line.new }

  it_should_behave_like 'a graphic element'
end

describe Text do
  let(:element) { Text.new }

  it_should_behave_like 'a graphic element'
end

The it_should_behave_like method will search for a shared specs group declared exactly with the same description, in our case “a graphic element”. What we did was extract everything that was common to both specs to an external piece of code. The shared group is executed in the same context as the describe blocks within which we used it_should_behave_like. This way, when we call element inside the tests, it uses the object created by the respective let method calls. However, we still have a point of duplication that can be improved without clarity. Note that in both describe blocks we use let in a very similar fashion to initialize the objects that will be tested. We can use the described_class method, which returns the class being tested:

shared_examples_for 'a graphic element' do
  let(:element) { described_class.new }

  describe "#rotation=" do
    it "sets the element's rotation" do
      element.rotation = 90
      element.instance_variable_get(:@rotation).should == 90
    end
  end

  describe "#rotation" do
    it "retrieves the element's rotation" do
      element.rotation = 90
      element.rotation.should == 90
    end
  end
end

describe Line do
  it_should_behave_like 'a graphic element'
end

describe Text do
  it_should_behave_like 'a graphic element'
end

This way the let method call will create an element of the right class for each spec.

julho 12, 2010 / cassiomarques

Testes genéricos com Rspec

Suponha que você tenha duas classes que incluam o mesmo módulo. Isso fará com que o comportamento dessas duas classes tenha pontos em comum. Se este for o caso, é grande a probabilidade de que seus testes também sejam bastante parecidos e, provavelmente, haverá duplicação entre eles. Em testes, nem sempre eu sigo a regra de ser DRY. Isso porque os testes devem ser o mais claros possível e tentar ser esperto em excesso para eliminar duplicação em tudo que é canto pode deixar o código obscuro. Mas em vários casos vale à pena ter um carinho a mais com o código dos testes.

Vou usar um exemplo bem simples para facilitar a compreensão. Imagine que temos duas classes, Line e Text. Ambas representam elementos gráficos que devem ser impressos em um plano de coordenadas cartesianas. Estes elementos devem possuir um ângulo de rotação em relação ao eixo horizontal. Uma possível implementação dos testes seria:

describe Line do
  describe "#rotation=" do
    it "sets the line's rotation" do
      line = Line.new
      line.rotation = 90
      line.instance_variable_get(:@rotation).should == 90
    end
  end

  describe "#rotation" do
    it "retrieves the line's rotation" do
      line = Line.new 
      line.rotation = 90
      line.rotation.should == 90
    end
  end
end

describe Text do
  describe "#rotation=" do
    it "sets the text's rotation" do
      text = Text.new
      text.rotation = 90
      text.instance_variable_get(:@rotation).should == 90
    end
  end

  describe "#rotation" do
    it "retrieves the text's rotation" do
      text = Text.new
      text.rotation = 90
      text.rotation.should == 90
    end
  end
end

A duplicação aqui é excessiva. É possível melhorar um pouco extraindo a inicialização dos objetos para fora dos blocos it

describe Line do
  let(:line) { Line.new }

  describe "#rotation=" do
    it "sets the line's rotation" do
      line.rotation = 90
      line.instance_variable_get(:@rotation).should == 90
    end
  end

  describe "#rotation" do
    it "retrieves the line's rotation" do
      line.rotation = 90
      line.rotation.should == 90
    end
  end
end

describe Text do
  let(:text) { Text.new }

  describe "#rotation=" do
    it "sets the text's rotation" do
      text.rotation = 90
      text.instance_variable_get(:@rotation).should == 90
    end
  end

  describe "#rotation" do
    it "retrieves the text's rotation" do
      text.rotation = 90
      text.rotation.should == 90
    end
  end
end

Ficou melhor, mas ainda está muito duplicado. O que podemos fazer a partir daqui é usar o recurso de shared_examples do Rspec. O que um shared_example faz é criar um conjunto de specs que pode ser compartilhado por vários testes. Ele deve ser usado em conjunto com o método it_should_behave_like, assim:

shared_examples_for 'a graphic element' do
  describe "#rotation=" do
    it "sets the element's rotation" do
      element.rotation = 90
      element.instance_variable_get(:@rotation).should == 90
    end
  end

  describe "#rotation" do
    it "retrieves the element's rotation" do
      element.rotation = 90
      element.rotation.should == 90
    end
  end
end

describe Line do
  let(:element) { Line.new }

  it_should_behave_like 'a graphic element'
end

describe Text do
  let(:element) { Text.new }

  it_should_behave_like 'a graphic element'
end

O it_should_behave_like vai procurar por um grupo de specs compartilhadas que tenha sido declarado usando exatamente o mesmo texto para descrição, no nosso caso “a graphic element”. O que fizemos foi extrair tudo que era código comum para um módulo externo. O grupo compartilhado é executado no mesmo contexto dos blocos describe dentro dos quais usamos o it_should_behave_like. Assim, quando dentro dos testes usamos element, é usado o objeto criado pelos respectivos métodos let. Porém, ainda temos um ponto de duplicação que pode ser melhorado sem perder clareza. Observe que nos dois blocos describe estamos usando o let de forma bastante semelhante para iniciar os objetos que serão testados. Podemos usar o método described_class, que retorna a classe sendo testada:

shared_examples_for 'a graphic element' do
  let(:element) { described_class.new }

  describe "#rotation=" do
    it "sets the element's rotation" do
      element.rotation = 90
      element.instance_variable_get(:@rotation).should == 90
    end
  end

  describe "#rotation" do
    it "retrieves the element's rotation" do
      element.rotation = 90
      element.rotation.should == 90
    end
  end
end

describe Line do
  it_should_behave_like 'a graphic element'
end

describe Text do
  it_should_behave_like 'a graphic element'
end

Dessa forma o let irá iniciar um elemento da classe correta para cada teste.

junho 6, 2010 / cassiomarques

Vídeo da minha palestra no Ruby + Rails no Mundo Real 2010

Vídeo da minha palestra sobre Refactoring + Design Patterns em Ruby, apresentada no evento Ruby + Rails no Mundo Real 2010, realizado pelo GURU-SP e pela Tempo Real.

Você pode ver os slides aqui.

Se você estiver lendo este post por algum leitor de feed, acesse aqui: http://agaelebe.blip.tv/file/3718567/

Obrigado a todos que estiveram por lá! E muito obrigado ao @agaelebe que fez as filmagens!

maio 30, 2010 / cassiomarques

Slides da minha palestra sobre Refatoração + Design Patterns no Ruby e Rails no Mundo Real 2010

Palestra feita no dia 29/05/2010 no evento Ruby e Rails no Mundo Real, realizado pelo Guru-SP e pela Tempo Real.

Obrigado a todos que estiveram por lá!

maio 16, 2010 / cassiomarques

Usando SQL puro para criar índices com o Thinking-Sphinx

Tenho usado bastante o Thinking-Sphinx (wrapper em Ruby para usar o ActiveRecord com o Sphinx) para implementar buscas. Esses dias enfrentei um problema diferente: precisava indexar um campo formatado mas permitir busca sem usar a formatação. Mais especificamente, precisava busca CPFs e CNPJs sem formatação, sendo que eles estavam gravados formatados no banco de dados. Exemplo: deveria ser possível encontrar o dono do CPF 111.444.777-35 buscando por 1114447735.

Por padrão, o Thinking-Sphinx vai indexar os valores exatos presentes na coluna em questão e associar o índice a este valor. Mas é possível contornar isso. A definição de indices pelo Thinking-Sphinx permite usar uma forma alternativa para passar os valores a serem indexados, geralmente usando uma query SQL nativa. No meu caso, usei os recursos de expressões regulares do PostgreSQL para remover tudo que não fosse número do meu CPF/CNPJ. A coluna usada no meu banco chama-se “entity_number”

define_index do 
  indexes "regexp_replace(entity_number, E'\[\[\:punct\:\]\]', '', 'g')", :as => :raw_entity_number
  group_by "raw_entity_number"
end

Existem duas “pegadinhas” aqui:

  • Os caracteres especiais da expressão regular precisam ser escapados para que o Sphinx monte a query corretamente. Se você vai ou não precisar fazer isso vai depender da query nativa sendo utilizada.
  • O Sphinx precisa sempre usar GROUP BY para cada campo indexado na query que ele envia ao banco para indexar os valores. Por padrão, campos criados como mostrado acima não são incluídos na cláusula GROUP BY e por isso sua indexação vai falhar. Por isso agrupamos explicitamente os resultados usando “group_by :raw_entity_number”. Nosso novo campo será concatenado ao final da query, sem bagunçar os demais valores já presentes na query.