Arquivo

Textos com Etiquetas ‘RSpec’

Porque não gosto de usar “should” nos testes de RSpec

23 de outubro de 2011

Desde que li o post RSpec Best Practices de Jared Carroll (post o qual David Chelimsky prefere citar como Good Guidelines) eu prefiro não mais utilizar o termo should para todos os exemplos (testes) de RSpec.

Primeiro que concordo com Jared sobre a redundância da palavra should e como o resultado dos testes ficam mais claros quando rodamos no formato de documentação.

Abaixo dois exemplos (extraídos do post de Jared).

O primeiro usa should:

$ rspec spec/controllers/posts_controller_spec.rb --format documentation

PostsController
  #new
    when not logged in
      should redirect to the sign in page
      should display a message to sign in

Agora eliminando o should e usando o verbo na terceira pessoa:

$ rspec spec/controllers/posts_controller_spec.rb --format documentation

PostsController
  #new
    when not logged in
      redirects to the sign in page
      displays a message to sign in

A segunda razão é a questão gramatical e do sentido dos testes.

O verbo modal should, entre outros significados, pode ser considerado uma obrigação, mas é uma obrigação gentil, cuidadosa, sem muita firmeza.

When not logged in, should redirect to the sign in page.
Quando não logado, deveria redirecionar para a página de login.

Deveria ou deve? Para o teste passar tem que redirecionar para a página de login. Se não redirecionar, o teste falhará. Nessa caso, gramaticalmente, não é melhor usar o verbo modal must, que expressa uma obrigação impreterível?

Então, para ficar simples, uso o verbo na terceira pessoa, evito verbos modais repetitivos e deixo explícito o que o teste está assegurando.

When not logged in, redirects to the sign in page.
Quando não logado, redireciona para a tela de login.

Ruby , ,

2010 em livros

5 de janeiro de 2011

Estou a “plagiando” uma idéia de Phillip Calçado que fez um post em seu blog mostrando os livros lidos no ano de 2010, com um pequeno comentário sobre cada um.

Com certeza minha lista é muito mais modesta e curta, mas a idéia de compartilhar conhecimento é a mesma.

Esse post também serve para me alertar a ler mais, mesmo com toda a correria de trabalho, família, estudo e prática de esportes, gostaria de ter lido mais que 7 livros durante 2010.

Os livros são apresentados na ordem que foram lidos:

Clean Code: A Handbook of Agile Software Craftsmanship

Clean Code: A Handbook of Agile Software Craftsmanship

Robert C. Martin
Prentice Hall
August 2008

Muito bom

Todo programador deveria ler. Uncle Bob nos concede um pouco de sua vasta experiência em desenvolvimento de software, mostrando como deixar seu código mais enxuto, legível e simples. Esse livro serviu de inspiração para várias palestras e apresentações por aí sobre como escrever código de melhor qualidade.

The RSpec Book: Behaviour-Driven Development with RSpec, Cucumber, and Friends

The RSpec Book: Behaviour-Driven Development with RSpec, Cucumber, and Friends

David Chelimsky, Dave Astels, Zach Dennis, Aslak Hellesøy, Bryan Helmkamp, Dan North
The Pragmatic Programmers
December 2010

Ótimo

Essencial para quem quer aprender RSpec. Explica como seria o ciclo ideal de BDD. Também traz exemplos práticos de utilização de RSpec e Cucumber com Ruby e Ruby on Rails.

JavaScript: The Good Parts

JavaScript: The Good Parts

Douglas Crockford
O’Reilly
May 2008

Muito bom

Quem curte JavaScript irá gostar bastante. Fiz anotações sobre esse livro nesse post.

Design Patterns in Ruby

Design Patterns in Ruby

Russ Olsen
Addison-Wesley Professional
December 2007

Bom

