Monday, November 12, 2007

Ruby: Testing Private Methods

Jason Rudolph recently wrote about Testing Private Methods in Ruby. Before I continue, it's probably a good idea to note that I rarely test private methods. I prefer to test through the public API. However, there are times when life is easier if you write a few tests for a private method or two.

The solution highlighted by Jason (original implementation from Evan Phoenix) is nice from a maintainability perspective since it encapsulates changing the behavior of the class within the test. However, it's a bit verbose for my taste.

Reading the entry I couldn't help but think that gaining temporary access to a private method for testing seems like something that can be handled by a general solution instead.

Here's how I would have solved the problem.

require 'rubygems'
require 'dust'
require 'test/unit'

class Ninja
private
def kill(num_victims)
"#{num_victims} victims are no longer with us."
end
end

class Class
def publicize_methods
saved_private_instance_methods = self.private_instance_methods
self.class_eval { public *saved_private_instance_methods }
yield
self.class_eval { private *saved_private_instance_methods }
end
end

unit_tests do
test "kill returns a murder string" do
Ninja.publicize_methods do
assert_equal '3 victims are no longer with us.', Ninja.new.kill(3)
end
end
end

Sunday, November 11, 2007

Rails: Enumerable#index_by

Documentation
Convert an enumerable to a hash. Examples:

people.index_by(&:login)
=> { "nextangle" => , "chade-" => , ...}
people.index_by { |person| "#{person.first_name} #{person.last_name}" }
=> { "Chade- Fowlersburg-e" => , "David Heinemeier Hansson" => , ...}
Usage
I've used Enumerable#index_by for 2 different reasons recently. In one instance I needed faster access to find an element in an array. We found that pulling something out of a hash was faster than using the find method of array. We did a few benchmarks similar to the contrived examples below.
array = [1..100].to_a
hash = array.index_by { |element| element }
array_bm = Benchmark.measure do
array.find { |element| element = 99 }
end
hash_bm = Benchmark.measure do
hash.include? 99
end

p array_bm.real # => 1.382
p hash_bm.real # => 1.001
note: we couldn't use the include? method for our particular instance, so I left it out of the contrived example.

The benchmarks for our project revealed even larger gains, thus it made sense to convert our array to a hash and work with that instead of the array.

The other usage we found for index_by was to utilize the fact that index_by overwrites the value instead of appending (which group_by does). We had a list of scores and wanted to group them together. The group_by method could handle that case; however, we only wanted to keep track of the highest score per group. The test below shows an example where a list of grades can be grouped by grade letter and the highest score per grade letter.

Test

require 'rubygems'
require 'active_support'
require 'test/unit'
require 'dust'

unit_tests do
test "index by highest numeric per grade level" do
grades = [50, 55, 67, 62, 71, 89, 84, 85, 99]
expected = {"A"=>99, "B"=>89, "C"=>71, "D"=>67, "F"=>55}
actual = grades.sort.index_by do |grade|
case
when grade < 60 then "F"
when grade < 70 then "D"
when grade < 80 then "C"
when grade < 90 then "B"
else "A"
end
end
assert_equal expected, actual
end
end

Saturday, November 10, 2007

Rails: Integer#even?

Documentation
None

Usage
The Integer#even? method is helpful when adding zebra striping to views.

For example:
  <% @customers.each_with_index do |customer, index| %>
<div style="color:<%= index.even? ? "white" : "gray" %>">
<%= customer.name %>
</div>
<% end %>

Test

require 'rubygems'
require 'active_support'
require 'test/unit'
require 'dust'

unit_tests do
test "10 is even?" do
assert_equal true, 10.even?
end
end

Friday, November 09, 2007

Rails: String#each_char

Documentation
Yields a single-character string for each character in the string. When $KCODE = ‘UTF8’, multi-byte characters are yielded appropriately.
Usage
The String#each_char method is nice for iterating through a string, one character at a time. I generally use regex for string manipulation; however, when context within the string matters, each_char is helpful.

Test

require 'rubygems'
require 'active_support'
require 'test/unit'
require 'dust'

unit_tests do
test "each_char can be used to strip every other charater" do
strip, result = true, ""
"hello world".each_char do |char|
result << char if strip
strip = !strip
end
assert_equal "hlowrd", result
end
end

Thursday, November 08, 2007

Rails: Hash#diff

Documentation
None

Usage
The Hash#diff method is helpful for determining the difference between two hashes.

note: The receiver matches on both keys and values. If the pair is not matched it be returned. (for example: {:a => 1, :b => 3}.diff({:a=>2}) # => {:a=>1, :b=>3})

Test

require 'rubygems'
require 'active_support'
require 'test/unit'
require 'dust'

unit_tests do
test "find the difference between two hashes" do
assert_equal({:b=>3}, {:a => 1, :b => 3}.diff({:a=>1}))
end
end