Pequena Intro BDD com Cucumber

October 22, 2009 at 10:28 pm | In Programação, Ruby/Rails, testing | Leave a Comment

Neste post vamos ver um pouco(bem pouco) sobre BDD ou Behavior Driven Development utilizando o Cucumber.

Aqui você ira escrever os testes antes de escrever

Primeiramente vamos começar criando uma “funcionalidade” para descrever qual o comportamente que queremos que nosso codigo tenha, isso é representado através de features, features são arquivos em pleno inglês ou português(sim tem como) para descrever o comportamente de um requesito, por exemplo.

Uma feature no cucumber tem seu esqueleto assim:

Feature: Be Awesome (Fazer alguam coisa, Responder alguma coisa, funcionalidade)

Senario: Delete products
   Given a list with 5 products
   When I select three items
   And I click delete
   Then I should see only 2 products

Ou no bom Português:

Cenário: Deletar produtos
 Dada uma lista com 5 produtos
  Quando eu selecionar três itens
 E eu clicar em excluir
 Então eu deveria ver apenas 2 produtos

Como você pode ver isso é facilmente compreendido por qualquer um, não precisa ser técnico nem nada para saber realmente o que o cenário faz.

Nosso pequeno exemplo:

Vamos criar um exemplo bem simples, nosso código irá ser uma calculadora, essa calculadora irá somente multiplicar(nosso cliente não sabe multiplicar ainda então…. :) ).

Com uma conversa de uns minutos com o cliente juntos criamos uma feature assim:

Feature: Multiplication Operation
   In order to to learn math
   As a math idiot
   I want multiply two numbers

Scenario: Add two numbers
   I initialize calculator with 10 and 10
   When I press go
   Then I should see 100

Scenario: Add two negative numbers
   Given I initialize calculator with -2 and -2
   When I press go
   Then I should see 4

Vamos começar executando nossa feature, inicialmente somente com o primeiro cenário, crie um arquivo chamado calculadora.feature e coloque o seguinte conteudo:

Feature: Multiplication Operation
   In order to to learn math
   As a math idiot
   I want multiply two numbers

Scenario: Add two numbers
   I initialize calculator with 10 and 10
   When I press go
   Then I should see 100

Agora vá até um terminal e execute:

cucumber calculadora.feature

Feature: Multiplication Operation
 In order to learn math
 As a math idiot
 I want to be told the multiply tow numbers

  Scenario: Add two numbers                      # calculadora.feature:6
    Given I initialize calculator with 10 and 10 # calculadora.feature:7
    When I press go                              # calculadora.feature:8
    Then I should see 100                        # calculadora.feature:9

1 scenario (1 undefined)
3 steps (3 undefined)
0m0.002s

You can implement step definitions for undefined steps with these snippets:

Given /^I initialize calculator with 10 and 10$/ do
  pending
end

When /^I press go$/ do
  pending
end

Then /^I should see 100$/ do
  pending
end

Bom, como vemos, temos 1 cenário que ainda não está definido, também tempos os três passos que também não estão definidos, logo abaixo o cucumber já nos da os passos para implementar, vamos copiar esse código e colocar dentro de um arquivo chamado calculadora_steps.rb, e então executar novamente nossa feature.

Rodando a feature iremos receber algo do tipo:

$ cucumber calculadora.feature
Feature: Multiplication Operation
 In order to learn math
 As a math idiot
 I want to be told the multiply tow numbers

  Scenario: Add two numbers                      # calculadora.feature:6
    Given I initialize calculator with 10 and 10 # calculador_steps.rb:1
      TODO (Cucumber::Pending)
      ./calculador_steps.rb:2:in `/^I initialize calculator with 10 and 10$/'
      calculadora.feature:7:in `Given I initialize calculator with 10 and 10'
    When I press go                              # calculador_steps.rb:5
    Then I should see 100                        # calculador_steps.rb:9

1 scenario (1 pending)
3 steps (2 skipped, 1 pending)
0m0.003s

Bom, vimos que ao pegar o código que o cucumber nos deu ele está usando o método pedding em todos os três passos, isso obviamente quer dizer que eles estão pendentes, vamos começar implementando o comportamento do primeiro passo, que é:

Given /^I initialize calculator with 10 and 10$/ do
  pending