Traz uma boa parte dos design patterns apresentados pela GoF utilizando Ruby. O que eu achei mais interessante foi a abordagem utilizada pelo autor: dado um design pattern, o mesmo é implementado de uma maneira clássica e depois reimplementado usando os recursos que o Ruby oferece, como por exemplo, metaprogramação, tornando alguns patterns totalmente diferentes de sua implementação original.

Professional ASP.NET MVC 2

Professional ASP.NET MVC 2

Jon Galloway, Scott Hanselman, Phil Haack, Scott Guthrie, Rob Conery
Wrox
June 2010

Muito bom

Sempre gostei da séria Professional da Wrox e esse livro mantém a linha de qualidade. Logo no primeiro capítulo tem um passo a passo da construção de uma pequena, mas completa, aplicação ASP.NET MVC 2. Os demais capítulos cobrem os demais recursos do framework. Só pelos autores já vale a leitura.

ASP.NET MVC 2 in Action

ASP.NET MVC 2 in Action

Jeffrey Palermo, Ben Scheirman, Jimmy Bogard, Eric Hexter, Matthew Hinze
Manning
June 2010

Regular

Talvez se eu não tivesse lido o livro da Wrox antes teria classificado melhor este. Em algumas vezes os autores focam demais no uso de ferramentas extras, auxiliares ao desenvolvimento de aplicações ASP.NET MVC 2. Isso é bom para quando você já tem conhecimento e/ou vivência do framework.

Test-Drive ASP.NET MVC

Test-Drive ASP.NET MVC

Jonathan McCracken
The Pragmatic Programmers
June 2010

Regular

Eu esperava bem mais desse livro por se tratar de TDD. Havia imaginado vários passos práticos de desenvolvimento orientado a testes no melhor estilo Kent Beck ou Uncle Bob. Para quem já conhece TDD, não irá ver grandes novidades.

Eu uso o Shelfari como prateleira virtual para organizar os livros que li, estou lendo e pretendo ler.
É bem legal e tem vários recursos. Por exemplo, esse endereço lista os livros que li no ano de 2010.

.NET, Arquitetura, JavaScript, Livros, Ruby, TDD , , , , , , , , , , , ,

Como testar gravação de log em Ruby on Rails

17 de novembro de 2010

Em uma aplicação Ruby on Rails, quando você precisa assegurar nos seus testes que a gravação de logs está sendo realizada com sucesso, não há a necessidade de abrir o arquivo físico de log, ler seu conteúdo e verificar se a informação que você deseja está lá.

Ao invés disso, você pode substituir a saída de escrita padrão do log por outra classe de stream, como a StringIO, onde você terá fácil acesso ao que for gravado no log durante o teste.

Log em inglês também é tronco, lenha.

Log em inglês também é tronco, lenha.

Vamos ver um exemplo utilizando RSpec para testar a gravação de log de um método chamado do_something em uma classe modelo SomeModel.

require 'spec_helper'

describe SomeModel do
  before(:each) do
    @some_model = SomeModel.new

    @log_stream = StringIO.new
    @some_model.stub(:logger).and_return(Logger.new(@log_stream))
  end

  describe "#do_something" do
    it "logs info" do
      @some_model.do_something
      @log_stream.string.should include("I am logging something")
    end
  end
end

Na linha 7, criamos uma instância de StringIO atribuindo à variável @log_stream. É nesse objeto que serão gravados todos os logs do nosso teste. Para isso, na linha 8, substituímos o logger original por um stub de Logger que faz referência à instância da classe StringIO que criamos.

Já na linha 14, a asserção do nosso teste verifica se o texto correto foi gravado no stream do log. O método StringIO#string retorna a string bruta, ou seja, o texto que foi gravado no stream.

A seguir está a implementação do método do_something:

class SomeModel < ActiveRecord::Base
  def do_something
    # some implementation

    logger.info "I am logging something"
  end
end

Para fazer testes de gravação de logs em controllers, a idéia é a mesma, basta apenas usar o stub no método logger do controller (linha 6):

require 'spec_helper'

