Friday, May 22, 2009

Selenium In Rails with Fixtures

Selenium is a framework for acceptance testing, designed for web applications. With selenium you can run your tests on all popular browsers.

Now a days web applications are consist of a lot of UI logic, animations, ajax calls. Only unit test and functional test does not ensure the stability of your application any more. A small change in Javascript can cause instability in your application. Tiny changes can break the contract of browser compatibility. A simple miss spelled link can give you a nightmare.

Selenium gives you the power to run automated acceptance test codes in real browsers. As it supports all popular browsers you can easily test browser compatibility of your application.

Key Features

  • Selenium IDE provides facility to record test script with ease and export the script to your preferred language(Java, C#, ruby, Perl, PHP etc)
  • Write one test script and run in all popular browsers and OS
  • Selenium Grid facilitates to run test codes parallely in multiple server, which pace up the execution of test code.

Setup

Install Selenium client

gem install selenium-client

Download Selenium Remote Control from here.

You will need Java installed on your machine to run Selenium Remote Control.

I write a script to start Selenium Remote Control, which is like.

cd E:\selenium\selenium-remote-control-1.0-beta-2\selenium-server-1.0-beta-2
java -jar selenium-server.jar

Use Firefox and Selenium IDE(a Firefox plugin) to capture the actions and convert it to ruby code.

XPath Checker and Fire Finder are two useful Firefox plugin to find and locate elements using xpath.

Record actions

Start Firefox, go to the root url, from where you are going to start test. Go to tools tab and start Selenium IDE.

Now as you browse through the site Selenium IDE will record the actions. After you have finished a scenario, you can stop recording and export test case as ruby file from File menu.

Selenium IDE generates is an old rails style code and it uses old selenium client driver. Here is a sample integration code that uses rails 2.3.2 style and new selenium client driver.Now to run your test code as you have noticed, you need mention the root url of the site you are testing.

require 'test_helper'
require 'selenium/client'

class PostsIntegrationTest < ActionController::IntegrationTest
fixtures :all

def setup
@browser = Selenium::Client::Driver.new(
:host => "localhost",
:port => 4444,
:browser => "*firefox",
:url => "http://localhost:3000",
:timeout_in_second => 60
)

@browser.start_new_browser_session
end

def teardown
@browser.close_current_browser_session
end

test 'the truth' do
@browser.open "/posts"
@browser.click "link=New post", :wait_for => :page
@browser.type "post_title", "test"
@browser.type "post_title", "Testing with Selenium"
@browser.type "post_body", "bla... bla..."
@browser.click "post_published"
@browser.click "post_submit", :wait_for => :page
assert_equal "Testing with Selenium", @browser.get_text("xpath=/html/body/p[2]/span")
end

end
Here is the trick. Run your application in test environment. So the application will use test database. As test database is prepared automatically by rails, you can use the test fixtures as the input set of you test codes. And it will be automatically reset to the fixture set for each test methods.

How to organize

1. Dry and reuse

Acceptance test codes are directly dependent on view code. So you need to reuse test codes as much as possible. If you don’t a simple change in view will make you life hell.

2. Separate your actions to methods, so that you can reuse

def login(user_name, password)
@browser.open "/"
@browser.type "user_name", user_name
@browser.type "password", password
@browser.click "css=.btn[name='commit']", :wait_for => :page
end

def login_as_developer()
login(users(:developer).user_name, '123')
end

Now you can call login method to login to the system. And do not put assertions in these methods. This way you can even test the failure path.

3. Now call these basic methods and do assertion. This will make your code readable.

test 'login as a developer with valid credential' do
login_as_developer()
assert_url('/dashboard/show')
end

Note: Well there is no such thing as assert_url in selenium client to assert the current relative url.You can copy the following code and put it in your test helper.

def assert_url(url)
assert_equal(url, @browser.get_location().match(/.+\/\/[^\/]+(.*)/)[1])
end

4. Use a different helper file for common methods. Don’t use the common test helper, as selenium test are quit different from unit and functional test of rails.

It does not uses mock request and response. So you can not use selenium methods else where. That is why it is better to separate the selenium test helper.

5. Use selenium-client, so that you can use different wait_for methods, which are very useful. See the documentation here.

Tuesday, May 12, 2009

A solution for a gem install mysql problem

Some times we get an error in linux as follows while running gem install mysql,

root@ip-10-251-30-82:/etc/mysql# gem install mysql
Building native extensions.  This could take a while...
ERROR:  Error installing mysql:
     ERROR: Failed to build gem native extension.

/usr/bin/ruby1.8 extconf.rb install mysql
checking for mysql_query() in -lmysqlclient... no
checking for main() in -lm... yes
checking for mysql_query() in -lmysqlclient... no
checking for main() in -lz... no
checking for mysql_query() in -lmysqlclient... no
checking for main() in -lsocket... no
checking for mysql_query() in -lmysqlclient... no
checking for main() in -lnsl... yes
checking for mysql_query() in -lmysqlclient... no
*** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of
necessary libraries and/or headers.  Check the mkmf.log file for more
details.  You may need configuration options.

Provided configuration options:
     --with-opt-dir
     --without-opt-dir
     --with-opt-include
     --without-opt-include=${opt-dir}/include
     --with-opt-lib
     --without-opt-lib=${opt-dir}/lib
     --with-make-prog
     --without-make-prog
     --srcdir=.
     --curdir
     --ruby=/usr/bin/ruby1.8
     --with-mysql-config
     --without-mysql-config
     --with-mysql-dir
     --without-mysql-dir
     --with-mysql-include
     --without-mysql-include=${mysql-dir}/include
     --with-mysql-lib
     --without-mysql-lib=${mysql-dir}/lib
     --with-mysqlclientlib
     --without-mysqlclientlib
     --with-mlib
     --without-mlib
     --with-mysqlclientlib
     --without-mysqlclientlib
     --with-zlib
     --without-zlib
     --with-mysqlclientlib
     --without-mysqlclientlib
     --with-socketlib
     --without-socketlib
     --with-mysqlclientlib
     --without-mysqlclientlib
     --with-nsllib
     --without-nsllib
     --with-mysqlclientlib
     --without-mysqlclientlib


Gem files will remain installed in /usr/lib/ruby/gems/1.8/gems/mysql-2.7 for inspection.
Results logged to /usr/lib/ruby/gems/1.8/gems/mysql-2.7/gem_make.out
root@ip-10-251-30-82:/etc/mysql# gem install mysql -- --with-mysql-config=/etc/mysql/my.cnf
Building native extensions.  This could take a while...
ERROR:  Error installing mysql:
     ERROR: Failed to build gem native extension.

The solution is simple :)

