Pular para o conteúdo
novembro 16, 2008 / cassiomarques

Testes funcionais no Rails usando o Cucumber

Introdução

Neste artigo pretendo falar um pouco sobre o Cucumber e mostrar como podemos usá-lo para escrever testes funcionais para aplicações Rails. Todos os testes foram executados em um Ubuntu 8.04 mas acredito que quem utiliza Mac OS também não terá muitos problemas.

O que são testes funcionais

Testes funcionais são aqueles que procuram testar as funcionalidades de sua aplicação, verificando a integração entre as diversas partes que a compõe. A idéia é simular a interação de um usuário real com o sistema, sem se preocupar com os detalhes de implementação da funcionalidade. Aqui temos um nível maior de abstração que nos testes unitários, onde testamos detalhes do código. Normalmente os testes funcionais são testes de caixa preta: Você fornece entradas, recebe saídas e verifica se as mesmas estão corretas. Como o sistema processou as entradas não interessa neste ponto.

A confusão com o termo “testes funcionais” no Rails

Quem desenvolve com o Rails e utiliza o Test::Unit para escrever seus testes sabe que muitas pessoas chamam os testes escritos para os controllers da aplicação (aqueles que verificam o comportamento de cada uma das actions do sistema) de testes funcionais. Esta definição está errada. Testes dos controllers escritos desta forma não são funcionais e sim testes unitários. Essa confusão se deve principalmente ao fato de que os testes dos controllers no Rails escritos com o Test::Unit ficam normalmente dentro da pasta test/funcionals e para executá-los fazemos rake test:funcionals. Se você pensa dessa forma e chama de testes funcionais os testes de controllers que testam action por action e entram nos seus detalhes de implementação, esqueça tudo. Não importa se você escreve estes testes com o Test::Unit, Shoulda ou RSpec. Se você está verificando como os objetos da sua aplicação se relacionam e não simulando o comportamento de um usuário, este não é um teste funcional, mas sim um teste unitário.

Como escrever testes funcionais?

Existem diversas maneiras de escrever testes funcionais. No caso de aplicações web, uma das ferramentas mais utilizadas atualmente é o Selenium, que usa o(s) browser(s) instalado(s) no seu sistema para interagir com a aplicação, entrando em páginas, preenchendo formulários, verificando a existência de elementos na estrutura dos documentos, etc. O Selenium é uma ferramenta muito boa mas que tem um pequeno problema: seus testes são lentos. Se você tiver muitos testes com o Selenium e quiser executá-los com muita frequência, vai sofrer um pouco. Outro ponto é que escrever estes testes pode se tornar algo um pouco monótono e cansativo.

O ponto neste artigo é mostrar como escrever testes funcionais utilizando uma abordagem um pouco diferente, ou seja, usando técnicas de Behaviour Driven Development (ou BDD).
Isso não significa que você deve deixar de testar com o Selenium ou ferramentas semelhantes, de forma alguma! Os testes com tais ferramentas são diferentes, com eles você pode por exemplo verificar que sua aplicação está funcionando corretamente em diversos browsers, sem precisar ficar igual bobo clicando em trocentos botões. Mas o BDD possibilita que você crie especificações executáveis.

BDD

A idéia do BDD é que a partir das especificações de sua aplicação (ou seja, o que essa joça tem que fazer!), você pode escrever testes que verifiquem o comportamento esperado, antes mesmo que ele esteja implementado. A coisa se mistura um pouco com a forma de pensar do Test Driven Development (ou TDD): Você sabe como a aplicação deve funcionar, mas ela ainda não funciona. Então você escreve um teste, que óbviamente vai falhar. Então você cria código que faz com o teste passe. O código estará feio, então você vai refatorá-lo, deixa-lo melhor e ver se o teste continua passando. Se sim, você escreve um novo teste. E o ciclo se repete, sempre tendo certeza de que todos os outros testes criados anteriormente continuam sendo executados com sucesso.
No BDD você fará basicamente a mesma coisa que no TDD, mas o contexto é um pouco diferente. Os testes são escritos com base no comportamento esperado dos elementos que compõe a aplicação (seus objetos, a view, rotas, interação com banco de dados, etc, etc, etc). No Ruby, possivelmente a ferramenta mais utilizada para BDD é o RSpec. Mas ele serve para especificar o comportamento em um nível de abstração mais baixo, mais próximo dos objetos e seus pequenos detalhes. Neste artigo queremos falar sobre as coisas de forma mais geral, mais alto nível: queremos especificar, implementar e testar User Stories.

