Ruby: Unit testing delegations

When working with Rails I tend to use a lot of delegations. Since I'm a big fan of the Law of Demeter I often find myself delegating an_ad_instance.lister_name to the name method of the lister attribute.

Class Ad < ActiveRecord::Base
extend Forwardable
def_delegator :lister, name, lister_name
def lister
...
end
end
Often, I end up writing tests for something even as simple as delegation, since I try to TDD all the code I write.
class AdTest < Test::Unit::TestCase
def test_lister_name_is_delegated_to_listers_name
ad = Ad.new(:lister => Lister.new(:name => 'lister_name'))
assert_equal 'lister_name', ad.lister_name
end
end
After a few of these tests, the duplication begins to tell me that I can do something simpler with some metaprogramming. So, I stole the idea from Validatable and created some delegation custom assertions.
class AdTest < Test::Unit::TestCase
Ad.delegates do
lister_name.to(:lister).via(:name)
lister_address.to(:lister).via(:address)
...
end
end
The above delegation testing DSL allows me to test multiple validations without the need for many (very similar) tests.

The implementation to get this working is fairly straightforward: I create a subclass of the class under test, add a constructor to the subclass that takes the objec tthat's going to be delegated to, and set the value of the attribute on the delegate object. I also add an attr_accessor to the subclass, since all I care about is the delegation (for this test). It doesn't matter to me that I'm changing the implementation of the lister method because I'm testing the delegation, not the lister method.
class DelegateAssertion
attr_accessor :desired_method, :delegate_object, :original_method

def initialize(desired_method)
self.desired_method = desired_method
end

def to(delegate_object)
self.delegate_object = delegate_object
self
end

def via(original_method)
self.original_method = original_method
end
end

class DelegateCollector
def self.gather(block)
collector = new
collector.instance_eval(&block)
collector.delegate_assertions
end

attr_accessor :delegate_assertions

def delegate_assertions
@delegate_assertions ||= []
end

def method_missing(sym, *args)
assertion = DelegateAssertion.new(sym)
delegate_assertions << assertion
assertion
end
end

class Class
def delegates(&block)
test_class = eval "self", block.binding
assertions = DelegateCollector.gather(block)
assertions.each do |assertion|
klass = Class.new(self)
klass.class_eval do
attr_accessor assertion.delegate_object
define_method :initialize do |delegate_object|
self.send :"#{assertion.delegate_object}=", delegate_object
end
end
test_class.class_eval do
define_method "test_#{assertion.desired_method}_is_delegated_to_#{assertion.delegate_object}_via_#{assertion.original_method}" do
klass_instance = klass.new(stub(assertion.original_method => :original_value))
begin
assert_equal :original_value, klass_instance.send(assertion.desired_method)
rescue Exception => ex
add_failure "Delegating #{assertion.desired_method } to #{assertion.delegate_object} via #{assertion.original_method} doesn't work"
end
end
end
end
end
end
The resulting tests make it hard to justify leaving of tests, even if you are 'only doing simple delegation'.

note: In the example I use an ActiveRecord object, but since I'm adding this behavior to Class any class can take advantage of these custom validations.

Read: Ruby: Unit testing delegations

AddThis Social Bookmark Button

相关文档(Relevant Entries)
NetBeans:Ruby开发者的新伙伴
Ruby on Rails 2.0的新特性介绍
Ruby 1.9正式发布 运行效率大幅度提升
RoR迈向2.0,强化企业开发机制
RUBY资源
Ruby入门[转]
Fast-track your Web apps with Ruby on Rails
关于ruby的电子书下载
WoW Powerleveling
Posted on May 5, 2007 6:53 PM | | | Comments (0) | | TrackBacks (0)

引用地址(TRACKBACKS)
 
TrackBack URL for this entry:
http://www.wujianrong.com/mt-tb.cgi/5283

发布评论(ADD YOUR COMMENTS)
 
感谢您参与评论;发表您的意见时请保持文章的相关性;不相关的或是单纯宣传的内容可能会被删掉。您的E-mail只是用来确认您发表的文章,不会出现在网页上。
Please keep your comments relevant to this blog entry. Email addresses are never displayed, but they are required to confirm your comments.

称呼(Name):      记住我的个人信息(Remember)
邮箱(Email):
网址(URL):
评论(Add your comments):

相关内容
广告计划