With the new year comes a new commitment from me to start practising Test-Driven Development (TDD) for my Rails' projects. Up until this point I've usually put off writing tests until towards the end of my projects, which obviously doesn’t lead to some of the benefits that TDD brings, such as increased confidence during refactoring or a cleaner model API design.
Therefore it’s timely that John Nunemaker over at RailTips has just initiated Test Awareness Month and is blogging about how to go about testing Ruby on Rails applications. One thing that caught my eye in one of John’s examples is that he’s using the more readable BDD-style syntax for his test/unit examples i.e:
test "should test something" do ...assertion goes here end
—instead of the old school way:
def test_should_test_something ...assertion goes here end
For no particular reason I decided to dig into the Rails' source code to see how this alternative syntax is implemented. I came across a wonderful 21 lines of code from Rails core member Jeremy Kemper that serves as a great example of the power and beauty of Ruby.
Here’s the code (from activesupport/lib/active_support/testing/declarative.rb):
module ActiveSupport module Testing module Declarative # test "verify something" do # ... # end def test(name, &block) test_name = "test_#{name.gsub(/\s+/,'_')}".to_sym defined = instance_method(test_name) rescue false raise "#{test_name} is already defined in #{self}" if defined if block_given? define_method(test_name, &block) else define_method(test_name) do flunk "No implementation provided for #{name}" end end end end end end
This code adds a method named test to the Declarative module within ActiveSupport’s Testing module. Modules in Ruby let you share code between classes. They're a bit like interfaces in Java or C#, except they can have implementation.
The test method accepts two parameters: the name of the test and a Ruby block containing the test implementation (everything between the do and end keywords in the example above). The test name is sanitized using a regex so that all spaces are replaced with underscores and then the to_sym method converts the name to a Ruby symbol, which is a sort of memory-light string that’s handy for identifying objects.
The method then checks to see if an instance method of the same name already exists, in which case it bails out by raising an exception. Finally, if a block was passed containing the test method implementation, then Ruby’s define_method method is invoked to dynamically create a method with the appropriate name and implementation. Otherwise, define_method is again used to create the test method, only this time the implementation is neatly set to flunk the test.
So in summary that’s a handful of lines of code that uses a little Ruby metaprogramming magic to support an alternative syntax for defining test methods by dynamically converting the new syntax to the old one behind the scenes. Good luck with doing that using Java!
Comments
There aren’t any comments on this post. Comments are closed.