Tratando Exceptions com RSpec

21 mai

Hoje me deparei com duas situações em que precisei “tratar” uma exception com RSpec, na primeira, precisei ignorá-la, pois meu “example” verificaria se não houve gravação do dado quando determinada condição ocorresse, na segunda, verificando se um erro específico ocorreria nesta mesma condição.

Testarei o método faz_algo da classe Contato, com o seguinte trecho:

@user = User.encontrar_ou_criar(params)
 raise "Este email já esta cadastrado como atendente." if @user.eh_atendente?

@contato = Contato.create({
 :cliente_id => params[:cliente_id],
 :user_id => @user.id,
 })

Criei um context agrupando as duas situações, na primeira usei um bloco apenas para que a exception não “estourasse”, verificando a situação após a execução do método:

lambda{ Contato.faz_algo(params) }
Contato.where(:user_id => @user.id).should == []

Desta forma, verifico se o Contato não foi gravado quando a situação @user.eh_atendente? for verdadeira.

Na segunda, verifico se houve uma exception com a mensagem “Este email já esta cadastrado como atendente.”:

lambda{ Contato.faz_algo(params) }.should raise_error('Este email já esta cadastrado como atendente.')

Observação: Não costumo usar mais de 1 verificação por teste (mais de 1 should por it), prefiro deixar o erro o mais isolado possível, aconselho que façam o mesmo.

Auto Gerenciamento para equipes imaturas

21 mai

Atualmente, um dos assuntos que mais me atraem à discussão é o auto gerenciamento, talvez por vivenciar essa experiência que ainda gera torcidas de nariz quando é apresentada. Sobre este assunto, vejo muitas falácias e generalizações de profissionais que tem medo de perder seus postos|cargos|salários por não serem bons “técnicos”, uma delas é que o “Auto gerenciamento só funciona com equipes maduras”.

Bem, inicialmente leia o post do Tales sobre auto organização.

Agora que conceitos não são mais necessários, partamos para a maturidade necessária para termos equipes auto geridas.

De início vejo uma bagunça presente no termo “maturidade”, este estaria relacionado à experiência ou à responsabilidade? Se estamos tratando de responsabilidade, a frase deveria ser “Auto gerenciamento não funciona com profissionais irresponsáveis”, daí surge uma nova pergunta, o Gerenciamento “tradicional” funciona com profissionais irresponsáveis? Bem, se sua resposta é sim, aconselho-o a buscar números de projetos de software, ou não, cancelados e atrasados.

Pensando no termo maturidade relacionado à experiência, a discussão pode ser mais produtiva.

É interessante lembrar que líderes existem, o que não é diferente em equipes auto geridas, o importante é que esse líder não seja imposto, nem tenha poder “de chefe” sobre os demais, este deve ser respeitado por seu conhecimento e postura profissional, isso fará com que os “colegas” o tenham como líder.

Equipes com menos experiência tendem a tomar decisões piores, óbvio, eles tem menos experiência, isso pode ocorrer com a tecnologia escolhida ou simplesmente com um algoritmo, com a arquitetura de um sistema ou de um componente, em algum momento a inexperiência irá afetar seu resultado, mas lembrem-se de tantas vezes que pensou em fazer um “sisteminha” pra resolver um problema de casa e sua esposa chegou uma planilha de excell e resolveu o caso. Experiência não é garantia de bons resultados.

Acredito que termos “líderes” em equipes auto geridas é quase necessário, assim como termos “calmos” e “empolgados”, pessoas que “sabem” falar em público, skills diversificadas que suprem a necessidade de um gerente “mega blaster” que <ironic>é incrivelmente maduro e experiente</ironic>.

Penso que equipes com profissionais que reunem uma maior quantidade de qualificações, tem uma maior probabilidade de sucesso em adotar essa estratégia, e obviamente, RESPONSABILIDADE com a equipe|empresa|projeto é NECESSÁRIO.

O mesmo profissional que suja o banheiro porque esta #chatiado com uma não promoção, sujará seu código e fará testes que “só passem”.

O assunto tem muito a evoluir, minha proposta é uma reflexão sobre a necessidade dessa experiência, será que buscar profissionais que “assumam a bronca” e sejam “responsáveis” é menos rentável que criar desculpas para cargos “desnecessários” e onerosos?

Menos Caciques, Mais Índios

12 nov

