Capybara or Die

In my last post I discussed the importance of RSpec and test-driven development (TDD) in evaluating Ruby code at the controller and model levels. We write tests to refactor our code, to prevent human error when developing sexy, dynamic web applications, to draft products for white labeling, and to offer guidance to future developers and collaborators of our code.

TDD is great because it ensures our models and controllers align with the overarching goals of our application — that these components communicate with each other and perform the necessary functions to transform our sexy app into a full-blown religious experience for our end user. (Yes – I said it).

But why, then, is testing such a laborious task for programmers? Like doing laundry, testing seems to evoke either pious adoration — something resembling Christianity in 17th c. New England — or vehement hisses to writing even the smallest of tests.

Before elaborating on the above — and the debate surrounding whether TDD has merit at all in the real world — I propose first an exercise in shoshin. Just go with me here.

Throw everything you know about testing in the metaphorical garburator. Wipe those Foucault college midterms and impromptu coding quizzes — thank you Flatiron! — from memory. Relax and get comfortable with TDD; heck, order-in a 12″ pie from Roberta’s and spend this Friday getting to know her better. She’s not going anywhere.

It’s important to understand testing isn’t magic — it’s not the wingardium leviosa! of spells — enabling your program to somehow run on your command. When we write tests, we are in the driver’s seat: we control our application’s engine. We think of testing as the Sparknotes of our application, offering a bit of guidance to our future self as we refactor — and to future developers who fork our code and use our Specs as Cliffnotes under the hood. As we know from High School: everybody loves Sparknotes.

A Brief Ethnographic Survey of Testing

Exploring this testing philosophy a bit deeper, I began conducting a little ethnographic surveying of my own. Reaching out to a few developer friends beyond the walls of Flatiron, I sought the hard-boiled truth to testing. Who tests? Is testing a thing? Is what I’m learning actually important to the end goal of being a ferocious femme programmer? Give me the sparknotes answer to my sparknotes metaphor!

What I discovered was astonishing: testing, it seems, is a pretty controversial topic, meriting a kind of Jerry Springer retaliation from all sides of the conversation. Never before had I encountered a topic so closely resembling an episode of Maury. And interestingly, everyone offered their opinion to why testing was either so good or so bad. From one side: TDD or die. From the other: “TDD is hocus — criminal!” – this guy.

Wow.

Different Applications of Testing 

As we venture deeper into full-stack development at Flatiron, I began to wonder how we might test user interaction across applications. After all, is not our user the end-goal of everything we do as technophile humanists? Alas — how do we know our user is interacting with our application in the way s/he is supposed to?

Capybara 

Enter Capybara. Capybara is a gem that gives us a bunch of methods that simulates how a user interacts with our Rails application. Capybara talks with many different drivers to execute our tests through the same clean and simple interface. Capybara is sometimes referred to as End-To-End testing because it tests higher-level tests while flexing the entire stack of our application (our Models, Views and Controllers).

RSpec and Capybara

This left me questioning the difference between RSpec and Capybara. Behold another metaphor: imagine RSpec as that austere, extremely-French, French teacher you had in Grade 10. Yes – that one, who quizzed you on verb conjugations, keeling over as you occasionally forgot the translations to seminal cultural tokens like aujourd’hui, maman est morte.” By contrast, Capybara is that gym teacher who once convinced you that line-dancing was an appropriate way to spend an hour of your time. The image of her slapping her hands together as I fell out of electric slide formation will be forever imprinted in memory.

Whereas RSpec tests how models and controllers communicate with one another in various ways — the conjugation of verbs and their correct usage in french sentences — Capybara tests how users interact with the pages of your Rails app. Capybara methods, like clicks on a page, form interaction, finding elements on a page, and the electric slide, are examples of this. OK, maybe not the electric slide, but Capybara gives us a ton of methods to help us test really important user interactions. Peeps the Capybara cheat sheet here.

capy
Key Benefits of Capybara
(from the Capybara API)

  • No setup necessary for Rails and Rack application. Works out of the box.
  • Intuitive API which mimics the language an actual user would use.
  • Switch the backend your tests run against from fast headless mode to an actual browser with no changes to your tests.
  • Powerful synchronization features mean you never have to manually wait for asynchronous processes to complete.

To run Capybara, we must include it in the Gemfile of our rails application.

group :development, :test do
 gem "rspec-rails"
 gem "capybara"
end

We group RSpec and Capybara under the development testing suite of our application because we are testing user interaction. To write our Capybara tests, we hard code sample user data to populate into our forms. By this measure, we can then test the functionality of our application.

In our testing environment, we need to first load RSpec and Capybara, configure RSpec, include Capybara in our Rails app, define the application we are testing, load the application defined in config.ru, and  finally configure Capybara to test against the application we are testing. In our spec/spec_helper.rb we add the following code (as taken from Learn):

# Load RSpec and Capybara
require 'rspec'
require 'capybara/rspec'
require 'capybara/dsl'
# Configure RSpec
RSpec.configure do |config|
# Mixin the Capybara functionality into Rspec
 config.include Capybara::DSL
 config.order = 'default'
end

