Showing posts with label test. Show all posts
Showing posts with label test. Show all posts

Thursday, July 22, 2010

Everyone should start writing macros

Did some cleaning & catching up of specs for railsbestpractices.com yesterday, added quite a number of spec macros to dry up stuff, as well as cutting down the complexity within the spec files. Personally, i really really & really like macros, because it is fun to write & easy to write, it has many beneficial side effects:

#1. Since it is so simple to use macros, everyone in the team is more willing to participate in spec writing. Personal experience has proven that if specs/tests are hard to write, some developers tend to skip it.

#2. Since it is so easy to read, maintenance becomes much easier

#3. Developers are smart people (and should be paid well, but that's another story), by replacing repeated copy & paste & minor edit-here-and-there with macros-writing-and-usage, they feel smarter, a huge morale boast ~> happier team, & when people are happy, they tend to show more love towards the project, take ownership ~> project in a better state.

Btw, macro writing is no rocket-science, let's get started:

#1. Macro for 3rd party declarative
1
2
3
4
class User < ActiveRecord::Base
acts_as_authentic
# (blah blah)
end

Ok, we all know that authlogic is well tested, so there is really no point in writing specs for it. Yet, how do we know the model is pulling in authlogic support ?? Here's what i've done:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
module RailsBestPractices
module Macros

def self.included(base)
base.extend(ClassMethods)
end

module ClassMethods

def should_act_as_authentic
# Get most basic modules included by ActiveRecord::Base
basic_included_modules = Class.new(ActiveRecord::Base).included_modules

# Grab the model class
model = description.split('::').inject(Object){|klass,const| klass.const_get(const) }

# Get the extra modules included by the model
model_included_modules = model.included_modules
extra_modules = (model_included_modules - basic_included_modules).map(&:to_s)

# As long as we have any extra module matching the regexp, we can conclude that
# :acts_as_authentic has been declared for the model
extra_modules.any?{|mod| mod =~ /Authlogic::ActsAsAuthentic::/ }.should be_true
end

end

end
end

The usage in the spec file is:
1
2
3
4
5
describe User do
include RailsBestPractices::Macros
should_act_as_authentic
# (blah blah)
end

Of course, i can probably remove the include statement altogether & do the auto-including elsewhere, but taking this route, other developers may throw WTF-is-should_act_as_authentic error ~> BAD !!

#2. Macro for project-specific declarative
1
2
3
4
class Post < ActiveRecord::Base
include Markdownable
# (blah blah)
end

I wanna make sure the support from the home-baked Markdownable is pulled in. Since it is home-baked, just making sure the module is mixed in is not good enough, i need to ensure the functionality is there as well. Thus (nested within the above mentioned RailsBestPractices::Macros::ClassMethods module):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def should_be_markdownable

# Determine which factory to call
factory_id = description.split('::').inject(Object){|klass, const| klass.const_get(const) }.
to_s.tableize.singularize.to_sym

# Generate an example group
describe "being markdownable" do

it "should generate simple markdown html" do
raw = "subject\n=======\ntitle\n-----"
formatted = "<h1>subject</h1>\n\n<h2>title</h2>\n"
Factory(factory_id, :body => raw).formatted_html.should == formatted
end

it "should generate markdown html with <pre><code>" do
raw = "subject\n=======\ntitle\n-----\n def test\n puts 'test'\n end"
formatted = "<h1>subject</h1>\n\n<h2>title</h2>\n\n<pre><code>def test\n puts 'test'\nend\n</code></pre>\n"
Factory(factory_id, :body => raw).formatted_html.should == formatted
end

end
end

And the usage:
1
2
3
4
5
describe Post do
include RailsBestPractices::Macros
should_be_markdownable
# (blah blah)
end

#3. Macro for any other vanilla functionality
1
2
3
4
5
class Implementation < ActiveRecord::Base
def belongs_to?(user)
user && user_id == user.id
end
end

The macro definition:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def should_have_user_ownership
factory_id = description.split('::').inject(Object){|klass,const| klass.const_get(const) }.
to_s.tableize.singularize.to_sym

