Merb: cucumber + webrat, czyli wszystko o testowaniu

Tym razem będzie o testowaniu aplikacji napisanej w Merbie. Większość pewnie zna framework testujący RSpec. Razem z RSpecem dostępny jest Story Runner. Jednak jak można wyczytać na stronie projektu:

RSpec’s Story Runner is now deprecated and will be extracted out to a separate gem soon. For more info on cucumber, see http://github.com/aslakhellesoy/cucumber/wikis I to właśnie wspomnianego ogórka opisze.

Cucumber podobnie jak RSpec Story Runner służy do testowania (i dokumentacji) aplikacji za pomocą czytelnych dla każdego scenariuszy zapisanych w formie zwykłego tekstu. Taki scenariusz jest przetwarzany i dopasowywany do odpowiednich, zdefiniowanych w osobnym pliku kroków. Główne zalety frameworka to m.in. możliwość przetestowania całej aplikacji od modelu przez kontroler po widok tak, jakby to robił zwykły użytkownik oraz “darmowa” dokumentacja aplikacji w formie przykładów użycia.

(Jeśli ktoś nie jest zaznajomiony z tematem, to polecam “Plain text stories”, a poza tym myślę, że przykład wszystko wyjaśni.)

Webrat natomiast pozwala na intuicyjne zapisanie tego, co robiłby użytkownik korzystający z aplikacji

visit home_path
click_link "Sign up"
fill_in "Email", :with => "good@example.com"
select "Free account"
click_button "Register"

Instalacja

Zakładam, że Merb jak i RSpec jest już zainstalowany ;)

webrat + cucumber

$ sudo gem install webrat cucumber

plugin merb_cucumber

Jeśli ktoś jeszcze nie dodał githuba do źródeł, to:

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

a potem wystarczy:

$ sudo gem install david-merb_cucumber

W katalogu aplikacji:

$ merb-gen cucumber --session-type webrat

Cucumber jest już zainstalowany i gotowy do pracy. Do katalogu aplikacji został dodany folder features, w którym to należy umieścić scenariusze. Warto przejrzeć zawartość tego folderu gdyż zawiera on ładny przykład scenariusza oraz kilka często używanych kroków.

W szczególności spodobał mi się plik features/steps/common_webrat.rb zawierający kroki opisujące działania wykonywane za pomocą webrata.

Może jakiś przykład?

Oto i on. Posłużę się kawałkiem obecnie rozwijanej przeze mnie aplikacji. Podaje kod modelu tylko po to, aby łatwiej było zrozumieć co się dzieje ;)

class Site
  include DataMapper::Resource

  property :id, Serial

  property :name, String, :nullable => false
  property :domain, String, :nullable => false
  property :description, Text
  property :keywords, String

end

Kontroler oraz widoki praktycznie nie różnią się od tych wygenerowanych przez merb-gen resource - standard.

Scenariusze grupowane są w pliki “właściwości/cech” (feature). Warto teraz wspomnieć, że cucumber nie narzuca żadnych konwencji nazewnictwa. Ważne tylko aby pliki scenariuszy miały rozszerzenie .feature, a pliki kroków znajdowały się w folderze steps (kroki są oczywiście pisane w Ruby, więc pliki będą kończyły się na .rb) - nazwa nie ma znaczenia.

Na pierwszy ogień pójdzie dodawanie nowej strony. Na początku krótki opis:

Feature: Create site
  To create site
  An admin
  Must fill the form.

Nie ma on większego znaczenia, jednak warto streścić tu zawarte niżej scenariusze.

Zajmijmy się teraz pierwszym przypadkiem - wypełniamy formularz, strona zostaje dodana i jesteśmy przekierowywani do listy wszystkich stron. A tak to wygląda:

Feature: Create site
  To create site
  An admin
  Must fill the form.

  Scenario: Creating new site
    Given no sites exist
    When I go to /sites
    And I follow "Add site"
    And I fill in "name" with "Cucumber"
    And I fill in "domain" with "cucumber.org"
    And I fill in "description" with "I love cucumbers"
    And I press "Add"
    Then I should see an notice message
    And I should see table like
      | Name | Domain |
      | Cucumber | cucumber.org |

Zapisujac scenariusz (razem ze wstępem) jako create_site.feature i uruchamiając bash$ rake features otrzymamy: cucumber pending