User stories e BDD

De forma geral, toda user story pode ser resumida em um sequência finita de passos, onde os três pontos princípais são:

  • Dado que … (o estado atual das coisas)
  • Deseja-se fazer algo (a ação)
  • Para que se consiga alguma coisa (o resultado ou objetivo)

A partir deste modelo podemos escrever nossos testes funcionais.

Conheça o Cucumber

Do site do Cucumber no GitHub:

O Cucumber é uma ferramenta que pode executar documentação de funcionalidades escrita em texto puro.

Você escreve especificações sobre como uma determinada funcionalidade da sua aplicação deve ser, usando texto puro mesmo. E o Cucumber executa testes em cima destas especificações. Parece mágica? Bom, só um pouco. Na verdade é BDD + algumas convenções + a mágica do Ruby.

Seguindo a filosofia do BDD, você deve escrever as especificações antes de implementar qualquer código. Você modela seu código a partir do comportamento esperado da aplicação.

Para cada coisa, a ferramenta certa

Se você está criando testes com maior grau de abstração, use o Cucumber. Quando precisar “descer” até os detalhes do código, use o RSpec para especificar como os elementos que formam sua aplicação devem interagir e como deve ser o comportamento de cada um destes elementos.

Um exemplo de especificação com o Cucumber

Vamos pegar um exemplo bem simples: Um cadastro de produtos. Uma especificação funcional (ou característica da aplicação) poderia ser:

Feature: Products registration
  In order to control my products
  As a system's user
  I want to be able to manage the products registration
  
  Scenario: Creating a new product
    Given I visit the new product page
    When I fill the new product form with Foobar as description and 10.00 as the price
    And click on the 'Create' button
    Then the number of existent products should be increased by one
    And I should be sent to the new product's page

Ou seja: Quero acessar a página de novos produtos, preencher o formulário e enviá-lo. Isso deve aumentar o número de produtos existentes em 1 unidade e eu devo então ver a página com os detalhes do produto recém-criado. As especificações como as descritas acima devem ser salvas em um arquivo com a extensão feature.

Note que usamos algumas palavras-chave:

  • Feature: define uma nova funcionalidade do sistema, a qual será composta por um ou mais cenários. Logo após o título da feature deve-se escrever uma breve descrição da feature, usando o modelo “objetivo/papel/o que deve ser feito para atingir o objetivo”
  • Scenario: define um possível cenário dentro da aplicação. Aqui temos a descrição de uma das possíveis user stories e podemos então descrever o estado atual da aplicação, o que desejamos fazer e quais são os resultados esperados.
  • Given: Usado para especificar o estado da aplicação no momento em que o teste for executado.
  • When: Usado para especificar as ações a serem realizadas
  • Then: Usado para especificar os resultados esperados.
  • And: Usado para ‘unir’ os diferentes passos que podem compor as cláusulas Given/When/Then

Estas palavras-chave são usadas pelo Cucumber para ‘ler’ o arquivo e separar cada uma das features e respectivos cenários. A partir destas especificações o Cucumber tenta executar testes sobre sua aplicação. Um aspecto muito interessante é que o Cucumber permite que sejam utilizados outros idiomas além do inglês para escrever as features. Português é um dos idiomas suportados. Para uma lista completa dos idiomas e quais são as palavras-chave que devem ser utilizadas em português, consulte o wiki do Cucumber.

Bom, para mostrar melhor como isso tudo funciona, vamos por a mão na massa! Logo você vai entender como o Cucumber faz para executar cada passo e como ele consegue acessar sua aplicação Rails.

Instalando o Cucumber

O Cucumber pode ser instalado como uma gem ou como um plugin do Rails. No primeiro caso, você pode usá-lo de forma mais genérica, para testar o comportamento de qualquer tipo de aplicação, até mesmo aplicações escritas em linguagens diferentes de Ruby. No segundo caso, mais específico para Rails, podemos escrever features e executá-las de forma totalmente integrada às nossas aplicações.
Apesar de atualmente estar usando o Cucumber apenas nas minhas aplicações Rails, eu instalei também a gem, porque com isso ganha-se o comando cucumber, através do qual pode-se ter maior controle sobre como as features são executadas.