# Define the application we're testing
def app
# Load the application defined in config.ru
 Rack::Builder.parse_file('config.ru').first
end
 
# Configure Capybara to test against the application above.
Capybara.app = app

For a more comprehensive walk-through of testing with RSpec and Capybara, check out the video below.

 

Resources

Capybara API

Capybara Cheat Sheet (from SmallFry GitHub).

How We Test Rails Applications by John Steiner

Automated Testing with Cucumber and Capybara

Ruby For Newbies – Testing in Capybara

Forums 

Google Forum on Capybara

Reddit Forum on Testing

Y Combinator Forum on TDD

RSpec and Test-Driven Development in Ruby

Big Idea

The RSpec Ruby gem is one of the best testing libraries for testing Ruby code. Testing your code is very important: in fact, tests are essential in setting the parameters of what your program is all about. Imagine tests as the guideposts and scaffolding of your code. We write tests as we build out our code to ensure our models align with the goals of our program.

This post takes a leisurely stroll through the process of testing your code with RSpec.

Setting Up RSpec

The first step in test-driven development (TDD) is to configure RSpec as a dependency via Bundler. Create a new directory in your project and title this one “Gemfile.” Within it type the following code.

# Gemfile
source "https://rubygems.org"
gem "rspec"
gem "pry"

Open this project’s directory in your terminal and type bundle install –path .bundle. This installs the latest version of RSpec and all related dependencies. You’ll see something that resembles the following:

Screen Shot 2016-02-23 at 5.43.21 PM

Perfect! You have now installed your test suite with the RSpec and Pry gems. Both gems are necessary for helping us build out our project.

Now we are ready to build a small project. This project will have two classes: a Song and Library class. Instances of the Song class (each new song object) contain attributes: a name, an artist, and a genre — just as every song belongs to an artist and a genre. Our Library Class stores a library of music — of song objects — can save these songs to a file and allow the user to query them by their name and genre.

This is what our project directory should look like:

In our program we write the specifications (the “specs”) in a spec directory. For each class file we have a corresponding spec_helper.rb file. For our specs to run we need to “require” the associative Ruby classes we are testing. We declare this in the spec_helper file like so:

require_relative "../itunes_library.rb"
require_relative "../song"
require "yaml"

Think of require_relative as “require”: here, instead of scanning your Ruby path it searches for the relative path to your current directory.

For the purpose of this example we will be requiring YAML. YAML is a very basic text database we will use to store our program data.

The Song Class

Now we can start building out our Song class. Remember to require “spec_helper” at the top of our file to see what’s going on under the hood of our method.

require "spec_helper"
   describe Song do 
end

We start our spec_song.rb with a describe block which introduces what we are testing: this could be a string, but in this example we are using the class Song name.

before :each do 
   @song = Song.new "Name", "Artist", :genre
end

We begin by calling “before”, passing the symbol iterator :each to declare that we want to run this code before each test. And what are we doing before each test? We are generating new instances of Song according to attributes “Name”, “Artist” and “:genre”. Note the syntax of these attributes: whereas name and artist are strings :genre references something different.

When we write specs we reference instance variable names with the @symbol. We do this to ensure our instance variable is accessible across our tests; if we don’t use “@” we can only access this local variable within each individual code block (between the before and end of this code block).

Now we can create our first test which describes the actions of a specific method we are testing. As we write out our first test we are testing to see if our initialize method is making a Song object.

describe "#initialize" do
   it "takes three attributes and returns a Song object" do
     expect(@song).to be_a(Song)
   end
end

It’s convention in test-driven development to include a “describe” block for our test. We use the hashtag symbol “#” to refer to instance methods like this: ClassName#methodName. We write the class name in the top level of our block to describe and include the method name which we want to reference.

Take a look at the syntax in the above example. We use expect(object).to do_something. This form will account for 9.9 out of 10 tests you write: you have an object, you expect it do something, then you pass that object the call of another function. This reads well and makes for a fairly intuitive test. Above, as we are testing the instance of @song, we reference this variable with the @ symbol.

Let’s run this. In TTD we run our tests in our terminal with rspec spec. Remember: the spec is the directory we created to hold our method tests.

Here’s a problem: when we run this program we encounter an error: “uninitialized constant Object::Song.” This means there is no Song class! Let’s add this now.

In TDD we only write enough code to fix the problem at hand. It is important in RSpec to write the smallest possible test case matching what we need to program in our Class.rb file (Andrew Burgess, “Ruby For Newbies“).

Moving from our spec/song.rb to our song.rb file, we write our code for the Song class:

class Song
end

Re-run our tests and you see we are failing them. Here’s why: we are lacking an initialize method with three arguments (to initialize a new instance of song for our song class) so calling #initialize has no effect on our program. Typically, we follow the process of writing a test (or tests), testing these tests, seeing these tests fail, making these tests pass, refactoring our code, and repeating this process until we are done.

Onto more tests for our Song:

describe "#title" do
   it "returns the correct song title" do
     expect(@song.title).to eq("Title")
    end
 end

 describe "#artist" do
   it "returns the correct artist" do
     expect(@song.artist).to eq("Artist")
    end
 end

 describe "#genre" do
   it "returns the correct genre" do
      expect(@song.genre).to eq(:genre)
    end
  end