Just run,

apt-get install libmysqlclient15-dev libmysqlclient15off zlib1g-dev libmysql-ruby1.8

Run the following if you do not have mysql installed,

apt-get install mysql-server-5.0 mysql-client-5.0 libmysqlclient15-dev libmysqlclient15off zlib1g-dev libmysql-ruby1.8

And then run

gem install mysql

Saturday, May 2, 2009

Testing model in rails

How we want our test environment

  1. Isolation of input fixtures, so that changing fixture of one test code will not impact others.
  2. Easily create input set and reuse existing.
  3. Readable test code.
  4. Runs very fast(Slower test codes are run less by developers).
  5. Write less to test.

Problems with built in fixture and test helper

  1. Fixture does not allow to isolate input set.
  2. No feature of reusability in fixtures causes developer to create unnecessary duplication in fixtures.
  3. Changes in table definition causes a lot of change in fixture set.
  4. Fixtures are hidden in different file. So it becomes difficult to trace a test code.
  5. Fixture is database driven. So even if you do not need a saved instance of an ActiveRecord object, you end up using one. The more the queries get executed, the slower the test code becomes.

Factory girl, one of the alternate of fixture seems to be the better solution then fixture. And shoulda makes the life easier for writing test codes.

Why factory?

  1. Isolated input set
  2. Different build method gives the flexibility to create saved and unsaved instances
  3. Can easily extend existing factory to create a new one
  4. Facility to override property and to stub methods to reduce database query
  5. Increase readability of test codes.

Why Shoulda?

  1. Context & Should blocks - Context and should block provides facilities to do BDD
  2. Assertions - Provides many common and useful assertions
  3. Macros - Generate many ActionController and ActiveRecord tests with helpful error messages

So let’s get some action. Here is a simple test done using factory and shoulda. We will write unit test for User model which looks like the following.

class User < ActiveRecord::Base
 has_many :posts, :order => 'created_at DESC'
 has_many :comments

 validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i
 validates_uniqueness_of :email

 def display_name
   return "#{first_name} #{last_name}"
 end

 def latest_post
   return self.posts.first
 end
end

The Post model is,

class Post < ActiveRecord::Base
 belongs_to :user
 has_many :comments, :dependent => :destroy

 validates_presence_of :title
 validates_presence_of :body
end

And the user factory is,

Factory.sequence :email do |n|
 "email#{n}@test.com"
end

Factory.define :user do |u|
 u.first_name 'Ashraf'
 u.last_name  'Zaman'
 #allows you to create different users with different email using the same definition
 u.email { Factory.next(:email) }
end

Post factory is,

Factory.define :post do |p|
 p.title 'A sample post'
 p.body  'This is a sample post'
 p.created_at Time.now
 p.updated_at Time.now
end

Finally the user model test is,

require 'test_helper'

class UserTest < ActiveSupport::TestCase

 should_have_many :posts
 should_have_many :comments
 should_require_unique_attributes :email

 should_not_allow_values_for :email, "ab.cd", "1234@"
 should_allow_values_for :email, "ashrafuzzaman.g2@gmail.com"

 should 'display_name' do
   user = Factory.build(:user, :first_name => 'Ashraf', :last_name => 'Zaman')
   assert_equal('Ashraf Zaman', user.display_name)
 end

 context 'Default posts' do
   setup do
     @user = Factory.build(:user, :posts => [
         Factory.build(:post, :title => 'Second Post', :created_at => Time.now),
         Factory.build(:post, :title => 'First Post', :created_at => 2.minutes.ago)
       ])
   end

   should 'return latest post' do
     assert_equal('Second Post', @user.latest_post.title)
   end
 end

end

You can download the demo from here

How factory helped

To test the display_name I do not need a saved instance, So I used Factory.build to build the model object with out saving it in the database.

Note: With Factory.build you can create an unsaved model instance from the factory prototype. It is very useful for testing model logic.

Some attributes of user prototype are intentionally redefined to increase the readability of the test code. Now you can easily read the test code as you can see the input set right in front of you. Thus you can effortlessly create different input set from one basic prototype.

The interesting part of this section is the test of latest_post of user model. Here as you can see the association method ‘posts’ is overridden with expected post list. Now you can mock this this methods as this is provided by the framework itself. So no need to test it. But by mocking this you actually increased the readability of the test code and avoided database call.

How shoulda helped

The Shoulda gem makes it easy to write elegant, understandable, and maintainable Ruby tests. Shoulda consists of test macros, assertions, and helpers added on to the Test::Unit framework.

With shoulda we defined a context 'Default posts' and initialized it. We can test codes related to posts with in the context. As the context is setup with the minimum and sufficient information of input set needed for the context, the test code becomes very simple and easy to read and maintain.

Resources

http://thoughtbot.com/projects/factory_girl

http://thoughtbot.com/projects/shoulda