Instalando a gem

O Cucumber está hospedado no GitHub e usa o gem server deles. Caso você nunca tenha instalado uma gem no seu sistema a partir do github, faça:

gem sources -a http://gems.github.com

Isso adicionará o GitHub como um novo servidor remoto de gems. Em seguida faça

sudo gem install cucumber

Algumas dependências serão também instaladas, caso as mesmas ainda não existam na sua máquina. Aqui no meu Ubuntu ele instalou o hpricot (usado para parsing de html/xml), o polyglot (para suporte a outros idiomas – mais sobre isso daqui a pouco), o term-ansicolor (lib em Perl para colorir saídas no terminal) e o treetop (um dos pilares do Cucumber, usado para descrever novas linguagens).

Instalando os plugins no Rails

Se você estiver usando o Git e sua aplicação Rails já estiver versionada com o mesmo, basta criar submódulos para os plugins e instalá-los:

git submodule add git://github.com/aslakhellesoy/cucumber.git vendor/plugins/cucumber
git submodule add git://github.com/brynary/webrat.git vendor/plugins/webrat
git submodule add git://github.com/dchelimsky/rspec.git vendor/plugins/rspec
git submodule add git://github.com/dchelimsky/rspec-rails.git vendor/plugins/rspec-rails

Isso irá instalar o plugin do Cucumber, o Webrat e os plugins do RSpec para Rails (precisamos deles para usar os matchers do RSpec nos nossos testes). Caso você prefira, pode simplesmente fazer um clone de cada um dos plugins acima. Entre em vendor/plugins e clone os plugins ali dentro.

Instale também o diff-lcs, implementação em Ruby do algorítmo Least Common Subsequence.

sudo gem install diff-lcs

Webrat

O Webrat (acronímo para ‘Ruby Acceptance Testing for Web applications’) é uma ferramenta para teste de aplicações web que manipula o DOM dos seus documentos HTML e simula interação com as páginas que compõe a aplicação. O Webrat pode ser instalado tanto como uma gem como um plugin dentro de sua aplicação. Os testes são executados de forma similar às ferramentas como Watir ou Selenium, porém é muito mais rápido e menos frágil. O Webrat possui um certo nível de inteligência para encontrar elementos dentro da estrutura do seu documento.
Por padrão o Cucumber utiliza o Webrat para interagir com a sua aplicação web: acessar páginas, preencher formulários, selecionar opções, clicar em botões e links, etc. Some isso com os matchers do RSpec e você terá uma poderosa forma de escrever e executar testes funcionais no Rails.
A desvantagem do Webrat em relação às outras soluções citadas é que ele não é capaz de testar JavaScript. Se as funcionalidades principais da sua aplicação não dependem muito de JavaScript/Ajax, acredito que o Webrat seja uma excelente solução. Caso você precise testar todos aquelas funcionalidades com Ajax da sua aplicação mas queira usar uma abordagem BDD para fazê-lo, é possível também usar o Cucumber com o Selenium. Na página do Cucumber no GitHub tem um exemplo de como fazê-lo.

O Webrat já foi instalado nos passos para instalação do Cucumber. Entretanto, algumas de suas funcionalidades foram implementadas usando o nokogiri, que é uma lib para parsear xml/html semelhante ao hpricot. É interessante que você também instale-o. Durante a instalação aqui no meu Ubuntu 8.04 tive alguns probleminhas com dependências nativas que estavam faltando para o nokogiri (libxml, libxslt e os respectivos bindings para ruby), mas foi fácil de resolver. Primeiro instale as dependências:

sudo apt-get install libxml2 libxml2-dev libxslt1.1 libxslt1-dev libxml-ruby libxslt-ruby

E depois

sudo gem install nokogiri 

Usando o Cucumber

Criando a estrutura necessária para o Cucumber na sua aplicação

Para usar o Cucumber no Rails precisamos criar os diretórios necessários e um Rakefile com as tasks necessárias para executar nossas features. Isso pode ser feito com o comando

ruby script/generate cucumber

Serão criados os seguintes diretórios e arquivos:

pastas criadas pelo cucumber