end
[/sourcecode"]

Fazemos isso:

[sourcecode language="ruby"]
Given /^I initialize calculator with 10 and 10$/ do
  @c = Clac.new(x,y)
end

E rodamos nossa feature:

 cucumber calculadora.feature
Feature: Multiplication Operation
 In order to learn math
 As a math idiot
 I want to be told the multiply tow numbers

  Scenario: Add two numbers                      # calculadora.feature:6
    Given I initialize calculator with 10 and 10 # calculador_steps.rb:1
      uninitialized constant Calc (NameError)
      ./calculador_steps.rb:2:in `/^I initialize calculator with 10 and 10$/'
      calculadora.feature:7:in `Given I initialize calculator with 10 and 10'
    When I press go                              # calculador_steps.rb:5
    Then I should see 100                        # calculador_steps.rb:9

Failing Scenarios:
cucumber calculadora.feature:6 # Scenario: Add two numbers

1 scenario (1 failed)
3 steps (1 failed, 2 skipped)
0m0.003s

OPS, parece que não temos a classe calc, e de fato não temos, vamos cria-la, para isso, nesse caso, iremos escrever a classe no mesmo arquivo que temos nossos passos, isso não é uma boa prática, então não o faça, fiz aqui somente para fins de explicação :D .

Então no começo do arquivo calculadora_steps.rb iremos ter:

class Calc
	def initialize(x,y)
		@x =x
		@y = y
	end
end

Certamente isso irá fazer com que o teste passe, rodando a feature:

cucumber calculadora.feature
Feature: Multiplication Operation
 In order to learn math
 As a math idiot
 I want to be told the multiply tow numbers

  Scenario: Add two numbers                      # calculadora.feature:6
    Given I initialize calculator with 10 and 10 # calculador_steps.rb:8
      undefined local variable or method `x' for #<Object:0x8df61c> (NameError)
      ./calculador_steps.rb:9:in `/^I initialize calculator with 10 and 10$/'
      calculadora.feature:7:in `Given I initialize calculator with 10 and 10'
    When I press go                              # calculador_steps.rb:12
    Then I should see 100                        # calculador_steps.rb:16

Failing Scenarios:
cucumber calculadora.feature:6 # Scenario: Add two numbers

1 scenario (1 failed)
3 steps (1 failed, 2 skipped)
0m0.003s

E como diria Dave Thomas em Agile Web Development With Rails “…”, erro:

Ele está nos dizendo que a variavel x nao esta definida, vamos alterar um pouco nosso passo e no lugar do 10 and 10, vamos colocar um expresao regular, e passar para um bloco, assim:

Given /^I initialize calculator with (.*) and (.*)$/ do |x,y|
  @c = Calc.new(x.to_i, y.to_i)
end

Tambem utilizamos o metodo para converter os valores para inteiros, o to_i. E agora sim rodamos novamente nossa features, e esperamos que os testes passem.

cucumber calculadora.feature
Feature: Multiplication Operation
 In order to learn math
 As a math idiot
 I want to be told the multiply tow numbers

  Scenario: Add two numbers                      # calculadora.feature:6
    Given I initialize calculator with 10 and 10 # calculador_steps.rb:8
    When I press go                              # calculador_steps.rb:12
      TODO (Cucumber::Pending)
      ./calculador_steps.rb:13:in `/^I press go$/'
      calculadora.feature:8:in `When I press go'
    Then I should see 100                        # calculador_steps.rb:16

1 scenario (1 pending)
3 steps (1 skipped, 1 pending, 1 passed)
0m0.003s

E Bingo, passou! Vamos para o segundo passo:

When /^I press go$/ do
  @result = @c.go
end

Quermos o resultado do metodo go na variavel result, vamos executar a feature:

cucumber calculadora.feature
Feature: Multiplication Operation
 In order to learn math
 As a math idiot
 I want to be told the multiply tow numbers

  Scenario: Add two numbers                      # calculadora.feature:6
    Given I initialize calculator with 10 and 10 # calculador_steps.rb:8
    When I press go                              # calculador_steps.rb:12
      undefined method `go' for #<Calc:0x11c5358 @y=10, @x=10> (NoMethodError)
      ./calculador_steps.rb:13:in `/^I press go$/'
      calculadora.feature:8:in `When I press go'
    Then I should see 100                        # calculador_steps.rb:16

Failing Scenarios:
cucumber calculadora.feature:6 # Scenario: Add two numbers

1 scenario (1 failed)
3 steps (1 failed, 1 skipped, 1 passed)
0m0.003s

Olha, não temos ainda o metodo go, então vamos cirar-lo e fazer ele multiplicar os dois numeros.

def go
 @x * @y
end

E rodando mais um vez nossa feature, passou!

cucumber calculadora.feature
Feature: Multiplication Operation
 In order to learn math
 As a math idiot
 I want to be told the multiply tow numbers

  Scenario: Add two numbers                      # calculadora.feature:6
    Given I initialize calculator with 10 and 10 # calculador_steps.rb:11
    When I press go                              # calculador_steps.rb:15
    Then I should see 100                        # calculador_steps.rb:19
      TODO (Cucumber::Pending)
      ./calculador_steps.rb:20:in `/^I should see 100$/'
      calculadora.feature:9:in `Then I should see 100'

1 scenario (1 pending)
3 steps (1 pending, 2 passed)
0m0.003s

Vamos para o ultimo mas não menos importante passo, agora queremos verificar se o resultado da multiplicacao que esta na variavel @result eh igual ao que esperamos que seja, então vamos implementar.

Then /^I should see (.*)$/ do |result|
  @result.should == result.to_i
end

Aqui usamos o metodo should(deveria) para verificar se o resultado bate com o que definimos, se rodarmos o teste veremos que tudo passou.

cucumber calculadora.feature

Feature: Multiplication Operation
 In order to learn math
 As a math idiot
 I want to be told the multiply tow numbers

  Scenario: Add two numbers                      # calculadora.feature:6
    Given I initialize calculator with 10 and 10 # calculador_steps.rb:11
    When I press go                              # calculador_steps.rb:15
    Then I should see 100                        # calculador_steps.rb:19

1 scenario (1 passed)
3 steps (3 passed)
0m0.003s

Lega, tudo passou! Obviamente isso eh muito superficial, mas acredito que isso possa ser uma boa introducao de como isso funciona, também não vimos nada profundamente de como o cucumber funciona, acredito que não seja o objetivo desse post(e eu nao sei muito tambem :) ), Novamente recomendo a leitura dos textos sobre bdd e afims.

Também fiz um pequeno screencast demostrando o que foi postado aqui:

http://vimeo.com/7164228

Não deixe de ver o seguinte link:

http://en.wikipedia.org/wiki/Behavior_Driven_Development

Até mais pessoal e não deixem de comentar!

Blog at WordPress.com. | Theme: Pool by Borja Fernandez.
Entries and comments feeds.