describe SomeModelsController do
  before(:each) do
    @log_stream = StringIO.new
    controller.stub(:logger).and_return(Logger.new(@log_stream))
  end

  describe "GET index" do
    it "logs info" do
      get :index
      @log_stream.string.should include("Now logging in a controller")
    end
  end
end
class SomeModelsController < ApplicationController
  def index
    # implementation

    logger.info "Now logging in a controller"
  end
end

Se você precisa usar log em uma classe que não seja um modelo ou controller, não vai poder chamar o método logger diretamente. Por exemplo, a classe a seguir, foi criada dentro da pasta lib de uma aplicação Rails.

class CoolClass
  def cool_method
    logger.info "Cool log!"
  end
end

Chamando o método cool_method teremos o seguinte erro:
> c = CoolClass.new
> c.cool_method
NameError: undefined local variable or method `logger’ for #<CoolClass:0×1e81bf0>

Resolver isso é simples, basta utilizar Rails.logger:

class CoolClass
  def cool_method
    Rails.logger.info "Cool log!"
  end
end

E no teste para gravação de log o stub é feito na classe Rails (linha 8):

require 'spec_helper'

describe CoolClass do
  before(:each) do
    @cool_class = CoolClass.new

    @log_stream = StringIO.new
    Rails.stub(:logger).and_return(Logger.new(@log_stream))
  end

  describe "#cool_method" do
    it "logs info" do
      @cool_class.cool_method
      @log_stream.string.should include("Cool log!")
    end
  end
end

Ruby, TDD , , , , ,

Não escreva código novo sem antes ter um teste falhando

16 de fevereiro de 2010

O título desse post é uma frase de Kent Beck, autor do livro Test Driven Development: By Example. A idéia é que você sempre escreva testes antes de implementar qualquer código. Após o teste escrito falhar, você implementa o suficiente para fazer o teste passar. Com os testes passando, você está livre para refatorar (tanto implementação, quanto teste). A partir daí você cria um novo teste e segue o mesmo fluxo. Esse ciclo se repete até você ter toda a funcionalidade deseja implementada, ou seja, ter testes para todas as possibilidades da sua implementação.

Este é o “bê-a-bá” de TDD, mas na prática isso dificilmente acontece. Não porque não queremos fazer testes (se você não quiser escrever testes, o problema é todo seu), mas porque somos exímios programadores, desenvolvemos orientados a testes por anos, e não precisamos mais seguir os baby steps (passos de bebê), afinal somos programadores maduros.

Sendo assim, pulamos etapas: codificamos primeiro para depois escrever os testes, refatoramos mesmo com testes ainda não passando, escrevemos mais testes mesmo tendo testes anteriores falhando, e por aí vai.

Cuidado! Por mais que você seja um programador “fodão”, ainda sim você pode deixar de testar alguma coisa. Uma lógica de negócio, uma alternativa de fluxo ou uma condição de erro podem passar desapercebidas ao se pular as etapas básicas de TDD. Esse teste faltando, por mais simples que seja, pode causar um erro em ambiente de produção e causar transtornos para o cliente e/ou usuário final da sua aplicação.

Vamos utilizar como exemplo uma simulação de pareamento, onde uma dupla de desenvolvedores irá criar um método chamado positive_balance? para dizer se uma conta bancária, representada pela classe BankAccount, possui saldo positivo.

A linguagem utilizada será Ruby e o framework para testes será RSpec.

Os programadores são Félix (piloto do pareamento) e Péricles. Os dois concordam em iniciar criando a classe BankAccount com a declaração do método positive_balance? sem nenhuma implementação:

class BankAccount
  def positive_balance?

  end
end

- Legal, agora vamos escrever nosso teste. - diz Péricles.
- Para uma conta bancária possuir fundos é nessário que seu saldo seja maior que zero.

describe BankAccount do
  it "should have positive balance" do
    account = BankAccount.new
    account.value = 100.00
    account.positive_balance?.should be_true
  end
end

Eles rodam o teste:

F

1)
NoMethodError in 'BankAccount should have positive balance'
undefined method `value=' for #
./spec/bank_account_spec.rb:6:

