public
Last active

Week One of Ruby on Rails: RSpec Gripe #1

  • Download Gist
gistfile1.md
Markdown

This will either be the first of a series of posts on "how I became a Ruby convert" or "why Ruby is the worst language on the planet."

I've spent the last two years of my life working with Python and its popular web frameworks (Django, webapp2, and Flask). Python was the first language I ever learned, so I'm admittedly a bit biased toward the "Pythonic" way of doing things. Python favors readability of code, but it also favors exactitude. I can push bits around in memory with the same ease as a GET request. The entire community feels like it's made up of scientists who want to support that - so libraries follow the Pythonic way of doing things. Boolean tests are written the same way in each library as they are in the core Python language.

So you can imagine my frustration when I start working with Ruby on Rails... and specifically, RSpec.

Let's take a look at a webapp2 test:

import unittest
import webapp2
import main # assuming that 'main' is the name of our application

class TestHandler(unittest.TestCase):
    def test_hello(self):
        request = webapp2.Request.blank('/')
        response = request.get_response(main.app)

        self.assertEqual(response.status_int, 200)

        #assuming that page should return Hello, world!
        self.assertEqual(response.body, 'Hello, world!')

See how nice that looks? I've got a Test Handler, which hits my main page with a GET request. The response is called by get_response. Then I assert that the response has a 200 status code and a body of 'Hello, world!' This is simple; it is exact. It looks like a computer science project, but you also have no doubts about what you're getting.

Let's look at this in Ruby. If I want to write a simple Boolean check, similar to the above, I could do this:

require 'test/unit'
require 'net/http'

class TestHandler < Test::Unit::TestCase
    def test_hello
        request = Net::HTTP.get('localhost', '/')
        response = http.request request

        assert_equal response.code, 200
        assert_equal response.message, 'Hello, world!'
    end
end

I can work with this. The double colon syntax is a little weird, but I could get used to that. Now, coming from Python... I expect that I will write unit tests in a similar manner, even if I fire up another testing framework. RSpec can't be that bad, right?

FALSE.

I'm following a very nice walkthrough over at RailsApps that takes the reader through the construction of a Ruby on Rails app. I selected one that makes use of Devise for user authentication, RSpec for unit testing and Cucumber for behavior testing. I say to myself "oh, I have heard some things about this RSpec, let's see how cool it is."

This is a test for index.html in RSpec:

require 'spec_helper'

describe HomeController do

  describe "GET 'index'" do
    it "should be successful" do
      get 'index'
      response.should be_success
    end
  end

end

WHAT THE HELL IS THIS.

This is some half Ruby core/half RSpec crap that I'm seeing in front of me. All I have to do is require spec_helper and suddenly I can just screw up the entire vibe of my code. This does not make me happy. Now I've got to deal with this hipster code where it "should be successful" is a valid Boolean test. Per the documentation: "RSpec uses the words 'describe' and 'it' so we can express concepts like a conversation. But that doesn't appear to be how the core of Ruby works - meaning that I have no idea when I'm supposed to be writing this pseudocode or writing actual Ruby.

Perhaps I'm off base. Maybe, once I get over the learning curve, this method of striving for natural language will make sense. But right now, I've encountered my first core gem that is just jacking my entire understanding of the language's flow around - and I've only been dealing with Ruby for a week or so. I can only hope it won't be this way for the rest of my learning experience.

Don't let the internet fool you into thinking RSpec and Cucumber are the one true path for Rails testing. I thought that as well when I was starting out, but after interacting with other developers in my community, I'd say we're split something like 50/50 between RSpec and Test::Unit.

If you really want to see controversy, check out DHH's tweet heard round the (Rails)world: http://www.rubyinside.com/dhh-offended-by-rspec-debate-4610.html. You don't have to buy into the cult of vaguely natural language obfuscated pseudo code if you don't want to.

Don't write controller specs. Write request specs which test controller behavior.

describe "Hello World Page" do 
  describe "/" do 
    it "should display 'hello world'" do
      get "/"
      response.status.should == 200
      response.body.should == "Hello World"
    end
  end
end

It might be a bit easier for your brain if you think of specs not as "Ruby-language scripts", but as "RSpec-language scripts", a language which is a superset of Ruby.

RSpec is a DSL — domain-specific language — on top of Ruby. It purposely pollutes the top-level namespace with shorthand methods so that you don't have to qualify them. Yes, the range of what is available is essentially something you will need to hunt down in the documentation. And yes, the available shorthands are also dependent on what RSpec extensions you have installed; things like WebMock extend the namespace with its own methods.

After the confusion settles I think you will find that the reduced syntax is very helpful. It removes boilerplate to a large extent. In a more verbose test framework such as Test::Unit, developers always move common boilerplate (eg., test setup, common mocks) into test-wide helpers. RSpec just sets the table in a way that also reduces common test boilerplate.

For example, surely describe Foo is better than class FooTest < Test::Unit::TestCase — even if you don't understand what describe really does or where it comes from. You don't need to if you accept it's RSpec syntax.

And surely you will agree that this:

get "/"
last_response.status.should eq 200

...more concisely expresses the intent of the test than this:

request = Net::HTTP.get('localhost', '/')
response = http.request request
assert_equal response.code, 200

I personally quite like writing assertions as infix-style expressions (x.should y) rather than assertions (assert_equal). One reason is the wonderful flexibility in choosing what goes on the left and right sides:

lambda {
  client.connect!
}.should_not raise_error(Client::ConnectionError)

Or:

stub = stub_request(:get, '/template').with({
  query: hash_including(name: 'signup'),
  headers: {
    'Content-Type' => 'application/json'
  }
})
get "/signup"
stub.should have_been_requested

Python is like a math equation. Ruby is like english.

To me, ruby is more like speaking out loud.

The description for get index is "When you get index, the response should be a success."

Problems that lend themselves to exact language translation from an equation (AI, scipy) tend to be highly developed in python, and rightly so. It's a great language for that.
The reason so many developers like ruby is it is very easy to translate user stories into code.

Those are different problems so it makes sense to have different solutions.

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.