Thursday, June 20, 2013

Testing with Cucumber, Sinatra and Capybara

Everything you need to know

There are many elements you need to simultaneously learn to do effective testing of your code. Because some of these elements are very simple a lot of explanations just jump over what you need to know and give them up as obvious. Let’s start with a list of the things you need to learn:
  • Gherkin (the language of Cucumber) ——> super easy
  • Capybara (the DSL that controls the browser tests)
  • Rspec (the DSL in which the actual pass/fail tests are written.)
None of these are hard. But having to learn all at the same time can seem daunting. But it’s not! It’s easy peasy but takes time. :-/

It took me three days to get a handle on this. And I hope by reading this you’ll get a handle on it much much quicker.

Let’s start with Cucumber first.
image of a cucumber by Mgmoscatello via Wikimedia commons


Five things you need to know about Cucumber:
  1. Cucumber tests are located on a features folder that have plain text files with a .feature extension and written in Gherkin.
  2. The .feature files contain tests that have a (1.)Feature: heading that explains what feature of the program you’re testing, with a (2.)Senario: tag that follows it and the Scenario uses keywords (3. Given, When, Then, And, But, *) to define the steps you go through testing your scenario. These keywords aren’t important (they can all be substituted by *) they just refer to how they’re defined.
  3. Because when you run your features files with cucumber features/testname.feature the output will give you exactly what you need to put on your step definition file to actually create the tests.
  4. The feature files refer to files in the step_definitions directory. These are plain ruby files that have the same name as the feature files but with _step.rb at the end instead of .feature extension. This is where you can paste the output of the previous command.
  5. The configuration file is in the support folder and called env.rb. This is automatically generated by cucumber-sinatra below.
That’s pretty much it for Cucumber. The step definitions have regex that matches the steps and a block that runs some code that actually creates the test but that’s all Ruby.
Side Note: You can use escaped (w/ a backslash) Markdown inside your Gherkin (feature files).


Feature: Switch Languages
  As a user of the website I want to be able to switch languages (English and Spanish).

Scenario: Spanish links work
  Given I am on "es/home"
  When I follow "contáctenos" within ".footer"  
  And I should see "Contáctenos:" within ".yielded"


What cucumber-sinatra does is set all the config files for your Sinatra Application to run cucumber with capybara and rspec and creates a sample step file with common step definitions such as When I got to "page-name". These are placed in the file called web_steps in the step-definitions directory inside the features directory.

     -- feature files (.feature)  
     |--> step-definitions directory.  
       -- web_steps.rb  
     |--> support directory  
       -- env.rb  
       -- paths.rb

In order for this to work cucumber-sinatra needs to know the class name of your modular Sinatra App and the path to the ruby file where it’s located.
To generate these files you do:

cucumber-sinatra init  MyAppClassName app/main.rb


Capybara is a DSL for interacting with a web application. It’s the successor of a previous tool called ‘WebRat’ (hence the name, a capybara is a large cute rodent).
image of a capybara from wikipedia
Capybara allows you to run your Sinatra application in two ways:
  1. Fast one directly through Rack (which doesn’t run the javascript on your page).
  2. Slower one through a headless browser using Selenium by default.
To run the second one all you need to do is preface your scenario with a @javascript tag.


Scenario: Switch to English Language
  Given I am on "es/home"
  And I should see "Servicios en Línea" within "#prTopBanner"
  When I press "English" within ".footer"
  Then The page redraws in English 
  And I should see "Online Services" within "#prTopBanner"
  And The html lang attribute is not "es"

Almost all the steps above are already generated by cucumber-rails. I use the @javascript tag because the language switching is done by javascript on the page. The only step I had to define was “Then The page redraws in English” & “And the html lang attribute is not ‘es’”. Since the first one of those is mainly rspec I’ll discuss that one later.

Then(/^The html lang attribute is not "(.*?)"$/) do |language_set|

This step is defined in Ruby with a regex (regular expression) that feeds whatever is within the ” ” quotes to a block as a variable I called language_set. Then I call on a Capybara Query (page.has_no_xpath?) to check that hat the html language attribute is not set to the variable supplied in quotes.

Capybara provides a rich DSL for interacting with the page elements. I found an excellent cheat sheet here. It’s pretty straight forward and relies on many of the same CSS matching elements as JQuery does. But most tests will require more than just a quick check of something with Capybara, Capybara typically just gets you the elements you need to test, and you test those expectations with rspec.
Side Note: To explore Capybara inside pry (or irb) just requrie 'capybara' and then include Capybara::DSL and you should have access to all the Capybara objects. (try Capybara.pry in pry.)


Rspec is another DSL for writing tests. Not just cucumber tests but any kind of test from unit to acceptance. Rspec looks super complicated to me, but that’s mostly because the old syntax (should) tries to be so much like a natural English sentence that it becomes harder to understand. I much prefer the new (expect) syntax. So lets look at an example.


Then(/^The page redraws in English$/) do
  current_path = URI.parse(current_url).path
  current_path_first_element = current_path.split('/')[1]
  expect(current_path_first_element).to eq('en')

This example sets a variable to the path using Capybara’s current_url method. Then creates an expectation of what part of that path should be. I find this new syntax much easier to read since the methods are clearly called. Here is a blog post by one of the Rspec maintainers describing the new syntax.

So, what is this testing exactly, anyway? The way the language feature works on the webpage it uses javascript to change the path appending the language tag to the beginning of the page.
Rspec has mocks (fake objects) and other goodies. But to get started all you need is the simple ‘expect(something).to’ syntax. Most cheat sheets include only the old syntax, here is one for the new one. Please note that not_to and to_not are aliases and you can use whichever you like (I prefer the later).
Side Note: To load Rspec on pry (or irb) just require 'rspec' and include Rspec::Matchers.

BDD, Testing and why do testing?

A key element to Behavior-Driven-Development (BDD), that I recently understood, is that the highest-level tests (the so called acceptance tests), the ones that are done in cucumber, are the ones that provide the most value. I’d seen presentations of BDD and TDD (test-driven-development) and many seemed only useful for expert programmers that knew how to solve the problem. But truly the best value tests give you are: documentation, introspection, and coverage for refactoring.

Yet only the third one requires some extensive testing. The first two can be satisfied to some extent solely with cucumber tests. By just looking at the test you know that the language switching occurs in javascript and does something to the path, specifically to the first element of the path. This is valuable knowledge for developers and maintainers looking at your application code.

The second one, introspection, forces you to think about code that is testable, maintainable and can grow. So even a happy-path test that just test what the application is supposed to do, can be very useful. By writing tests after the application was working, I was able to locate bugs that I had missed simply because the test required me to think in a different way and look at the code form a different perspective. For more on BDD you can look at this youtube video.

Testing Middleware

This setup above works very well for Sinatra based apps, but not for Rack Middleware. I’m still figuring how to effectively do cucumber tests for that.
Update: I figure out how to do it. Open the env.rb file and change this line = MyAppClassName to this long as thing below. = eval " {( " + + 
'/../../') + "\n )}"

— David

No comments:

Post a Comment