pastas criadas pelo cucumber

  • Arquivos com features escritas em texto puro vão dentro de features/
  • Arquivos com as definições dos passos a executar (arquivos com código Ruby) vão dentro de features/step_definitions/
  • Dentro de features/support/ temos o arquivo env.rb, que funciona de forma semelhante ao test_helper.rb (do Test::Unit) ou o spec_helper.rb (do RSpec)

Fluxo de desenvolvimento com o Cucumber

Como já dito anteriormente, o Cucumber é uma ferramenta para BDD. Isso significa que espera-se que você escreva as suas especificações com o comportamento esperado para uma determinada funcionalidade do seu sistema antes mesmo de escrever qualquer código. Nada impede que, caso você já tenha uma aplicação com um monte de coisa pronta e queira passar a usar o Cucumber com ela, você escreva features para testar as coisas que já estão funcionando. Aumentar a cobertura de testes da sua aplicação é sempre muito bom! Mas neste artigo vou apresentar o uso do Cucumber seguindo as técnicas do BDD.

Escrevendo nossa feature

Vamos usar o mesmo exemplo anteriormente: Um cadastro para produtos. Nossa primeira feature então será a possibilidade de cadastrar novos produtos. Precisamos escrever essa feature (na verdade isso já foi feito ali pra trás…). O plugin do Cucumber nos fornece um generator para criar os arquivos necessários para escrevermos nossas features, que pode ser usado com

script/generate feature ModelName [field:type, field:type]

Entretanto eu não aconselho o uso deste generator, pois User stories devem ser escritas em conjunto com o cliente, ele é quem tem que explicar como as coisas devem funcionar. Usando o generator, você vai acabar tendo que reescrever praticamente tudo o que foi gerado e vai usar só os arquivos que foram gerados, os quais você provavelmente vai acabar renomeando também…

Para escrever as features, devemos criar um arquivo em features/products.feature. Este arquivo pode ter qualquer nome, não há convenção quanto a isso. Apenas use a extensão feature. Nossa feature então poderia ser escrita assim:

Feature: Products registration
  In order to control my products
  As a system's user
  I want to be able to manage the products registration
  
  Scenario: Creating a new product
    Given I visit the new product page
    When I fill the new product form with Foobar as description and 10.00 as the price
    And click on the 'Create' button
    Then the number of existent products should be increased by one
    And I should be sent to the page with the product's details

Executando nossa feature

Para executar a feature você pode usar a rake task criada com o comando script/generate cucumber ou então usar o executável do Cucumber que vem com a instalação da gem. A rake task vai executar todas as suas features:

rake features

Usando o comando cucumber você ganha diversas opções, como formatação das saídas, poder gravar a execução em um arquivo, poder executar somente cenários específicos de suas features, etc. Executando cucumber -help você terá uma lista com todas as possíveis opções, sendo as mais interessantes:

  • -l, –line LINE – Executa somente o cenário existente na linha informada
  • -s, –scenario SCENARIO – Executa apenas o cenário com o nome informado
  • -a, –language LANG – Especifica a linguagem que será utilizada para executar as features. O padrão é o inglês, se você quiser escrever suas features em português basta usar –language pt ou -a pt
  • -f, –format FORMAT – Formata a saída com os resultados da execução das features. O padrão é pretty, mas você pode usar profile, progress, html ou autotest. O modo em HTML é bem interessante, pois ele gera uma saída que pode posteriormente ser usada como documentação da aplicação.

Como ponto de partida, criei uma aplicação Rails do zero e gerei apenas um controller (vazio) e um model Product com os atributos description:string e price:float

Vamos usar o padrão e rodar nossa feature com o rake. Por padrão ele usa o formato pretty e carrega todo o banco de teste do zero, rodando a rake task db:test:prepare automaticamente. A execução do comando rake features neste momento nos trará a seguinte saída:

cassio@cassio-laptop:~/desenvolvimento/cucumber_howto$ rake features
(in /home/cassio/desenvolvimento/cucumber_howto)
NOTA:  CREATE TABLE criará sequência implícita "products_id_seq" para coluna serial "products.id"
NOTA:  CREATE TABLE / PRIMARY KEY criará índice implícito "products_pkey" na tabela "products"
Feature: Products registration  # features/products.feature
  In order to control my products
  As a system's user
  I want to be able to manage the products registration
  Scenario: Creating a new product                                   # features/products.feature:6
    Given I visit the new product page                               # features/products.feature:7
    When I fill the new product form with Foobar as description and 10.00 as the price                                 # features/products.feature:8
    And click on the 'Create' button                                 # features/products.feature:9
    Then the number of existent products should be increased by one  # features/products.feature:10
    And I should be sent to the page with the product's details      # features/products.feature:11