end

This logic is straightforward: as you read the above tests you should know exactly what they are doing and referencing.

But run your tests again and you see they are failing! This is because in our Song class we have to initialize new instances of Song with the attributes outlined above: :title, :artist, and :genre. Let’s fix this now in our Song.rb file.

class Song
  attr_accessor: :title, :artist, :genre
   def initialize(title, artist, genre)
      @title = title
      @artist = artist
      @genre = genre
   end
 end

We pass in three arguments to our Song class which initializes new instances of Song with a title, an artist, and a category. Run our tests now and they pass.

You may notice although our tests pass we are not able to track our progress within our test suite. Luckily we can fix this by customizing the way RSpec runs by creating a “.rspec” file in our main directory.


Run these tests again and you’ll see a few colorful features added to your testing suite!

RSpecing the Library Class

This is slightly more complicated and involves some hard coding. Let’s build out a new instances of song in our Library object.

require "spec_helper"
require "pry"
describe "Library" do
   before :all do
      lib_obj = [
         Song.new("The End", "The Doors", :classic_rock),
         Song.new("Oops I did it Again", "Britney Spears", :pop),
         Song.new("Blackstar", "David Bowie", :glam_rock),
         Song.new("Save Me", "Queen", :glam_rock),
         Song.new("Strange (I've Seen That Face Before)", "Grace Jones", :grace_jones)
         ]
      File.open "songs.yml", "w" do |f| 
         f.write YAML::dump lib_obj
       end
       # This dumps the library object into new YML file.
       end
    before :each do
        @lib = Library.new "songs.yml"
     end
  end

We use two :before blocks: one for :each and one for :all. In the before :all code block we hard code some of our data to use in our tests. Here I have created an array of Songs, each with a corresponding genre, title, and artist. When we run our program we will open the file “songs.yml” in “w” write mode and use YAML to dump this data as an array into this file.

You’ll notice when we run rspec spec in our terminal, YAML zips up this data and dumps it into a new “songs.yml” file in our main directory. Check it out:


YAML is “is a human friendly data serialization standard for all programming languages.” Essentially, a text-based database kind of like JSON. We import our YAML in our spec_helper.rb by requiring it with our other gems.

In our example we use two commands: dump, which spits out serialized data into a string, and load which takes the data string and converts it back into Ruby objects. The YAML load syntax looks something like this:

 def initialize(lib_file = false)
     @lib_file = lib_file
     @songs = @lib_file ? YAML::load(File.read(@lib_file)) : []
   # YAML syntax to initialize text file with song data.
 end

So far we have created a file with some data we’ve hard coded. Perfect. Before :each test we create a new Library object, passing it the YAML file name. We write our corresponding tests for this:

describe "#initialize" do
   context "initialize Library with no parameters" do
      it "Library has no songs" do
      lib = Library.new
     expect(lib.songs).to eq([])
   end
 end
   context "Initialize Library with a YAML file parameter" do
      it "Library has five songs" do
      @lib.songs.length == 5
    end
  end
end
describe "#get_songs_in_genre" do
      it "returns all the songs in a given genre" do
      @lib.get_songs_in_genre(:classic_rock).length == 2
   end
 end
describe "#add_song" do
    it "accepts new songs and adds to library" do
    @lib.add_song(Song.new("A Jealous Heart Can Never Rest", 
     "The Black Madonna", :techno))
   end
 end
end

We start these tests with a describe block for the #initializeLibrary method. Notice above new code block: context, which specifies a particular testing situation for our #initialize method. Here we spell out two contexts: “Initialize Library with no parameters” and “Initialize Library with a YAML file parameter.” In the former we are testing a Library object at initialization without any data. What does this look like? Well, an empty array of course! Thus we write:

expect(lib.songs).to eq([])

When we initialize with a YAML file we expect .length of Library to equal 5 because we hard coded 5 songs in our lib_obj. Run these tests and you’ll see they are passing for this particular texting context.

Naturally, we’d like to build out the functionality of our program. Let’s say we want our program to add a new instance of Song to our Library object — or query a song by its title.  Let’s write these methods in our Library.rb file:

class Library
  attr_accessor :songs
 def initialize(lib_file = false)
 @lib_file = lib_file
    @songs = @lib_file ? YAML::load(File.read(@lib_file)) : []
    # YAML syntax to initialize text file with song data.
  end

  def get_songs_in_genre(genre)
     @songs.select do |song|
     song.genre == genre
   end
 end

  def add_song(song)
   @songs.push song
  end

  def get_song(title)
    @songs.select do |song|
   song.title == title
  end.first
 end
   [...]

Once we are happy with the functionality of our program we stop. Of course we can continue writing methods and their tests ad infinitum — until our program does what we want it to do.

Let’s stop here and run our tests.


Well looky there: our tests pass!

RSpec is sometimes described as a Domain-Specific Language (DSL) because it describes the behaviour of objects. As programmers we write our tests first because we need to know what to test before learning how to test (Build Awesome Command Line Applications in Ruby: 143).

Moral of the story: learn to love RSpec.

Fork my repo here.