BDD com Shoulda: Menos código, testes mais legíveis
Para quem ainda não conhece, BDD (Behavior Driven Development) é uma prática de processos ágeis que vem mudando a maneira como os desenvolvedores escrevem testes automatizados para seus sistemas. Testes escritos sobre este conceito são mais fáceis de entender e podem ser utilizados como ferramenta de modelagem de um sistema, já que eles ajudam pessoas com pouco conhecimento técnico à entender funcionamento do software. Além disso, testes simples de entender também são simples de manter.
Essas vantagens me fizeram pensar na idéia de migrar os testes do meu projeto Rails, que tinha seus testes unitários escritos sobre o framework Test/Unit, o padrão utilizados pelo Rails para TDD (Test Driven Development). Para realizar a mudança era necessário aprender a utilizar outro framework, no caso o RSpec.
Foi então que, acompanhando a blogosfera de Railers, através de um post do Carlos Brando, conheci o Shoulda. O Shoulda é um framework recente e que eu considero como um passo intermediário entre o Test/Unit e o RSpec. Ele traz um conjunto de classes e métodos que melhoram o Test/Unit padrão, permitindo o uso de uma sintaxe parecida com a do RSpec. Assim é possível se aproximar de BDD sem ter muito impacto na forma de programar os testes.
Vou exemplificar aqui o migração dos testes de um model simples do Sismiko, a classe City que possui apenas os atributos “name” e “state_id”, representando o nome da cidade e o id do estado, respectivamente.
Observe o código utilizando o modo padrão:
class CityTest < Test::Unit::TestCase fixtures :cities #testa se o nome da cidade é um campo requerido def test_name_required check_required(:name) end #testa se o campo state_id da cidade é um campo requerido def test_state_required check_required(:state_id) end #testa se o tamanho máximo do campo nome é de 100 caracteres def test_max_name city = create(:name => "a" * 101) assert city.errors.invalid?(:name),"name must have a maximum of 100 chars" assert_not_nil city.errors.on(:name) assert !city.valid?, "city shouldn't be created" end private #cria uma cidade padrão def create(options={}) City.create({ :name => "Maringá", :state_id => 1 }.merge(options)) end #verifica se o campo passado em att é requerido pelo objeto def check_required(att) city = create(att => nil) assert city.errors.invalid?(att) assert_not_nil city.errors.on(att) assert !city.valid?, "city shouldn't be created" end end
No código acima vemos três testes sendo feitos e mais dois métodos auxiliares, para criar um objeto com valores padrão em seus atributos e outro para verificar se um atributo é requerido. Quanto as testes, o primeiro e o segundo testes verificam se os campos “name” e “state_id” são obrigatórios ao criar um objeto de cidade. O terceiro verifica se o campo “name” aceita no máximo 100 caracteres.
Vejamos agora o código necessário para fazer os mesmo testes, usando a declaração de contextos pregada pelo BDD para deixar os testes mais legíveis, já utilizando o Shoulda
class CityTest < Test::Unit::TestCase fixtures :cities context "A City instance" do setup do @city = create end should "have a required name" do check_required(:name) end should "have a required state" do check_required(:state_id) end #uso de contexto aninhado context "with a large name" do setup do @city.name = "a" * 101 @city.save end should "validate max size of name" do assert city.errors.invalid?(:name) end end end private #cria uma cidade padrão def create(options={}) City.create({ :name => "Maringá", :state_id => 1 }.merge(options)) end #verifica se o campo passado em att é requerido pelo objeto def check_required(att) city = create(att => nil) assert city.errors.invalid?(att) assert_not_nil city.errors.on(att) assert !city.valid?, "city shouldn't be created" end end
Com certeza ficou mais fácil de entender e também de localizar o erro. Por exemplo, caso o teste de atributo “name” requerido falhe, recebemos a seguinte mensagem:
test: A City instance should have a required name
O uso do novo framework melhorou a legibilidade dos testes, mas podemos melhorar ainda mais. Fazendo uso de helpers acrescentados pelo Shoulda, é possível reduzir o código para algo assim:
class CityTest < Test::Unit::TestCase fixtures :cities should_require_attributes :name, :state_id #1 should_ensure_length_in_range :name, (0...100) #2 end
Sem dúvida, muito mais simples. A linha comentada com #1 substitui dois testes escritos anteriormente, validando os campos “name” e “state_id” como requeridos. Em #2 verifica-se se o atributo “name” aceita valores com tamanho entre 0 e 100, rejeitando valores maiores.
Ficou impressionado? Instale o Shoulda e faça suas experiências, o projeto está no github:
script/plugin install git://github.com/thoughtbot/shoulda.git
Veja também:
O Lucas Hungaro fez um screencast muito bacana com uma introdução ao Shoulda.
Existem vários outros helpers além dos apresentados aqui. A documentação de todos eles pode ser encontrada na documentação do projeto.