Thursday, June 11, 2009

Run focused tests in TextMate with test/unit

If you are using TextMate version 1.5.8 (1498) and want to run focused test (⌘-shift-r) with the test/unit notation

class SimpleTest < Test::Unit::TestCase
test "simple me" do
# test code
end
end

then modify the file

/Applications/TextMate.app/Contents/SharedSupport/Bundles/Ruby.tmbundle/Support/RubyMate/run_script.rb

and add lines 1, 5-7 and 15-16

    1   spec, context, name, test_name = nil, nil, nil
2
3 File.open(ENV['TM_FILEPATH']) do |f|
4 # test/unit
5 lines = f.read.split("\n")[0...n].reverse
6 name = lines.find { |line| line =~ /^\s*def test[_a-z0-9]*[\?!]?/i }.to_s.sub(/^\s*def (.*?)\s*$/) { $1 }
7 # ActiveSupport::Testing::Declarative#test
8 # to use the new test "" do;end syntax
9 test_name = $3 || $4 if lines.find { |line| line =~ /^\s*(test)\s+('(.*)'|"(.*)")+\s*(\{|do)/ }
10 # test/spec.
11 spec = $3 || $4 if lines.find { |line| line =~ /^\s*(specify|it)\s+('(.*)'|"(.*)")+\s*(\{|do)/ }
12 context = $3 || $4 if lines.find { |line| line =~ /^\s*(context|describe)\s+('(.*)'|"(.*)")+\s*(\{|do)/ }
13 end
14
15 if name and !name.empty?
16 args << "--name=#{name}"
17 elsif test_name and !test_name.empty?
18 args << "--name=test_#{test_name.gsub(/\s+/,'_')}"
19 elsif spec and !spec.empty? and context and !context.empty?
20 args << %Q{--name="/test_spec \\{.*#{context}\\} \\d{3} \\[#{spec}\\]/"}
21 else
22 puts "Error: This doesn't appear to be a TestCase or spec."
23 exit
24 end
25

Sunday, November 30, 2008

Sometimes

Have a look at this interesting blog post from Dan Croak about questioning current testing methods.

Thursday, July 24, 2008

Remember to reload your associations in your tests

Save time by reloading your associations when using RSpec.

    1 # your_object.rb
2 class YourObject < ActiveRecord::Base
3 def your_method
4 self.your_association << TestObject.new
5 end
6 end
7
8 # your_object_spec.rb
9 it "should test your_method" do
10 lambda { your_object.your_method }.should change(your_object.your_association, :size) # => no change, test fails....
11 end
12


Just put a reload after your association call:

    1 # your_object_spec.rb
2 it "should test your_method" do
3 lambda { your_object.your_method }.should change(your_object.your_association.reload, :size) # => association reloads and the test succeeds
4 end

Tuesday, July 22, 2008

ordered method testing with rspec

Since I am learning RSpec for Rails I wonder how to write a test which tests the order of the methods called by my method. Now I found a solution which works. The answer is simple: should_receive takes a block which is called after the assertion of the should_receive ran.

Your code may look like this:

    1 # user.rb
2 def change_my_attributes(name,login,password)
3 self.name = name
4 self.login = login
5 self.password = password
6 save!
7 end
8
9 # user_spec.rb
10 it "should run my methods in #change_my_attributes in the correct order" do
11 @user = User.new
12 @user.should_receive(:name=) do
13 @user.should_receive(:login=) do
14 @user.should_receive(:password=) do
15 @user.should_receive(:save!)
16 end
17 end
18 end
19 @user.change_my_attributes('foo','bar','foobar')
20 end


Looks kind of ugly but it works...

Monday, July 14, 2008

test/unit, test/spec, RSpec, shoulda

Who has experience with two of these test frameworks, and can tell about this? Which one is better, and why?

Saturday, July 12, 2008

Keep your tests small

Keeping your tests small sounds a bit odd, but it's quite easy to get in a test flow and to write spaghetti tests, testing too much at once. Stay focussed on what to test, and just test this single aspect of your code!
    1 # don't do this!
2 describe "A user's display name" do
3 it "should reflect the user's name" do
4 user = User.new(:login => 'alto')
5 user.display_name.should eql('alto')
6
7 user.firstname = 'Thorsten'
8 user.display_name.should eql('Thorsten')
9
10 user.lastname = 'Böttger'
11 user.display_name.should eql('Thorsten Böttger')
12 end
13 end
14
This test is hard to read, since it tests three aspects at once. Even the dscription block is a bit ambiguous. The test is unfocused, and as a matter of result, in case your test contains an error itself, it's hard to debug. The following three tests are much better:
    1 # much better
2 describe "A user's display name" do
3 before do
4 @user = User.new(:login => 'alto')
5 end
6 it "should equal the login name if no other name is given" do
7 @user.display_name.should eql('alto')
8 end
9 it "should equal his firstname if it is given" do
10 @user.firstname = 'Thorsten'
11 @user.display_name.should eql('Thorsten')
12 end
13 it "should equal his fullname if firstname and lastname are given" do
14 @user.firstname = 'Thorsten'
15 @user.lastname = 'Böttger'
16 @user.display_name.should eql('Thorsten Böttger')
17 end
18 end
19

Saturday, June 28, 2008

Hide external access behind a small facade for easy mocking

Imagine you have validation like this, where you call the youtube website to make sure the provided video id is valid:


1 class Video < ActiveRecord::Base
2 def validate
3 response = Net::HTTP.start('youtube.com') { |http| http.head("/v/#{self.youtube_video_id}") }
4 unless response.is_a?(Net::HTTPOK)
5 errors.add(:youtube_video_id, "no valid Youtube Video ID")
6 end
7 end
8 end
9
Actually, this code fragment is a pain in the neck to test. If you hide the external access behind a small facade (or wrapper if you like) like this:

    1 class YouTube
2 def self.video_id_valid?(video_id)
3 response = Net::HTTP.start('youtube.com') do |http|
4 http.head("/v/#{self.youtube_video_id}")
5 end
6 response.is_a?(:Net::HTTPOK)
7 end
8 end
9
10 class Video < ActiveRecord::Base
11 def validate
12 unless Youtube.video_id_valid?(self.youtube_video_id)
13 errors.add(:youtube_video_id, "no valid Youtube Video ID")
14 end
15 end
16 end
17
it's much easier to test:

    1 # video_spec.rb
2 describe Video, "validations" do
3 before(:each) do
4 @video = Video.new(:youtube_video_id => 'iY6VroKoB8E')
5 end
6 it "should succeed with correct youtube video id" do
7 Youtube.stub!(:video_id_valid?).and_return(true)
8 @video.should be_valid
9 end
10 it "should fail with incorrect youtube video id" do
11 Youtube.stub!(:video_id_valid?).and_return(false)
12 @video.should_not be_valid
13 end
14 end
15