O termo “Engenharia de Software” já nasceu carregado de deficiências, níveis hierárquicos e burocracia necessária em outras áreas de atuação, mas por pior que tenha sido seu nascimento, nada se compara a adolescência.
Bem, como toda criança que cresce vendo seus pais cometendo desvios de conduta, uma hora mostrará sintomas desta exposição, a Engenharia de Software não foi diferente, nasceu comparando Software a uma Casa (Prédio, Barragem, etc) e criou Cargos e Processos similares, esquecendo que Alto Acoplamento é algo OBRIGATÓRIO numa Construção e algo a ser EVITADO no Desenvolvimento de Software, permitindo assim Refazer partes do software ao invés de derrubar uma parede e começar tudo de novo.
Bem, como em toda obra, não pode faltar um Gerente, senhor supremo do poder e conhecimento, aquele que dá a última (na maioria das vezes, até a primeira) palavra sobre qualquer assunto, pois é, essa é mais uma herança que este adolescente “ganhou”.
Em conversas com esses Engenheiros de Software já ouvi muita coisa interessante como “sem um processo definido não funciona”, “alguem precisa dar a última palavra” ou mesmo “a mesma pessoa que desenvolve o software não pode testar”, cada uma delas merece um post bem recheado de verdades, mas não será esse, aliás, talvez um pouco do segundo.
Quem não conhece a história do Excelente programador que tornou-se um Péssimo analista? Ou pior, o Gerente que NUNCA foi programador? Como alguem que nunca programou pode dizer em quanto tempo uma funcionalidade será desenvolvida? Esta última é boa, talvez você tenha pensado em algo como a famigerada “Estimativa por Pontos de Função”, perfeito, se formos usar o Word e descrever a funcionalidade, desenvolver software é mais um processo criativo, como pintar ou esculpir algo, não um processo braçal como empilhar pedras e por uma quantidade de cimento entre eles, uma estimativa depende das pessoas da equipe, do nível de entrosamento entre eles e diversos outros fatores. O gerente que estima sem perguntar à equipe é o mesmo que chama-os de recurso e os trata assim. E o gerente se “protege” dizendo que os desenvolvedores “concordaram” com a estimativa, essa é melhor, quem vai discordar do Gerente?

Auto-gestão não pode ser vista como um bando de programadores imaturos tomando as decisões da diretoria, chefes existem e devem existir, contudo devem atuar onde sua expertise fará diferença. Então como saber se a equipe esta trabalhando corretamente? Como saber se eles estão cumprindo o prometido? Essa é fácil, faça um acompanhamento de perto. Reuniões periódicas onde é mostrado o que foi feito, métricas de qualidade onde são apresentados os quantitativos de bugs, de suporte gerado, etc. O que é mais importante para a organização, sua equipe entregar software de qualidade e num bom tempo, ou, pontualmente, chegar as 8h e sair as 18h? Que seu cliente esteja satisfeito com o software e se sinta parte da equipe ou ter um contrato para lhe apoiar numa “possível” futura briga judicial? Óbvio que o cliente não se sentirá parte da equipe simplesmente isolando o gestor, isso é apenas um princípio ágil que segue a ideia.
A equipe estará satisfeita, pois dificilmente uma equipe insatisfeita proverá código de qualidade, o salário do gerente pode ser usado para manter os Desenvolvedores acima da média que você perderia ou transformaria em analistas, tornando-os profissionais medíocres, pois eles eram bons em desenvolver.
Esses programadores mais experientes se tornam líderes naturalmente, conduzindo os mais jovens, ajudando nos impaces e desta forma falando com conhecimento de causa, pois se ele é um bom programador pode FACILMENTE provar que aquele relatório levará 3hs e não 8hs.

Concordo com quem diz que Auto-gestão só funciona com maturidade, o problema é que quem o diz, não esta minimamente preocupado em amadurecer a equipe. A exposição aos problemas, as tomadas de decisões, são coisas que farão sua equipe melhorar.

Até a próxima.

[RoR] Autorização, faça você mesmo.

13 jul

Inicialmente é importante diferenciar Autorização e Autenticação.
A autenticação é o processo que verifica a identidade de uma pessoa, por sua vez, a autorização verifica se esta pessoa possui permissão para executar determinadas operações. Por este motivo, a autenticação sempre precede a autorização. (retirado da Wikipédia)

