Building off of my post regarding spiking automated testing solutions, one thing I didn’t really do was show you any sort of effective coding practice there at all. In that post I was showing how to get a series of technologies working together for that old “sense of accomplishment” thing. What I want to do here is focus on using two of those technologies together — specifically, Capybara and RSpec — while also showing you how you can start to separate out bits of logic as you start thinking about how to construct automated test frameworks for testing browser-based applications.
After I’ve done spikes to make sure I understand how something works, I then try to get a little better with one specific aspect of what I spiked so that I can better learn how to utilize the tools. This post will give you some insight into how I go about that process when I have to incorporate my results into my testing work.
When I first heard about RSpec what I heard was that it’s a behavior-driven development (BDD) test framework written in Ruby. That told me very little. I then heard that RSpec is a testing framework that facilitates using a BDD process for Ruby. Okay, but I’m still not sure what I’d do with it. Then I heard that, by itself, RSpec can only test Ruby applications or scripts. This was often followed by lots of stuff that showed how RSpec was incorporated into Rails development. Hmm. That sounded good if I had a Rails application or all of my application was in Ruby. But I just needed to test a user interface in a browser.
Then I came across Capybara. (Well, actually I came across the test framework Watir which then led me to Selenium which then led me to Capybara.) At first I heard that Capybara was a cool thing that was going to replace Webrat (whatever that was) and was going to simplify running integration tests on Rack applications (whatever those were). Maybe that was cool. I had no idea at the time. What kept me looking at Capybara was that I heard it was “driver agnostic” and supported Selenium. Eventually what I realized is that Capybara is a tool to automate navigating through web sites and web applications through a browser. (Maybe it was just my searching, but it took me a surprisingly long bit of time to actually just find out that simple statement, divorced from Webrats, Rack applications, and so forth.)
I should note that there is not, at the time I write this, a Watir adapter/driver for Capybara. From what I’m reading it doesn’t seem like there will be anytime soon.
I ended up in a situation where developers wanted to utilize RSpec for their integration-level tests and were curious if I’d be willing to do the same for system-level tests that required the UI. I had already decided I wanted to try out Capybara so, sure, why not. What I ended up doing is sort of wrapping Capybara within RSpec execution. What does this get me? In terms of actual UI-based testing, not much. In terms of documenting my tests, however, this allows me to wrap the executable portions of my tests around specific RSpec constructs that have the potential of documenting my tests. So I’ll show you what I did using a very simplified example.
For this example, you’ll need Ruby and Rubygems installed on your system. You’ll also need the appropriate gems installed. If you execute the following commands, you should be good:
$ gem install rspec $ gem install selenium-webdriver $ gem install capybara
First, create a project directory called spec. In that directory, create the following files: spike_capybara_spec.rb, spec_helper.rb, and test_helper.rb.
In spike_capybara_spec.rb, start with the following code:
1 2 3 4 5 6 7 |
require 'rspec' require 'capybara' require 'capybara/dsl' require 'spec_helper.rb' Capybara.default_driver = :selenium Capybara.app_host = "http://www.youtube.com" |
Here I’m just requiring the necessary gems and specifying a few Capybara attributes. Capybara uses a driver to control browsers. If you don’t specify a specific driver, then Capybara will use something called “rack_test” which is (1) specific to Rack applications and (2) doesn’t handle JavaScript. In my case, I’ve changed the driver to Selenium. I do this because Capybara supports Selenium 2.0 (using WebDriver) without any other configuration.
Note for those who know Selenium: Capybara does not support Selenium 1.0, otherwise known as Selenium RC.
I set the app_host because default Capybara operation has the tool assuming it’s testing an in-process Rack application. Specifying an app_host like this means I’m telling Capybara that it’s going to be talking to a web server.
One of the things I want to use is Capybara’s DSL (domain-specific language) that provides easy to reference phrases for interacting with a web browser and the elements on a web page. Beyond requiring the gem, as I did above, I need to tell RSpec to include this Capybara DSL so my tests — which are going to be wrapped in RSpec constructs — will be able to use the reference phrases (methods, basically) that the Capybara DSL provides.
Now, this gets a little into RSpec setup here. Sometimes you’re going to have some generic code you want to run before or after every single one of your tests in RSpec. This generic code can be defined in an RSpec configuration. This configuration typically lives in a spec_helper.rb file that is required from other files.
In the spec_helper.rb file, put the following code in place:
1 2 3 |
RSpec.configure do |config| config.include Capybara::DSL end |
Now back in the spike_capybara_spec.rb file, put the following code in:
1 2 3 4 |
describe "Searching for a video" do it "allows searches for general terms" do end end |
Now try running the following command from outside of your spec directory:
$ rspec spec --format documentation
You’ll see that RSpec tosses back just the information in the file that pertains to the nature of the test. The RSpec descriptions of what you are testing go in a ‘describe’ method. You put the actual behavior you will be testing in an ‘it’ method. The nice thing about this is that the overall logic of your test reads like a series of English statements:
Searching for a video allows searches for general terms
While the string for the ‘describe’ method describes an overall feature, the string for the ‘it’ method should describe the scenario you’re working to test. What you now have to do is put a code block between the ‘do’ and ‘end’ that includes the code for the actual test. This is where you tell your test what to do with the browser and the pages that appear in it.
Now modify the logic of the test logic so that it looks like this:
1 2 3 4 5 6 7 8 |
describe "Searching for a video" do it "allows searches for general terms" do visit "" fill_in('search_query', :with => "text adventures") click_button('search-btn') page.should have_content("GET LAMP: The Text Adventure Documentary") end end |
Run this test with the following command (again, outside of your spec directory):
$ rspec spec
Incidentally, if you want to generate a friendly report of your test execution, execute this command instead:
$ rspec spec --format html --out results.html
The behavioral part of the test contains lots of implementation details (such as “fill_in”, “click_button”). That may or may not be bad depending upon your viewpoint. Let’s assume you think that’s bad. So, in the test_helper.rb file, put the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
module TestHelper def visit_youtube visit "http://www.youtube.com" end def search_for_term(text) fill_in('search_query', :with => text) click_button('search-btn') end def verify_I_see(text) page.should have_content(text) end end |
Here I’m just creating some methods with friendly names that are meant to be as specific as possible in terms of what that method does. In order to utilize these test helper methods, I need to make sure I include the TestHelper module in my RSpec configuration. This requires adding just two lines to the spec_helper.rb file:
1 2 3 4 5 6 |
require 'test_helper' RSpec.configure do |config| config.include Capybara::DSL config.include TestHelper end |
With these changes in place, I can now change the test execution in spike_capybara_spec.rb to read like this:
1 2 3 4 5 6 7 |
describe "Searching for a video" do it "allows searches for general terms" do visit_youtube search_for_term("text adventures") verify_I_see("GET LAMP: The Text Adventure Documentary") end end |
Functionally, I’m doing the same exact thing I was before. The difference is in how I’ve structured my behavioral logic to make it a little more generic. In truth, I’m still really tied to whatever site I’m testing. As just one example, look at the search_for_term method in test_helper.rb. This method contains literal identifiers (‘search_query’ and ‘search-btn’) that are very specific to YouTube. A better approach might be to have that information passed in to the method. But let’s think about that. Let’s say I changed the method to look like this:
1 2 3 4 |
def search_for_term(text, widget_id_1, widget_id_2) fill_in(element1, :with => text) click_button(element2) end |
That would mean I could make the following call from my test (in spike_capybara_spec.rb):
1 |
search_for_term("text adventures", "search_query", "search-btn") |
Can I get a show of hands for those who think this is awful? Not only does this take away the readability of my test but it still ties me to the implementation since the search_for_term assumes that I’m going to fill in one field and then click one button. Maybe that’s true in many cases, but what if there are multiple text fields to fill in? Or what if there is a drop-down to select categories of things to be searched on? Clearly I haven’t done myself any favors here. Okay, so what if I made an even more specific method in test_helper.rb:
1 2 3 4 5 |
def search_for_youtube_videos_about(text) visit "http://www.youtube.com" fill_in('search_query', :with => text) click_button('search-btn') end |
Note the change in name of the method as well as the addition of the visit call. That means my test logic in spike_capybara_spec.rb can now be this:
1 2 3 4 5 6 |
describe "Searching for a video" do it "allows searches for general terms" do search_for_youtube_videos_about("text adventures") verify_I_see("GET LAMP: The Text Adventure Documentary") end end |
Notice how this lets me be more specific about my intent in the test and also lets me incorporate more YouTube-specific logic in the method being called that executes the test, such as that visit part. (This would mean I don’t need the visit_youtube method in test_helper.rb.) If I later had a specific method for, say, Amazon then I could have a “search_for_books_titled” method or something like that.
Here’s something else to consider. Look at the specification-level statements of RSpec:
1 2 3 |
describe "Searching for a video" do it "allows searches for general terms" do ... |
Reading those statements, they sort of match the exact information of the test method I wrote:
1 |
search_for_youtube_videos_about("text adventures") |
I’m kind of saying the same thing over again in the method that I said in the RSpec statements. I’m saying it once as a specification and then again as part of a method naming scheme. You might wonder if the two could be combined in some sense, such that the natural language parts are, in fact, the executable code. That’s the basis of using a tool like Cucumber, which I’ll be covering in some other posts.
Clearly I’ve just scratched the surface here, not only in terms of two specific solutions (RSpec and Capybara) but also in terms of writing good automation test logic. My goal in taking you through this was to give you an idea of how you might start thinking about a framework to your testing. You can see here a clear separation of files that hold different bits of logic, the idea of separating intent from implementation, and the idea of how to frame tests so that they communicate their business actions at a high-level while relegating lower-level implementation details to a different area. I’ve found these aspects to be the thinking battles I engage in most when I’m considering how to build effective and efficient automated testing frameworks.
Thanks for the great, easy-to-understand articles. I just tweeted two of your articles.
Thank you for the excellent tutorial. It is the best I have found for an introduction on this topic.
Brilliant resources! I had the same issues as you stated at the beginning of this post (going from one terminology loop to another, not finding a clear and simple starting point anywhere). I’m fairly new to test automation, Ruby, Rails, and scripting, so your posts on this blog have been an excellent and clearly-written resource for me. Thanks a billion, and keep up the good work!
Newer versions of RSpec also now support the feature scenario syntax from Cucumber.
This means acceptance testing specs can now be visible different from unit testing specs, rather that relying on file naming & directory naming conversions.
You are absolutely correct on this and I appreciate you bringing it up. One thing I want to do is re-explore some aspects of RSpec in this blog because it is a tool that is continually being added to.
Exactly what I was looking for, testing a website without the context of Rack, Rails etc and without the context of all the other tools I have no interest in.
Great article, thank you!