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.)
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.
Cucumber
Five things you need to know about Cucumber:- Cucumber tests are located on a
features
folder that have plain text files with a.feature
extension and written in Gherkin. - 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. - 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. - 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. - The configuration file is in the
support
folder and calledenv.rb
. This is automatically generated by cucumber-sinatra below.
Side Note: You can use escaped (w/ a backslash) Markdown inside your Gherkin (feature files).
Example:
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"
Cucumber-Sinatra
Whatcucumber-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.|-->features
-- 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
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).Capybara allows you to run your Sinatra application in two ways:
- Fast one directly through Rack (which doesn’t run the javascript on your page).
- Slower one through a headless browser using Selenium by default.
@javascript
tag. Example:
@javascript
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|
page.has_no_xpath?('.//html[@lang="#{language_set}"]')
end
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) justrequrie 'capybara'
and theninclude Capybara::DSL
and you should have access to all the Capybara objects. (tryCapybara.pry
in pry.)
Rspec
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.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')
end
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) justrequire 'rspec'
andinclude 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 Capybara.app = MyAppClassName
to this long as thing below.Capybara.app = eval "Rack::Builder.new {( " + File.read(File.dirname(__FILE__) +
'/../../config.ru') + "\n )}"
— David
Comments
Post a Comment