Vamos em frente, para o processo de autenticação usaremos o Devise por ser simples e fácil de implementar, além de ser a Gem mais popular para tal tarefa. Existem algumas alternativas para Autorização, como o CanCan, a questão é que queria algo mais “simples”.

Bem, gostaria de deixar toda a tarefa de mapear Grupos de Acesso(roles) e Funcionalidades(features) no Banco de Dados, cabendo a mim somente verificar se este acesso é permitido.

Então fiz o seguinte:

O model User, criado pelo Devise ganha o relacionamento com roles e os métodos can? e cannot?:

class User < ActiveRecord::Base
  has_many :users_roles, :class_name => "UsersRoles"
  has_many :roles, :through => :users_roles
  
...
  
  def can? controller, action
    admin? || allowed_actions.include?([controller, action])
  end
  
  def cannot? controller, action
    !can? controller, action
  end
  
  def allowed_actions
    roles.includes(:features).map{|x| x.features}.flatten.map{|x| [x.controller, x.action]}
  end
end

ps. Uso o “admin” como o usuário que terá acesso a TUDO, ele não será uma Role.

Criei as entidades Role e Feature:

class Role < ActiveRecord::Base  
  attr_accessible :name
  
  has_many :users_roles, :class_name => "UsersRoles"
  has_many :users, :through => :users_roles

  has_many :roles_features, :class_name => "RolesFeatures"
  has_many :features, :through => :roles_features
end

class Feature < ActiveRecord::Base
  attr_accessible :action, :controller, :description
  
  has_many :roles_features, :class_name => "RolesFeatures"
  has_many :roles, :through => :roles_features
end

E seus Relacionamentos:

class RolesFeatures < ActiveRecord::Base
  belongs_to :role
  belongs_to :feature
end

class UsersRoles < ActiveRecord::Base
  belongs_to :user
  belongs_to :role
end

Por fim, suas migrations:

create_table "roles", :force => true do |t|
  t.string   "name"
end

create_table "features", :force => true do |t|
  t.string   "controller"
  t.string   "action"
  t.string   "description"
end

create_table "roles_features", :force => true do |t|
  t.integer  "role_id"
  t.integer  "feature_id"
end

create_table "users_roles", :force => true do |t|
  t.integer  "user_id"
  t.integer  "role_id"
end

Com a modelagem de dados montada, fica somente a tarefa de verificar a cada ação. No ApplicationController teremos:

class ApplicationController < ActionController::Base
  protect_from_forgery
  before_filter :authenticate_user!, :authorize_user!
  skip_filter :authorize_user!, :if => lambda {|controller| !user_signed_in?}

  def authorize_user!
    if !is_a?(DeviseController) && current_user.cannot?(self.controller_name, self.action_name)
      render :text => 'Sem acesso'
    end
  end
end

Onde :authenticate_user! é um método do devise.

ps. Se você quiser criar uma Gem que automatize esta tarefa, nem precisa pedir autorização, me mande o link somente para que eu possa contribuir. :)

Como testar o ApplicationController? (Anonymous Controller)

13 jul

Comumente usamos o ApplicationController para criar comportamentos genéricos para todos os controllers da aplicação, comportamento este que varia desde métodos de autenticação, autorização, auditoria, etc. O problema é: Como testa-lo? Já que o ApplicationController não tem rota.

Podemos testar através de outro controller, simples assim, um dos motivos que incomoda nesta abordagem é que este controller pode deixar de existir e teus testes vão “pro saco”.

Como insinuado no título, a solução é o Anonymous Controller , vamos ao exemplo:

No ApplicationController tenho:

class ApplicationController < ActionController::Base
...
  before_filter :only => :destroy do |controller|
    Rails.cache.delete current_user.key_cache if controller.is_a? Devise::SessionsController
  end
...
end

Configuro no spec_helper.rb:

RSpec.configure do |config|
...
  config.infer_base_class_for_anonymous_controllers = true
...
end

E no spec do ApplicationController:

require 'spec_helper'