Finished in 0.010015 seconds

1 example, 1 failure

E o resultado com erro diz a eles que não existe um atributo value na classe BankAccount. Félix e Péricles o criam:

class BankAccount
  attr_accessor :value

  def positive_balance?

  end
end

E executam o teste novamente:

F

1)
'BankAccount should have positive balance' FAILED
expected nil to be true
./spec/bank_account_spec.rb:7:

Finished in 0.010605 seconds

1 example, 1 failure

O teste falha. Então chegou a hora de escrever código novo, a implementação da funcionalidade que eles querem. Félix implementa o suficiente para o teste passar.

class BankAccount
  attr_accessor :value

  def positive_balance?
    true
  end
end

Péricles discorda totalmente.
- Cê tá louco, mano?! Vai retornar true para tudo?! O cara vai ter sempre saldo na conta?

Félix argumenta.
- A gente não precisa escrever código suficiente para o teste passar? Isso é suficiente.

E roda o teste:

.

Finished in 0.009987 seconds

1 example, 0 failures

- Viu? Passou. - finaliza Félix.
- Mas isso é muito baby step. - reclama Péricles - Vamos implementar o código real, ou seja:

class BankAccount
  attr_accessor :value

  def positive_balance?
    self.value > 0
  end
end

- Mas por que vamos implementar isso agora? Afinal nossos testes estão passando. - Félix rebate.
- Porque está na cara que esse código retornando true sempre não funciona.

Félix continua forçando a discussão.
- Como não funciona? Funciona sim, os testes estão passando.
- Funciona, mas a implementação está errada. - diz Péricles.
- Errada? Mas atende os requisitos até o momento. Afinal, os testes são para assegurar que a lógica do negócio está sendo cumprida.

Péricles fica pensativo.
- Mas o único teste que fizemos não está cobrindo todos os casos da lógica.
- Concordo com você, Péricles. E o que devemos fazer agora então?
- Devemos escrever um teste em que a conta bancária não irá ter fundos.
- Exatamente! - confirma Félix.

E eles continuam nesse linha de raciocínio até o final do pareamento.

Não estou aqui dizendo que você tem que sempre seguir à risca o Red Green Refactor do TDD, muito menos usar baby steps toda vez que você codificar (afinal, a vida não é um dojo), mas que você tenha atenção e controle do que está fazendo, tento o domínio da funcionalidade que está implementando.

Uma das maneiras de se conseguir isso é com pareamento. Seu par irá lhe ajudar a não deixar escapar nenhum teste. Outra maneira é com inspeção de código. De repente, outro desenvolvedor que não participou da implementação pode enxergar algo que você (e/ou seu par) não viu.

De qualquer forma, seja humilde. Use as etapas de TDD para funcionalidades ou lógica mais complexas. E também fique livre para burlar as regras para implementar coisas simples e funcionalidades básicas, ou quando estiver bastante à vontade e seguro do que está fazendo. Mas nunca, eu disse nunca, deixe de escrever os testes.

TDD , , ,

Cheeseburgers, Decorators e Ruby

13 de setembro de 2009

No post Cheeseburgers, Decorators e Mocks eu mostrei um exemplo prático de utilização do Design Pattern Decorator, que começa com um design usando herança, desaclopa usando composição e finalmente aplica Decorator. Tudo isso foi feito em .NET com C#. Agora vamos fazer o mesmo exemplo de Decorator Pattern utilizando Ruby. Para entender melhor o contexto do exemplo utilizado, sugiro que você leia antes o post anterior.

Imagem original de MarketFare Foods, Inc.

Imagem original de MarketFare Foods, Inc.

Além de Ruby, utilizarei o RSpec como ferramenta de testes unitários. No final do post há os links para baixar o código completo.

Leia mais…

Arquitetura, Ruby , , , , , , , ,