5 steps pending

You can use these snippets to implement pending steps:

Given /^I visit the new product page$/ do
end

When /^I fill the new product form$/ do
end

When /^click on the 'Create' button$/ do
end

Then /^the number of existent products should be increased by one$/ do
end

Then /^I should be sent to the page with the product's details$/ do
end

Aqui no meu blog a saída está toda em preto, mas como o Cucumber usa o term-ansicolor, a saída no terminal sairá colorida. Os passos que foram executados com sucesso ficam em verde, os que ainda estão pendentes ficam em amarelo e os que falham em vermelho. No nosso caso temos 5 passos em amarelo, todos pendentes.
Isso é uma característica interessante do Cucumber: Em uma sequência de passos, caso um deles esteja pendente ou falhe, os passos subsequentes não serão executados. Ainda assim, caso um dos passos subsequentes também esteja pendente, ele também será exibido em amarelo.

Pode-se observar também que o Cucumber já nos deu um ponto de partida para implementar a execução dos nossos passos: 5 snippets com os passos que devem ser implementados. É aqui que começa a brincadeira! Vá copiando os passos do terminal para um arquivo product_steps.rb, salvando-o em features/step_definitions/. Agora vamos escrever um pouco de Ruby!

Given /^I am loggged in$/ do
end

Given /^I visit the new product page$/ do
end

When /^I fill the new product form$/ do
end

When /^click on the 'Create' button$/ do
end

Then /^the number of existent products should be increased by one$/ do
end

Then /^I should be sent to the page with the product's details$/ do
end

Observe que o que cada passo Given/When/Then recebe é uma expressão regular. Isso é porque essa é uma boa forma para que o Cucumber relacione os passos que você escreveu nos seus cenários no arquivo products.feature e as devidas implementações destes passos. Vamos começar a implementar isso. Para testar os models, vou usar o Factory Girl ao invés de fixtures. Caso você ainda não conheça o Factory Girl, dê uma olhada no site do projeto, a utilização é bastante simples.
Para que as factories do Factory Girl estejam disponíveis para o Cucumber, vamos precisar dar uma mexida no arquivo features/support/env.rb, coisa simples:

# Sets up the Rails environment for Cucumber
ENV["RAILS_ENV"] = "test"
require File.expand_path(File.dirname(__FILE__) + '/../../config/environment')
require 'cucumber/rails/world'
Cucumber::Rails.use_transactional_fixtures

# Comment out the next line if you're not using RSpec's matchers (should / should_not) in your steps.
require 'cucumber/rails/rspec'

require 'factory_girl'
require File.expand_path(File.dirname(__FILE__) + '/../../spec/factories/factories.rb')

Adicionei as linhas 10 e 11, onde primeiro faço um require no factory_girl e em seguida carrego meu arquivo com as definições das fábricas existentes, neste caso localizado em RAILS_ROOT/spec/factories/factories.rb. Pronto, já podemos usar o Factory Girl com o Cucumber.

A implementação do passo que visita a página para criação de um novo produto fica:

Given /^I visit the new product page$/ do
  visits new_product_url
end

É importante notar que este é um arquivo Ruby comum. Qualquer código Ruby válido pode ser usado aqui! E como você está dentro de uma aplicação Rails e está usando o plugin para Rails do Cucumber, tem acesso a todos os seus models daqui.

O método visits vem do Webrat e serve para acessar uma determinada url da sua aplicação. Você pode passar para ele uma string para a URI como products/new, um hash na forma :controller => “foo”, :action => “bla” ou o método para geração da URI de uma named_route.

Executando novamente nossas features, temos:

