Tag: Ruby on Rails

« Previously Next Page »

Just a Typical Rails Testing Session

Goal: Find out how fast this code runs.

Solution: Write a performance test using Rails’ built-in script/generate performance_test

Steps:

  1. script/generate performance_test homepage
  2. edit test/performance/homepage_test.rb
  3. Oh wait, I don’t have a test database set up
  4. ssh dev
  5. pg_dump -Fc devdb > db.dump
  6. exit
  7. scp dev:~/db.dump .
  8. Wait for 700Mb compressed dump file to download over wi-fi connection
  9. pg_restore -d testdb db.dump
  10. errors: could not access $libdir/pg_trgm, $libdir/uuid-ossp, $libdir/fuzzystrmatch, tons of no relation errors
  11. Search Google: what is all this stuff?
  12. uuid-ossp depends on http://www.ossp.org/pkg/lib/uuid/
  13. Download, untar, configure, make, make install
  14. Oops, forgot to build with postgres support
  15. GOTO 13 and return
  16. That didn’t work; GOTO 11 and return
  17. Oh, compiling uuid-ossp on Mac has problems: http://cvs.ossp.org/tktview?tn=81
  18. Try running one user’s suggestion of renaming uuid_t
  19. rgrep -l uuid_t | grep -v ChangeLog | xargs perl -i -pe 's/uuid_t/ossp_uuid_t/g'
  20. rgrep: command not found
  21. GOTO 11 and return
  22. sudo port install rgrep (nope)
  23. Is rgrep source available? (nope)
  24. Ah, rgrep is part of the jed text editor
  25. sudo port install jed (installs slang and jed)
  26. GOTO 19, 20, (swear once), 21
  27. What was my goal again?
  28. locate rgrep (nope)
  29. locate jed (nope)
  30. How do you update the locate db again? GOTO 11 and return
  31. sudo /usr/libexec/locate.updatedb (receive warning, “the Lord will kill you for running as root”)
  32. Why am I doing this?
  33. I know, I’ll write a funny post on how great it is to be a programmer and how 90% of your time is debugging your system or your code (because no one’s ever written about that before)
  34. Let’s go over the steps again, start from the beginning
  35. rake test:benchmark (database structure loads with errors, but the test runs; what?!)
  36. “rake aborted: undefined method `use_transactional_fixtures=' for Test::Unit::TestCase:Class"
  37. edit test_helper.rb: replace Test::Unit::TestCase with ActiveSupport::TestCase
  38. rake test:benchmark (it works: “wall_time: 5 ms”)
  39. GOTO 32 and return
  40. Why are memory, objects, gc_runs, and gc_time all zero?
  41. GOTO 11 and return
  42. Oh, I need to patch Ruby with a GC patch
  43. cd /usr/local/src/ruby-1.8.6-p369
  44. curl http://rubyforge.org/tracker/download.php/1814/7062/17676/3291/ruby186gc.patch | patch -p0
  45. “8 out of 28 hunks FAILED”
  46. Does it compile? ./configure; make (error)
  47. Oh well
  48. GOTO 27 and return

How to Post Your Flights to Twitter via OAuth

I just posted a writeup of the new Twitter posting feature on the Jetrecord blog, complete with a short screencast hosted on Vimeo. Hopefully you’ll get a good idea of how the OAuth handshake works from an end user perspective.

Twitter’s OAuth

Earlier today I added Twitter’s OAuth authentication process to Jetrecord, making it possible to post your logged flights to Twitter in the same manner that you can post your location via Brightkite or photos via Twitpic, the difference being that Jetrecord doesn’t store your Twitter password.

The process was surprisingly easy, thanks to the Ruby tutorial on the Twitter API wiki and the documentation from the OAuth gem. No other gems were necessary, other than the dependencies of the OAuth gem. I’ve been using the Twitter4r gem to communicate replies and handle Jetrecord followers, but even that may be unnecessary in the future.

Some day I may post a more detailed writeup with code but I just wanted to report that it was possible.

Here’s one thing not covered in the tutorials which may trip you up but it’s worth getting into your app from the beginning. Make sure you include a workflow for revoking access. What happens if users cancel their accounts on your app or with Twitter or if they just want to revoke privileges from your app? In the world of data portability and transparency, it’s not enough to facilitate the setup process. You’ve got to make it easy to cancel, too.

Thankfully, Twitter makes it really easy on their end to revoke access. The burden is on us to match that ease of use.

Cheers!

Nautical Miles Added to Geokit Gem

I submitted a patch to the geokit gem to add nautical miles and Andre merged it into version 1.2.1. This means the geokit gem is now suitable for use in marine and aviation applications where nautical miles are the default unit for distance.

I’m already using it in Jetrecord for distance calculations on routes. Give it a go!

The Building of Jetrecord: Episode 4: Cucumbers and Webrats!

The message here is that with automated tests, we don’t have to worry about code being fragile. We can try out some stuff we think might work, and test it in seconds. We can be more courageous with our coding, and not have to code with cotton gloves on. — Test Driven Development

In the last episode I started my Rails code, committed it to a Git repository, and released my first deployment with Capistrano. In this episode I’m going to start my first feature by writing some tests and then implementing the code.

Cue film.

Download (right-click and save) QuickTime MOV (72 Mb) | QuickTime iPod MP4 (19 Mb) | QuickTime SMIL with captions (72 Mb) [Note: you must choose to open the SMIL file with QuickTime]

Adding RSpec, Cucumber, and Webrat to our Rails App

With the RSpec-Rails, Cucumber, and Webrat gems installed, let’s start a new branch in our Git repository and set up our app for testing:

$> git branch cucumber
$> git checkout cucumber
$> script/generate cucumber
$> script/generate rspec

Now edit RAILS_ROOT/features/support/env.rb. This file was created when we ran script/generate cucumber. We need to change a few lines. Here is what my env.rb file looks like after editing.

# Sets up the Rails environment for Cucumber
ENV["RAILS_ENV"] = "test"
require File.expand_path(File.dirname(__FILE__) + '/../../config/environment')
require 'cucumber/rails/world'
require 'cucumber/formatters/unicode' # Comment out this line if you don't want Cucumber Unicode support
Cucumber::Rails.use_transactional_fixtures
 
# Comment out the next two lines if you're not using RSpec's matchers (should / should_not) in your steps.
require 'webrat/rails'
require 'cucumber/rails/rspec'

We can add autotesting capability by installing the ZenTest gem. Autotest re-runs our tests after every code change, greatly speeding up our testing process. After getting the gem installed, we need to set some global variables so autotest knows to run our RSpec and Cucumber tests. Open up your .bash_profile file (usually located in your user’s home directory on your system). Add the following lines to the bottom of the file.

export RSPEC=true
export AUTOFEATURE=true

Additionally, if you’re on a Mac, I think it’s worth setting up Growl notifications for autotest. It’s not a requirement for testing or even using autotest but it’s a nice enhancement. Search Google for “autotest+growl” and you’ll find a number sites that will show you how to set it up.

Note: Growl notifications currently don’t work with Cucumber but they do work with RSpec and we will get into using RSpec eventually.

Let’s write a feature test to check our output from the last episode. If you remember, all we did was set up our home page to say “Hi.” I’m going to write a test to make sure it says that.

$> script/generate feature Page
      exists  features/step_definitions
      create  features/manage_pages.feature
      create  features/step_definitions/page_steps.rb

Open up the RAILS_ROOT/features/manage_pages.feature file. I’m writing my test like so:

Feature: First Test
  In order to prove that I can write a quick feature
  Harry
  wants to write a simple test
 
  Scenario: Friendly home page
    Given I am on the home page
    Then I should see "Hi."

Now I save that and run the test with rake:

$> rake features
(in /Users/harry/Sites/jetrecord)
Feature: First Test  # features/manage_pages.feature
  In order to prove that I can write a quick feature
  Harry
  wants to write a simple test
  Scenario: Friendly home page  # features/manage_pages.feature:6
    Given I am on the home page # features/manage_pages.feature:7
    Then I should see "Hi."     # features/step_definitions/webrat_steps.rb:83
 
1 scenario
1 step skipped
1 step pending (1 with no step definition)
 
You can use these snippets to implement pending steps which have no step definition:
 
Given /^I am on the home page$/ do
end

So the test ran but didn’t pass because I don’t have a step defined that tells Cucumber how to get to the home page. Thankfully, it gives me a snippet of code at the end of the output there. I open up RAILS_ROOT/features/step_definitions/page_steps.rb and add that in, filling it in with the code it needs to fulfill that step.

Given /^I am on the home page$/ do
  visit '/'
end

Quite simply, I’m telling Cucumber to visit the URL that looks like ‘/’, which is the home page. I run the test again.

$> rake features
(in /Users/harry/Sites/jetrecord)
Feature: First Test  # features/manage_pages.feature
  In order to prove that I can write a quick feature
  Harry
  wants to write a simple test
  Scenario: Friendly home page  # features/manage_pages.feature:6
    Given I am on the home page # features/step_definitions/page_steps.rb:1
      No route matches "/" with {:method=>:get} (ActionController::RoutingError)
      [stacktrace info]
 
1 scenario
1 step failed
1 step skipped
rake aborted!

Okay, we’re getting somewhere but we didn’t move very far. Cucumber barfed because it was looking for a route defined by the Rails application. Cucumber runs in the context of the Rails application, not of the whole site architecture. It doesn’t care that I have an index file in the public directory because it’s not actually crawling my web site like a browser would.

This is not necessarily a bad thing. If I wanted I could install Selenium, hook into it via Cucumber and re-run the tests. This would catch my index.html page because Selenium acts like a web browser. Eventually, I can see myself doing that because I’m going to need to test some Ajax interactions. But right now it’s not necessary.

In order to run my application I need to remove the index file anyway, so the real solution is to define my home page in Rails and then test again. To do that I need to add a route to my RAILS_ROOT/config/routes.rb file. Then I need to define the controller and a view for the home page.

# config/routes.rb
ActionController::Routing::Routes.draw do |map|
  map.root :controller => 'pages', :action => 'home'
end
# app/controllers/pages_controller.rb
class PagesController < ApplicationController
  def home
  end
end
# app/views/pages/home.html.erb
Hi.
# app/views/layouts/application.html.erb
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
  <head>
    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
    <title>Hello</title>
  </head>
  <body>
    <%= yield %>
  </body>
</html>

Alright. One last thing. We need to rename or remove the public/index.html file. I’m just going to remove it. Then we try the test again.

$> git rm public/index.html
$> rake features
(in /Users/harry/Sites/jetrecord)
Feature: First Test  # features/manage_pages.feature
  In order to prove that I can write a quick feature
  Harry
  wants to write a simple test
  Scenario: Friendly home page  # features/manage_pages.feature:6
    Given I am on the home page # features/step_definitions/page_steps.rb:1
    Then I should see "Hi."     # features/step_definitions/webrat_steps.rb:83
 
1 scenario
2 steps passed

Bingo! Now let’s try running autotest.

$> autotest
loading autotest/cucumber_rails_rspec
/opt/ruby-enterprise-1.8.6-20080810/bin/ruby /opt/ruby-enterprise-1.8.6-20080810/lib/ruby/gems/1.8/gems/cucumber-0.1.15/bin/cucumber features --format progress --format autotest --color --out /var/folders/f1/f1HkdqHjFe43ByUhxU-FO++++TI/-Tmp-/autotest-cucumber.5265.0 
..

Great! It’s working. Two passing steps (the two dots at the end) and autotest is still running, waiting for me to do something stupid. I’m going to introduce an error into home.html.erb.

# app/views/pages/home.html.erb
Hello.

And then back to the terminal.

/opt/ruby-enterprise-1.8.6-20080810/bin/ruby /opt/ruby-enterprise-1.8.6-20080810/lib/ruby/gems/1.8/gems/cucumber-0.1.15/bin/cucumber features --format progress --format autotest --color --out /var/folders/f1/f1HkdqHjFe43ByUhxU-FO++++TI/-Tmp-/autotest-cucumber.5265.1 
.F
 
Failed:
 
1)
expected: /Hi./m,
     got: "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n        \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html>\n  <head>\n    <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\" />\n    <title>Hello</title>\n    \n  </head>\n  <body>\n    Hello.\n  </body>\n</html>" (using =~)
Diff:
@@ -1,2 +1,2 @@
-/Hi./m
+"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n        \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html>\n  <head>\n    <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\" />\n    <title>Hello</title>\n    \n  </head>\n  <body>\n    Hello.\n  </body>\n</html>"
 
[stacktrace info]
 
./features/step_definitions/webrat_steps.rb:84:in `Then /^I should see "(.*)"$/'
features/manage_pages.feature:8:in `Then I should see "Hi."'

And that’s how it works. We write a test, it fails, we write some code to pass the test, it passes.

I have to apologize. This post has already become too long. I like the direction it took but I don’t think moving on to writing an actual Jetrecord feature would be wise right now. So, really, next time I’m going to write tests for the first feature. I’ll even include some RSpec in there.

Coming Up in the Next Episode

No more rhymes now, I mean it. I’m going to write some tests for the first feature. Until next time, cheers and happy flying.

Material You May Find Useful Related to This Episode

Cucumber: Behaviour Driven Development with elegance and joy

Creative Commons License The Building of Jetrecord by Harry Love is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License. When code, text, or media in this series is not created by me and is not in the public domain I will provide links to their sources from which you can find their respective licenses and terms of use.

« Previously Next Page »