describe ApplicationController do
  # Crio os métodos do meu Controller Anônimo
  # As rotas REST serão criadas automaticamente
  controller do
    def destroy
      render :text => "destroy called"
    end  
  end
  
  # Implemento meus exemplos normalmente
  describe "DELETE 'destroy'" do
    context "Quando o controller for Devise::SessionsController" do
      it "Deve apagar a session do usuário" do
          controller.stub(:is_a?).and_return(true)
          Rails.cache.should_receive(:delete)
          delete :destroy, :id => 1
      end
    end
  
    context "Quando o controller for Devise::SessionsController" do
      it "Não deve apagar a session do usuário" do
          controller.stub(:is_a?).and_return(false)
          Rails.cache.should_receive(:delete).never
          delete :destroy, :id => 1
      end
    end    
  end

end

Esta solução não serve para métodos “novos”, além dos métodos padrões, por motivos óbvios (já que estamos testando comportamento padrão).

O controller criado extende ApplicationController, caso queira que o controller seja extendido de outro controller pode fazer da seguinte forma:

controller(BaseController) do
  def destroy
    render :text => "destroy called"
  end  
end

[RSpec L08] Testes de Views

27 jun

De todos os testes unitários, talvez o mais “negligenciado” seja o teste de view, o que se torna justo por analisar o Custo X Benefício.

Quando quero testar minhas views, prefiro partir pro Teste de Integração, que é um pouco mais caro, mas o benefício é muito maior. Testar a view ganha maior importância quando temos regras nela, o que é desaconselhável e facilmente substituível por Helpers.

De qualquer forma mostremos como funcionam.

Temos uma listagem de Sorteios e queremos saber se haverá o título “Sorteio”:

describe "sorteios/index" do
  it "mostrar titulo Sorteios" do
    assign :sorteios, []
    render
    rendered.should have_content("Sorteios")
  end
end

No describe é informada a página que será testada, no caso “sorteios/index”.
O assign simula a variável de instância do Controller, então é como se tivessemos atribuído:

@sorteios = []

O render é o método responsável pela renderização do conteúdo, é o “execute” do teste.
E o objeto renderer guarda todo o conteúdo renderizado.

Adicionaremos mais um regra, vou inserir um objeto e quero verificar se seu nome esta na célula de uma tabela HTML:

it "mostrar listagem de sorteios" do
  assign :sorteios, [ FactoryGirl.build(:sorteio, :id => '1', :nome => 'mega sena') ]
  render
  rendered.should have_selector("tr > td") do |td|
    td.should have_content('mega sena')
  end
end

O default do RSpec(até a versão que testei) utiliza o webrat, que esta meio ultrapassado, aconselho fortemente o uso do Capybara. Inclusive usaremos-no nos Testes de Integração.

Até a próxima.

[RSpec L07] Testes de Rotas

26 jun

Testando o Controller, podemos adicionar o teste de rotas no mesmo escopo, porém considero que perdemos um dos maiores motivos de fazer Testes Unitários, a localização rápida de erros.

Pensando assim, e vendo como testes de rotas são “baratos”, acho que fazê-los é tambem uma boa prática. Embora se usar a aplicação Rails para integrar-se com outra tecnologia, e esta integração seja feita atraves das rotas, o teste das mesmas torna-se vital.

Os matchers que precisamos, na grande maioria dos casos, conhecer são somente:
route_to e be_routable

Imagine este arquivo de rotas:

resources :sorteios do
  member do
    get :sortear
  end
  resources :concorrentes, :only => [:new, :create, :destroy]
end

Começemos com um teste para o index de :sorteios:

it "routes to #index" do
  get("/sorteios").should route_to("sorteios#index")
end

Se a rota é do tipo :member, precisamos informar a propriedade de identificação do objeto:

it "routes to #sortear" do
  get("/sorteios/1/sortear").should route_to("sorteios#sortear", :id => "1")
end

Deveremos testar tambem as rotas nested, isso acontece da mesma forma. Vejamos um teste para o destroy do :concorrente :

it "routes to #destroy" do
  delete("/sorteios/1/concorrentes/2").should route_to("concorrentes#destroy", :sorteio_id => "1", :id => "2")
end

Observe que em nosso mapeamento de :concorrente, teremos somente as rotas :new, :create e :destroy (informadas no :only).
Então é interessante garantir que um “desavisado” não irá remover esta clausula, deixando disponível as outras rotas padrões (REST).
Testaremos então se as outras rotas são roteáveis:

it "routes to #index" do
  get("/sorteios/1/concorrentes").should_not be_routable
end

Até a próxima.

Seguir

Obtenha todo post novo entregue na sua caixa de entrada.