Feature: Products registration  # features/products.feature
  In order to control my products
  As a system's user
  I want to be able to manage the products registration
  Scenario: Creating a new product                                   # features/products.feature:6
    Given I visit the new product page                               # features/step_definitions/products_steps.rb:1
      No action responded to new (ActionController::UnknownAction)
      /usr/lib/ruby/gems/1.8/gems/actionpack-2.1.1/lib/action_controller/filters.rb:579:in `call_filters'
      /usr/lib/ruby/gems/1.8/gems/actionpack-2.1.1/lib/action_controller/filters.rb:572:in `perform_action_without_benchmark'
      ... um monte de erro ...
      /usr/lib/ruby/gems/1.8/gems/actionpack-2.1.1/lib/action_controller/integration.rb:501:in `method_missing'
      ./features/step_definitions/products_steps.rb:2:in `Given /^I visit the new product page$/'
      features/products.feature:7:in `Given I visit the new product page'
    When I fill the new product form with Foobar as description and 10.00 as the price   # features/step_definitions/products_steps.rb:5
    And click on the 'Create' button                                 # features/step_definitions/products_steps.rb:8
    Then the number of existent products should be increased by one  # features/step_definitions/products_steps.rb:11
    And I should be sent to the page with the product's details      # features/step_definitions/products_steps.rb:14


1 steps failed
4 steps skipped
rake aborted!

Isso aconteceu porque ainda não temos uma action new no nosso ProductsController. Após implementá-la corretamente, teremos algo assim:

Feature: Products registration  # features/products.feature
  In order to control my products
  As a system's user
  I want to be able to manage the products registration
  Scenario: Creating a new product                                   # features/products.feature:6
    Given I visit the new product page                               # features/step_definitions/products_steps.rb:1
    When I fill the new product form with Foobar as description and 10.00 as the price  # features/products.feature:8
    And click on the 'Create' button                                 # features/products.feature:9
    Then the number of existent products should be increased by one  # features/products.feature:10
    And I should be sent to the page with the product's details      # features/products.feature:11


1 steps passed
4 steps pending

Estamos no caminho! Vamos implementar o restante dos passos e o código necessário para executá-los com sucesso (nossos models, actions, etc) e ver como isso fica!

Given /^I visit the new product page$/ do
  visits new_product_url
end

When /^I fill the new product form with (.+) as description and (\d+\.\d+) as the price$/ do |description, price|
  @actual_count = Product.count
  fills_in "Description", :with => description
  fills_in "Value", :with => price.to_f
end

When /^click on the 'Create' button$/ do
  clicks_button "Create"
end

Then /^the number of existent products should be increased by one$/ do
  Product.count.should == @actual_count + 1
end

Then /^I should be sent to the page with the product's details$/ do
  response.should have_tag("p", :text => /Description:/)
  response.should have_tag("p", :text => /Price:/)
end

e a execução do comando rake features nos retornará sucesso!

5 steps passed

Vamos estudar um pouco do que fizemos aqui. Os métodos fills_in e clicks_button são do Webrat e suas funções podem ser facilmente deduzíveis a partir de seus nomes. Para uma relação mais completa dos métodos do Webrat, dê uma olhada no wiki do projeto no GitHub.
Algo muito importante de estudarmos aqui é o passo que começa na linha 5. Aqui, alteramos um pouco o conteúdo da expressão regular e colocamos alguns grupos de captura. O que esses grupos fazem é, no trecho em questão, casar o texto passado no nosso arquivo products.feature com um padrão específico. Dessa forma podemos criar uma definição de passo que cria produtos com descrições e preços variáveis. Isso torna nosso passo reutilizável. Os valores capturados pelos grupos são passados como parâmetros para o bloco, na ordem em que foram declarados.

No último passo usei alguns matchers para testes de views do RSpec, mais específicamente o have_tag, que verifica se uma determinada tag está presente na estrutura da página. O RSpec possui uma sério de matchers desse tipo, muito poderosos por sinal. Se você ainda não os conhece, sugiro que dê uma olhada nos mesmos.

Shared Steps

Podemos escrever definições para passos que podem ser compartilhados por todas as features. Isso é possível porque por padrão o Cucumber vai carregar todos os arquivos dentro de features/step_definitions quando você executar uma feature. É por isso que o nome que você vai dar aos arquivos não faz a menor diferença na maioria dos casos. Mas procure manter a coisa toda organizada!
Um ótimo exemplo para um passo compartilhado é o login de um usuário no sistema. Caso você queira verificar funcionalidades da sua aplicação levando em consideração se o usuário atual está ou não logado, você pode criar um shared step e reutilizá-lo em diversas features, ao invés de fazer ctrl-c, ctrl-v em trocentos lugares.

Scenario: A logged user trying to destroy a product
  Given I do my login
  And visit the /products/1/page
  When I click the 'Destroy' link
  Then the product count should decrease by one
  And I should see the producs index page

Uma implementação para o passo “Given I do my login” poderia ser (usando o restful_authentication e o Factory Girl):

Given /^I do my login$/ do
  @user = Factory(:user)
  visits new_session_path
  fills_in "login", :with => @user.login
  fills_in "password", :with => @user.password
  clicks_button "Login"
end

Você poderia salvar este código em um arquivo em features/step_definitions/login.rb e então usar o texto “Given I do my login” em vários outros cenários sem nunca mais precisar escrever a definição dos passos necessários para o login novamente.

Fantástico, não?

About these ads

22 Comentários

Deixe um comentário
  1. Cairo Noleto / nov 16 2008 8:12 pm

    Ótimo artigo :)

    Muito bom não apenas para testes funcionais, mas um passo inicial para usar Cocumber :)

    Abraços e continue com esses artigos :)

  2. Rafael Lima / nov 17 2008 4:07 pm

    Muito bom seu artigo!

    Vou começar a utilizar o Cucumber a partir dele…

    Valeu!

  3. cassiomarques / nov 17 2008 4:14 pm

    Valeu pela força pessoal!

  4. Rafael Souza / nov 19 2008 5:00 am

    Ótimo artigo, parabéns!!

    Algumas dúvidas:
    É possível definir vários cenários dentro de único arquivo .features?
    Se sim, é certo fazer isso?

    Vou exemplificar. Estou testando um formulário, e quero testar dois casos, um submit com o formulário vazio e outro com os dados preenchidos, para ver se está sendo feita a validação corretamente e tal.

    Qual seria a melhor abordagem para este caso?

    Abraço!

  5. cassiomarques / nov 19 2008 11:57 am

    Olá Rafael,

    Na verdade é exatamente dessa forma que se deve fazer. Você descreve uma FEATURE (ou seja, uma funcionalidade) por arquivo .feature. Todos os cenários que fazem parte dessa funcionalidade devem ser descritos neste arquivo, de forma a agrupar os testes. Só não aconselho colocar mais de uma feature em uma mesmo arquivo feature, ai eu já acho que fica meio bagunçado.
    Mas por exemplo, eu crio arquivos feature até meio “gerais”, tipo “Cadastro de BlaBla”. Nesse arquivo cubro todos os cenários de criação de um elemento, edição, remoção, exibição, listar, etc, etc.

    Abraço!

  6. Diego Carrion / nov 19 2008 8:28 pm

    Oi, edição, remoção, exibicição, etc não são cenários, som features. Como features, elas devem estar em estorias diferentes porque podem representar diferente valor para o cliente. Por exemplo, criação de um elemento pode ser muito mais valioso pro cliente que a remoção.

    Cenario seria por exemplo criar um elemento que não existe, criar um elemento que ja existe, criar um elemento inválido por algúm motivo, etc…

    Gostei muito desse artigo, me fez pensar em muitas coisas. Não tenho concordado com todas mas sempre é legal ver outros pontos de vista, parabén Cassio (:

  7. cassiomarques / nov 19 2008 8:50 pm

    Olá Diego,

    Concordo com você que operações CRUD sejam features. A questão é que no artigo usei como exemplo de feature um “cadastro” como um todo, tipo “Deve ser possível gerenciar os itens”, o que incluí as 4 operaçòes do CRUD. Claro que em muitos casos vale mais à pena separar isso em features diferentes e escrever testes funcionais para cada uma em separado. O exemplo no artigo ficou mais genérico mesmo.

    Valeu pelo apoio :-)

  8. Eduardo Fiorezi / nov 21 2008 3:49 pm

    Ótimo artigo, virou referência.

    Abraços

  9. Pedro Pimentel / nov 26 2008 12:53 pm

    Muito completo! Parabéns Cássio pelo artigo! Como disseram acima, virou referência!

    Abraços

  10. davi / jan 16 2009 5:02 am

    Muito bom o artigo, virou referência.

    Mas fiquei com uma dúvida bem básica: apesar de terminologia estar incorreta, no Rails eu faço testes funcionais para testar meus controllers e views e testes unitários para testar meus models. Utilizando o Cucumber (que realmente faz testes funcionais), eu testo o que? O comportamento da minha aplicação. Até aí, sem problemas: entendi. A dúvida começa agora: com o Cucumber acaba a história de testar model, view e controller? Quero dizer, é tudo abstraído de tal forma que eu não tenho que me preocupar com o que o model faz, mas com o que a aplicação retorna?

    Trocando em miúdos: com o Cucumber eu não preciso, necessariamente, me preocupar com o que (model, view ou controller) eu estou testando?

    Eu sou muito novo em testes, TDD e BDD, então a dúvida é assim, básica mesmo. :-)

    Obrigado pela atenção

  11. cassiomarques / jan 16 2009 1:09 pm

    @Davi,

    Se você escreve e executa um teste de controller ou view sem que haja qualquer tipo de integração com o browser ou pelo menos com o HTML gerado (como o webrat faz) você está escrevendo testes unitários. Se em um teste de view você simplesmente pega o HTML de uma action e verifica se existe uma certa tag ou texto, por exemplo, você está criando testes unitários. Isso fica mais claro quando você pensa no fato de que está testando cada camada da aplicação (no caso do Rails o MVC) em separado. O objetivo destes testes não é verificar a aplicação como um todo, mas sim as camadas ou, de forma mais granular ainda, cada classe que compõe seu sistema.
    Quando usamos o cucumber estamos realizando um teste funcional, onde testamos o *comportamento* da aplicação quando submetida à determinadas situações e estados. Por outro lado, você também pode enxergar alguns dos testes feitos com o Cucumber como testes de integração, pois você integra todas as camadas quando executa os testes.
    Você não vai deixar de fazer testes unitários usando o Cucumber. A idéia é você definir o comportamento esperado (em um nível maior de abstração) usando o Cucumber e, quando precisar de maiores detalhes (tiver que testar a implementação), vai descer no nível de abstração e escrever testes unitários usando Test::Unit, Rspec, Shoulda ou qualquer outro.

  12. davi / jan 16 2009 1:27 pm

    Obrigado Cassio.

    Então, se não viajei na maionese, o Cucumber, em si, não faz testes unitários, apenas funcionais. Mas usar ele não me isenta de ter que fazer meus bons e velhos testes unitários. É isso mesmo, certo? :-)

    Obrigado novamente. As coisas ficaram muito mais claras para mim agora.

    Ah! E parabéns e obrigado pela tradução do “Scrum e XP direto das trincheiras”. É um livro inspirador e motivador para qualquer área.

  13. cassiomarques / jan 16 2009 1:30 pm

    Exatamente Davi. E obrigado pelo apoio!

  14. Tailor R. Fontela / jan 26 2009 4:16 am

    Excelente artigo Cassio,
    Já estou usando como referencia em um projeto aqui

    Continue compartilhando mais sobre o assunto!

  15. dayvison / jul 29 2009 10:50 pm

    muito bom o artigo … queria ter lido antes de quebrar a cabeça aqui com um projeto !

  16. Bruno Frank Cordeiro / mai 2 2011 2:30 pm

    Acho que o uso do Cucumber só é valido se o usuário for ler os testes caso contrário só aumenta a complexidade e o trabalho, a menos também que na equipe tenha algum programador que não saiba programar. :D

    Abraço a todos.

    • cassiomarques / mai 2 2011 2:46 pm

      Depois de usar o Cucumber por bastante tempo (esse post já tá ficando bem velhinho) compartilho da sua opinião, desde que testes de integração sejam escritos usando outra tecnologia, como Capybara + Rspec. Testes funcionais e de integração continuam sendo, ao menos na minha opinião, os testes mais importantes.

Trackbacks

  1. Como testar upload de arquivos com o Cucumber « /* CODIFICANDO */
  2. How to test file uploads with Cucumber « /* CODIFICANDO */
  3. Materiais sobre cucumber « Diego Alvarez Nogueira
  4. Cucumber « Crab Log
  5. Riopro Blog - » Testes de integração usando webrat, rspec, authlogic e subdomain_fu

Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s

Seguir

Obtenha todo post novo entregue na sua caixa de entrada.

%d blogueiros gostam disto: