Colorindo o output dos testes no Rails

13/06/07

Output colorido com RedGreen Se você anda escrevendo muitos testes mas precisa se esforçar um pouco para ler a saída gerada por eles, experimente a gem RedGreen, criada por Pat Eyler. Ela não faz nada além de colorir com a cor verde os testes que passaram e com a cor vermelha os testes que falharam. Simples assim, mas muito útil!

Para instalá-lo, execute o comando sudo gem install redgreen e adicione o código require 'redgreen' no fim do arquivo "environment.rb".

Como migrar suas Stored Procedures no ambiente de teste

11/06/07

Normalmente, evito utilizar Stored Procedures e Functions até onde posso, mas no Spesa, não foi possível, devido uma quantidade de consultas "avançadas" que são feitas. Se não as utilizasse, a repetição de código seria absurdamente grande e, por conseguinte, seria impossível de se fazer eventuais modificações. Como estou refatorando o código do Spesa e escrevendo testes para cada item do sistema, caí em um problema um pouco incômodo na hora da migração.

Não sei se você sabe, mas toda vez que você quer sincronizar o seu banco de testes com o de desenvolvimento, basta executar o comando rake db:test:prepare. O grande problema deste comando é que ele não leva em consideração os arquivos de migração, mas sim o arquivo schema.rb, que não adiciona queries executadas através do método execute. Para corrigir isto, basta executar o comando rake db:migrate RAILS_ENV="test".

Update: depois de fazer alguns testes, vi que as stored procedures continuavam a serem excluídas. O único jeito foi estender o ActiveRecord para ele fazer o dump das funções e salvá-las no arquivo schema.rb. Basta adicionar o código abaixo no arquivo environment.rb.

module ActiveRecord
  class SchemaDumper
    def dump(stream)
      header(stream)
      tables(stream)
      functions(stream)
      trailer(stream)
      stream
    end
 
    private
      def functions(stream)
        result = @connection.execute <<QUERY
        SELECT
          ROUTINE_NAME AS name
        FROM 
          INFORMATION_SCHEMA.ROUTINES 
        WHERE 
          ROUTINE_TYPE='FUNCTION' AND 
          ROUTINE_SCHEMA='#{@connection.current_database}'
QUERY
        result.each do |row|
          stream = function(row[0], stream)
        end
      end
 
      def function(name, stream)
        result = @connection.execute "SHOW CREATE FUNCTION #{name}"
        fields = result.fetch_hash
        func = fields['Create Function'].gsub(/CREATE DEFINER.*? FUNCTION/, "CREATE FUNCTION")
 
        stream.puts <<FUNCTION
execute <<QUERY
  #{func}
QUERY
 
FUNCTION
        stream
      end
  end
end

Use o método save! ao escrever seus testes

03/06/07

Toda vez que você estiver escrevendo um teste — seja ele qual for — e tiver que chamar o método save, opte pelo método save!. A diferença entre eles é que o primeiro retorna true ou false em caso de sucesso ou falha, respectivamente. Já o segundo, irá disparar a exceção ActiveRecord::RecordInvalid, caso o modelo seja inválido em relação às suas regras de validação.

Aplicado aos testes, a vantagem do método save! está na dependência de alguma alteração do modelo para testar uma outra situação. Imagine o seguinte modelo:

class User < ActiveRecord::Base
  validates_length_of :password,
    :minimum => 4,
    :message => 'A senha deve ter no mínimo 4 caracteres'
 
  attr_accessor :password
end

Já o teste irá validar se um usuário pode se logar com um cookie expirado.

def test_should_fail_expired_cookie_login
  user = users(:quentin)
  user.remember_me
  user.remember_token_expires_at = 5.minutes.ago
  user.save
 
  @request.cookies["auth_token"] = cookie_for(:quentin)
  get :index
  assert !@controller.send(:logged_in?)
end

Ao executá-lo, ele falharia porque o atributo password não foi definido e o método validates_length_of invalidaria nosso modelo, só que você não iria ter nenhuma indicação de que esse foi o motivo. Neste caso, se você trocar a linha user.save por user.save!, uma exceção seria disparada, indicando qual validação não passou. No modelo você poderia utilizar o seguinte código para validar o modelo:

class User < ActiveRecord::Base
  validates_length_of :password,
    :minimum => 4,
    :message => 'A senha deve ter no mínimo 4 caracteres',
    :if => :require_password?
 
  attr_accessor :password
 
  private
    def require_password?
      !password.blank?
    end
end