describe 'having user ownership' do

it 'should belong to someone if he is the owner of it' do
someone = Factory(:user)
Factory(factory_id, :user => someone).belongs_to?(someone).should be_true
end

it 'should not belong to someone if he is not the owner of it' do
someone = Factory(:user)
Factory(factory_id).belongs_to?(someone).should be_false
end

end
end

Finally, the usage:
1
2
3
4
describe Implementation do
include RailsBestPractices::Macros
should_have_user_ownership
end

Of course, it doesn't make sense to define macro for every functionality available. The general thumb of rule is that when similar code appears more than once, u can consider defining a macro for it. However, if similar-code is to appear more than once, u will probably start by extracting the code & placing it inside a module (eg. Markdownable), so this probably falls under #2. Macro for project-specific declarative.

Last but not least, the above macro definition can probably benefit from some refactoring, but i guess i'll leave this exercise to u :]

Thursday, December 10, 2009

Adding Webrat Matchers to Culerity

If u are using culerity, and somehow u missed the webrat matchers, eg. has_xpath(), here's what u can do:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
require 'webrat/core/xml'
require 'webrat/core/matchers'

module WebratLike

include Webrat::Matchers

def response
html = $browser.html
def html.body ; to_s ; end
html
end

end

World(WebratLike)
end
end

Enjoys !!

Wednesday, December 9, 2009

Testing of Clicking of Checkbox

We have checkboxes registered for onchange event handler. In testing using blueridge, to simulate checking of a checkbox, the following more ideal approaches, sadly, don't work:
1
2
$("input:checkbox:first").change(); #1
$("input:checkbox:first").click().change(); #2

But the following works:
1
$("input:checkbox:first").attr('checked', true).change();

Bitten once.

Tuesday, November 3, 2009

Nested Step Definitions: Passing Table as Arg

From within a step definition, it is possible to pass a table to another step using the following technique:
1
2
3
4
5
6
7
8
Given /^I have just entered the results for "([^\"]*)"$/ do |identifier|
Given %\I follow "Enter Results" for "#{identifier}"\
And %\I enter the following class test results:\, table(%\
| # | Name | Score|
| 1 | John Tan | 99.0 |
| 2 | John Hoo | 80.0 |
\
)
end

Here's the step definition that does processing of the table (nothing unusual actually):
1
2
3
4
5
Given /^I enter the following class test results:$/ do |table|
table.hashes.each do |hash|
# blah blah
end
end

Sweet :]

Tuesday, October 27, 2009

Smart click_button

In previous post, i've mentioned abt webrat's smart click_link(), and do u know that click_button() is also quite smart, meaning:
1
<input type="image" alt="Update" />

Works as well if we do:
1
click_button("Update")

Nice ?!

Monday, October 26, 2009

Smart click_link()

WOW, just found out image within <a/> tag (see below):
1
2
3
<a class="image" href="...">
<img src="..." alt="Back"/>
</a>

Actually work for the following pair of:
1
2
# Step
Given I follow "Back"

&:
1
2
3
4
# Step definition
Given /^I follow "([^\"]+)"$/ do |link|
click_link(link) # plain vanilla webrat
end

Cool !!

Sunday, October 25, 2009

Workaround for Webrat's have_xpath()

I have the following xpath:

//td[normalize-space(text())='Peter']/parent::tr//label[normalize-space(text())="Present"]/@for

It is valid, yet webrat's have_xpath() kept complaining that it cannot find it. As a workaround, i did something like this in my cucumber:
1
2
hdoc = Nokogiri::HTML(response.body)
(field_id = hdoc.xpath(the_above_rejected_xpath)).first.should_not be_nil

That's all.

Webrat's have_xpath() in diff modes

have_xpath() behaves slightly differently in :selenium & :webrat mode.

From webrat's doc, we have:

Webrat::Matchers#have_xpath(expected, options = {}, &block)

And

Webrat::Selenium::Matchers::have_xpath(xpath)

If u are a xpath fan like me, there is no way u can conveniently switch between the 2 modes without getting bitten.

Labels