Jak widać, otrzymaliśmy ładnie pokolorowany scenariusz. Niebieskie kroki zostały pominięte ze względu na brak kroku dla pierwszej linii scenariusza. Żółty kolor oznacza właśnie, iż linia nie została dopasowana do żadnego kroku. Poniżej znajdują się kawałki kodu które można użyć do implementacji brakujących kroków.

Utwórzmy teraz plik steps/site_steps.rb a w nim:

Given /^no sites exist$/ do
  Site.all.destroy!
end

# ten krok przyda się na później, ale opisze go teraz
Given /^site with name "(.*)" and domain "(.*)" exists$/ do |name, domain|
  Given "no sites exist" # wywołanie kroku znajdującego się powyżej
  Site.create(:name => name, :domain => domain)
end

Then /^I should see table like$/ do |table|
  table.hashes.first.keys.each do |head|
    response.should have_xpath("//table/tr/th[. = '#{head}']")
  end
  table.hashes.each do |hash|
    hash.each_pair do |key, value|
      response.should have_xpath("//table/tr/td[. = '#{value}']")
    end
  end
end

Składnia kroków jest bardzo prosta: Given/When/Then + RegExp + blok opcjonalnie z parametrami. Poprzez zastosowanie wyrażeń regularnych można wykorzystać ten sam krok dla różnych danych co znacznie zmniejsza ilość potrzebnych kroków. Przyczynie się do tego również możliwość wywoływania innych kroków z wewnątrz kroku. Ostatni krok wykorzystuje tabele i selektory XPath do sprawdzenia poprawności strony wynikowej.

Odpalmy testy ponownie. Powinno się pokazać wynik podobny do: cucumber passed

Działa :D

Dodajmy jeszcze jeden scenariusz, tym razem sprawdzający czy aplikacja zachowa się poprawnie w przypadku próby dodania strony o istniejącej już nazwie: (na koniec pliku create_site.feature)

Scenario: Creating site with existing name or domain
    Given site with name "Cucumber" and domain "cucumber.org" exists
    When I go to /sites
    And I follow "Add site"
    And I fill in "name" with "Cucumber"
    And I fill in "domain" with "cucumber.org"
    And I press "Add"
    Then the creating request should fail
    And I should see an error message

Wykorzystamy tu drugi krok, który utworzy w bazie stronę o nazwie “cucumber” i domenie “cucumber.org”.

Po uruchomieniu okaże się, że nasza aplikacja nie przechodzi testu: cucumber fail Aby to naprawić należy nice zmienić model:

property :name, String, :nullable => false, :unique => true
property :domain, String, :nullable => false, :unique => true

I oto co uzyskamy: cucumber passedTadam ;]

Mam nadzieję, że ten krótki tutorial przybliży nieco zagadnienie testowania aplikacji za pomocą scenariuszy. Jeszcze raz polecam wiki cucumbera - można znaleźć tam odpowiedzi na wiele pytań, jak również zbiór przykładów i jeszcze kilku innych. W ostateczności można się o coś zapytać w komentarzach ;)

Na koniec mała ściąga all-in-one

sudo gem install webrat cucumber
gem sources -a http://gems.github.com
sudo gem install david-merb_cucumber
merb-gen cucumber --session-type webrat
rake features

Miłego testowania.

Edit: Dla tych co nie lubią/nie znają angielskiego albo po prostu chcą potem pokazać scenariusze komuś kto nie zna tego języka jest specjalna opcja. Cucumber pozwala tworzyć scenariusze w dowolnym języku, np. polskim*.

* - Może jutro to opisze, dzisiaj już nie mam siły myśleć.

EDIT: Autotest

Aby features uruchamiały się automatycznie wystarczy zainstalować najnowszą wersje merb_cucumber (sudo gem install david-merb_cucumber), uruchomić merb-gen cucumber w katalogu aplikacji oraz dodać do pliku cucumber.yml linijke z profilem autotest i opcjami np. takimi: {geshi lang=“yaml”}autotest: -r features –format pretty features{/geshi}

Opcja -r features automatycznie ładuje wszystkie pliki z katalogu features. Teraz wystarczy już tylko uruchomić autospec w katalogu aplikacji. (Działa pod rspec 1.1.11)


Looking for comments section?

Send me an email instead to teamon@me.com