Thursday, February 28, 2008
Ruby: Replace method_missing with dynamic method definitions
You have methods you want to handle dynamically without the pain of debugging method_missing.
becomes
Motivation
Debugging classes that use method_missing can often be painful. At best you often get a NoMethodError on an object that you didn't expect, and at worst you get stack level too deep (SystemStackError).
There are times that method_missing is required. If the usage of an object is unknown, but must support unexpected method calls you may not be able to avoid the use of method_missing.
However, often you know how an object will be used and using Dynamically Define Method you can achieve the same behavior without relying on method_missing.
Mechanics
Delegation is a common task while developing software. Delegation can be handled explicitly by defining methods yourself or by utilizing something from the Ruby Standard Library such as Forwardable. Using these techniques gives you control over what methods you want to delegate to the subject object; however, sometimes you want to delegate all methods without specifying them. Ruby's Standard Library also provides this capability with the delegate library, but we'll assume we need to implement our own for this example.
The simple way to handle delegation (ignoring the fact that you would want to undefine all the standard methods a class gets by default) is to use method_missing to pass any method calls straight to the subject.
This solution does work, but it can be problematic when mistakes are made. For example, calling a method that does not exist on the subject will result in the subject raising a NoMethodError. Since the method call is being called on the decorator, but the subject is raising the error it may be painful to track down where the problem resides.
The wrong object raising a NoMethodError is significantly better than the dreaded stack level too deep (SystemStackError). This can be caused by something as simple as forgetting to use the subject instance variable and trying to use a non-existent subject method or any misspelled method. When this happens the only feedback you have is that something went wrong, but Ruby isn't sure exactly what it was.
These problems can be avoided entirely by using the available data to dynamically define methods at run time. The following example defines an instance method on the decorator for each public method of the subject.
Using this technique any invalid method calls will be correctly reported as NoMethodErrors on the decorator. Additionally, there's no method_missing definition, which should help avoid the stack level too deep problem entirely.
Example: Using user defined data to define methods
Often you can use the information from a class definition to define methods instead of relying on method_missing. For example, the following code relies on method_missing to determine if any of the attributes are nil.
The code works, but it suffers from the same debugging issues that the previous example does. Utilizing Dynamically Define Method the issue can be avoided by defining the attributes and creating the empty_attribute? methods at the same time.
@subject = subject
end
@subject.send sym, *args, &block
end
endbecomes
subject.public_methods(false).each do |meth|
(class << self; self; end).class_eval do
define_method meth do |*args|
subject.send meth, *args
end
end
end
end
endMotivation
Debugging classes that use method_missing can often be painful. At best you often get a NoMethodError on an object that you didn't expect, and at worst you get stack level too deep (SystemStackError).
There are times that method_missing is required. If the usage of an object is unknown, but must support unexpected method calls you may not be able to avoid the use of method_missing.
However, often you know how an object will be used and using Dynamically Define Method you can achieve the same behavior without relying on method_missing.
Mechanics
- Dynamically define the necessary methods
- Remove method_missing
- Test
Delegation is a common task while developing software. Delegation can be handled explicitly by defining methods yourself or by utilizing something from the Ruby Standard Library such as Forwardable. Using these techniques gives you control over what methods you want to delegate to the subject object; however, sometimes you want to delegate all methods without specifying them. Ruby's Standard Library also provides this capability with the delegate library, but we'll assume we need to implement our own for this example.
The simple way to handle delegation (ignoring the fact that you would want to undefine all the standard methods a class gets by default) is to use method_missing to pass any method calls straight to the subject.
@subject = subject
end
@subject.send sym, *args, &block
end
endThis solution does work, but it can be problematic when mistakes are made. For example, calling a method that does not exist on the subject will result in the subject raising a NoMethodError. Since the method call is being called on the decorator, but the subject is raising the error it may be painful to track down where the problem resides.
The wrong object raising a NoMethodError is significantly better than the dreaded stack level too deep (SystemStackError). This can be caused by something as simple as forgetting to use the subject instance variable and trying to use a non-existent subject method or any misspelled method. When this happens the only feedback you have is that something went wrong, but Ruby isn't sure exactly what it was.
These problems can be avoided entirely by using the available data to dynamically define methods at run time. The following example defines an instance method on the decorator for each public method of the subject.
subject.public_methods(false).each do |meth|
(class << self; self; end).class_eval do
define_method meth do |*args|
subject.send meth, *args
end
end
end
end
endUsing this technique any invalid method calls will be correctly reported as NoMethodErrors on the decorator. Additionally, there's no method_missing definition, which should help avoid the stack level too deep problem entirely.
Example: Using user defined data to define methods
Often you can use the information from a class definition to define methods instead of relying on method_missing. For example, the following code relies on method_missing to determine if any of the attributes are nil.
attr_accessor :name, :age
empty?(sym.to_s.sub(/^empty_/,"").chomp("?"))
end
self.send(sym).nil?
end
endThe code works, but it suffers from the same debugging issues that the previous example does. Utilizing Dynamically Define Method the issue can be avoided by defining the attributes and creating the empty_attribute? methods at the same time.
attr_accessor *args
args.each do |attribute|
define_method "empty_?" do
self.send(attribute).nil?
end
end
end
attrs_with_empty_methods :name, :age
endLabels: define_method, metaprogramming, method_missing, refactoring, ruby
Monday, February 25, 2008
Ruby: Dynamically Define Method
Defining Methods Dynamically
You have methods that can be defined more concisely if defined dynamically.
becomes
Motivation
I use Dynamically Define Method quite frequently. Of course, I default to defining methods explicitly, but at the point when duplication begins to appear I quickly move to the dynamic definitions.
Dynamically defined methods can help guard against method definition mistakes, since adding another method usually means adding one more argument; however, this is not the primary reason for Dynamically Define Method.
The primary goal for Dynamically Define Method is to more concisely express the method definition in a readable and maintainable format.
Mechanics
Defining several similar methods is verbose and often unnecessary. For example, each of the following methods is simply changing the value of the instance variable state.
The above code executes perfectly well, but it's too similar to justify 11 lines in our source file. The following example could be a first step to removing the duplication.
Dynamically defining methods in a loop creates a more concise definition, but it's not a particularly readable one. To address this issue I define the def_each method. The motivation for defining a def_each method is that it is easy to notice and understand while scanning a source file.
Example: Defining instance methods with a class method.
The def_each method is a great tool for defining several similar methods, but often the similar methods represent a concept that can be used within code to make the code itself more descriptive.
For example, the previous method definitions were all about setting the state of the class. Instead of using def_each you could define a states class method that would generate the state setting methods. Defining a states class method helps create more expressive code.
becomes
Example: Defining methods by extending a dynamically defined module
Sometimes you have an object and you simply want to delegate method calls to another object. For example, you might want your object to decorate a hash so that you can get values by calling methods that match keys of that hash.
As long as you know what keys to expect, you could define the decorator explicitly.
While this works, it's truly unnecessary in Ruby. Additionally, it's a headache if you want to add new delegation methods. You could define method_missing to delegate directly to the hash, but I find debugging method_missing problematic and avoid it when possible. I'm going to skip straight to defining the methods dynamically from the keys of the hash. Let's also assume that the PostData instances can be passed different hashes, thus we'll need to define the methods on individual instances of PostData instead of defining the methods on the class itself.
The above code works perfectly well, but it suffers from readability pain. In cases like these I like to take a step back and look at what I'm trying to accomplish.
What I'm looking for is the keys of the hash to become methods and the values of the hash be returned by those respective methods. The two ways to add methods to an instance are to define methods on the metaclass and to extend a module.
Luckily for me, Ruby allows me to define anonymous modules. I have a hash and a decorator, but what I want is a way to define methods of the decorator by extending a hash, so I simply need to convert the hash to a module.
The following code converts a hash to a module with a method for each key that returns the associated value.
With the above code in place, it's possible to define the PostData class like the example below.
You have methods that can be defined more concisely if defined dynamically.
self.state = :failure
end
self.state = :error
endbecomes
def_each :failure, :error do |method_name|
self.state = method_name
endMotivation
I use Dynamically Define Method quite frequently. Of course, I default to defining methods explicitly, but at the point when duplication begins to appear I quickly move to the dynamic definitions.
Dynamically defined methods can help guard against method definition mistakes, since adding another method usually means adding one more argument; however, this is not the primary reason for Dynamically Define Method.
The primary goal for Dynamically Define Method is to more concisely express the method definition in a readable and maintainable format.
Mechanics
- Dynamically define one of the similar methods
- Test
- Convert the additional similar methods to use the dynamic definition
- Test
Defining several similar methods is verbose and often unnecessary. For example, each of the following methods is simply changing the value of the instance variable state.
self.state = :failure
end
self.state = :error
end
self.state = :success
endThe above code executes perfectly well, but it's too similar to justify 11 lines in our source file. The following example could be a first step to removing the duplication.
[:failure, :error, :success].each do |method|
define_method method do
self.state = method
end
endDynamically defining methods in a loop creates a more concise definition, but it's not a particularly readable one. To address this issue I define the def_each method. The motivation for defining a def_each method is that it is easy to notice and understand while scanning a source file.
method_names.each do |method_name|
define_method method_name do
instance_exec method_name, &block
end
end
end
endThe instance_exec methodWith def_each now available I can define the methods similar to the example below.
Ruby 1.9 includes instance_exec by default; however, Ruby 1.8 has no such feature. To address this limitation I generally include the following code created by Mauricio Fernandez.
; end
include InstanceExecHelper
begin
old_critical, Thread.critical = Thread.critical, true
n = 0
n += 1 while respond_to?(mname="__instance_exec")
InstanceExecHelper.module_eval{define_method(mname, &block) }
ensure
Thread.critical = old_critical
end
begin
ret = send(mname, *args)
ensure
InstanceExecHelper.module_eval{remove_method(mname) } rescue nil
end
ret
end
end
def_each :failure, :error, :success do |method_name|
self.state = method_name
endExample: Defining instance methods with a class method.
The def_each method is a great tool for defining several similar methods, but often the similar methods represent a concept that can be used within code to make the code itself more descriptive.
For example, the previous method definitions were all about setting the state of the class. Instead of using def_each you could define a states class method that would generate the state setting methods. Defining a states class method helps create more expressive code.
self.state = :error
end
self.state = :failure
end
self.state = :success
endbecomes
args.each do |arg|
define_method arg do
self.state = arg
end
end
end
states :failure, :error, :success
endExample: Defining methods by extending a dynamically defined module
Sometimes you have an object and you simply want to delegate method calls to another object. For example, you might want your object to decorate a hash so that you can get values by calling methods that match keys of that hash.
As long as you know what keys to expect, you could define the decorator explicitly.
@post_data = post_data
end
post_data[:params]
end
post_data[:session]
end
endWhile this works, it's truly unnecessary in Ruby. Additionally, it's a headache if you want to add new delegation methods. You could define method_missing to delegate directly to the hash, but I find debugging method_missing problematic and avoid it when possible. I'm going to skip straight to defining the methods dynamically from the keys of the hash. Let's also assume that the PostData instances can be passed different hashes, thus we'll need to define the methods on individual instances of PostData instead of defining the methods on the class itself.
(class << self; self; end).class_eval do
post_data.each_pair do |key, value|
define_method key.to_sym do
value
end
end
end
end
endThe above code works perfectly well, but it suffers from readability pain. In cases like these I like to take a step back and look at what I'm trying to accomplish.
What I'm looking for is the keys of the hash to become methods and the values of the hash be returned by those respective methods. The two ways to add methods to an instance are to define methods on the metaclass and to extend a module.
Luckily for me, Ruby allows me to define anonymous modules. I have a hash and a decorator, but what I want is a way to define methods of the decorator by extending a hash, so I simply need to convert the hash to a module.
The following code converts a hash to a module with a method for each key that returns the associated value.
hash = self
Module.new do
hash.each_pair do |key, value|
define_method key do
value
end
end
end
end
endWith the above code in place, it's possible to define the PostData class like the example below.
self.extend post_data.to_mod
end
endLabels: define_method, metaprogramming, refactoring
Sunday, February 24, 2008
Pair Programming all the time
I was having a discussion with a friend a few days ago about Pair Programming. I am, obviously, an advocate, but my friend doesn't believe in it.
Many people do not believe in Pair Programming (pairing). Most of those people don't agree with the benefits of pairing. However, my friend does believe that pairing is beneficial, he just does not believe in pairing all the time.
This is where semantics become very important. He was defining all the time as every minute that you are at work. I define all the time (in terms of pairing) as when I'm writing code that I'm going to commit.
There's plenty of time in the day that I spend on my own. Here's a few examples, but it's not representative of everything I do solo.
Patrick Kua talks about working in two different modes, Experimental and Focused, in the context of Test Driven Development. I agree with Patrick and I believe the discussion also directly applies to Pair Programming. When you know what you want to get done, pairing ensures that you do it well. But, when you don't know what you need to do, it's generally a pain to try to talk through the problem with your pair. Talking through a problem can be helpful, but I often like to get a decent understanding before trying to explain it to someone else. I also like to be able to stop going in one direction and immediately switch to another if I spot something interesting.
Pair programming is a good thing, but like all good things, it's best when done in moderation. There are times when you do not need to pair, and Wikipedia has a decent list of reasons that people are opposed to pair programming. But, those things should not discourage you from giving Pair Programming a fair shot.
Given the right set up and approach, I believe pairing is the most efficient way to deliver quality software.
Many people do not believe in Pair Programming (pairing). Most of those people don't agree with the benefits of pairing. However, my friend does believe that pairing is beneficial, he just does not believe in pairing all the time.
This is where semantics become very important. He was defining all the time as every minute that you are at work. I define all the time (in terms of pairing) as when I'm writing code that I'm going to commit.
There's plenty of time in the day that I spend on my own. Here's a few examples, but it's not representative of everything I do solo.
- Read about a library that might help us later on.
- Spike a new way to solve a complex problem.
- Debug a tricky issue.
Patrick Kua talks about working in two different modes, Experimental and Focused, in the context of Test Driven Development. I agree with Patrick and I believe the discussion also directly applies to Pair Programming. When you know what you want to get done, pairing ensures that you do it well. But, when you don't know what you need to do, it's generally a pain to try to talk through the problem with your pair. Talking through a problem can be helpful, but I often like to get a decent understanding before trying to explain it to someone else. I also like to be able to stop going in one direction and immediately switch to another if I spot something interesting.
Pair programming is a good thing, but like all good things, it's best when done in moderation. There are times when you do not need to pair, and Wikipedia has a decent list of reasons that people are opposed to pair programming. But, those things should not discourage you from giving Pair Programming a fair shot.
Given the right set up and approach, I believe pairing is the most efficient way to deliver quality software.
Labels: pair programming
Ruby: Inline Rescue
On my current project we use action specific Presenters 20% of the time. We opened ActionController and ActionView and redefined the render methods to automatically create presenters based on convention. For example the CommentsController has a show action, so render will try to create Comments::ShowPresenter when the show action is rendered. This works well for us, but like I said we only need a specific presenter 20% of the time. The other 80% of the time we still want a presenter that contains basic methods necessary on each page, but it is not specific to an action.
We solved this issue by attempting to create the specific presenter and rescuing with the base presenter.
Inline rescues promote concise code, but they shouldn't be overused to create overly complex lines of code. For example, I probably could have combined the two lines above into one line, but that would be a bit much to digest in my opinion.
In the example we attempt to create the class using constantize, but if an error (like uninitialized constant) occurs we rescue with the base Presenter class. Either way, the result is assigned to the klass local variable.
We solved this issue by attempting to create the specific presenter and rescuing with the base presenter.
klass = "_presenter".camelize.constantize rescue Presenter
@presenter = klass.newInline rescues promote concise code, but they shouldn't be overused to create overly complex lines of code. For example, I probably could have combined the two lines above into one line, but that would be a bit much to digest in my opinion.
In the example we attempt to create the class using constantize, but if an error (like uninitialized constant) occurs we rescue with the base Presenter class. Either way, the result is assigned to the klass local variable.
Labels: ruby
Ruby: Creating Anonymous Classes
Classes in Ruby are first-class objects—each is an instance of class Class. -- ruby-doc.orgMost classes are defined using the syntax of the following example.
...
endDefining classes this way is familiar to most developers. Ruby provides another syntax for defining classes using Class.new. The following example also defines the Person class.
Person = Class.new do
...
endClass.new(super_class=Object) creates a new anonymous (unnamed) class with the given superclass (or Object if no parameter is given). You can give a class a name by assigning the class object to a constant. -- ruby-doc.orgThe following example shows that the new class begins as an anonymous Class instance, but becomes a named class when it's assigned to a constant.
klass = Class.new
klass.ancestors # => [#<Class:0x21b38>, Object, Kernel]
klass.new.class # => #<Class:0x21b38>
Person = klass
Person.ancestors # => [Person, Object, Kernel]
Person.new.class # => Person
klass.ancestors # => [Person, Object, Kernel]
klass.new.class # => Person
klass # => Person
I generally use traditional syntax when defining named classes, but I have found anonymous classes very helpful when creating maintainable tests. For example, I often need to test instance methods of a Module. The following tests show how you can define a class, include a module, and assert the expected value all within one test method.
# implementation...
true
end
end
klass = Class.new do
include Gateway
end
assert_equal true, klass.new.post(1)
end
endThere are other ways to solve this issue, but I prefer this solution because it keeps everything necessary for the test within the test itself.
Another advantage to defining classes using Class.new is that you can pass a block to the new method; therefore, the block could access variables defined within the same scope. In practice, I've never needed this feature, but it's worth noting.
Labels: class, metaprogramming, ruby
Tuesday, February 19, 2008
Rake: Task Overwriting
Update at bottom
By default Rake Tasks append behavior every time they are defined. The following example shows that both definitions are executed.
I like this behavior, but sometimes you want to overwrite a task instead of appending to what's already there.
When you no longer want the existing behavior the
The overwrite method is good, but sometimes you want to redefine the task using one of the specialized Rake tasks. I recently wanted to redefine the test task, so I created the
Hopefully you wont need this type of thing very often, but it can be handy when you want to overwrite a task that has been previously by a framework you've included.
Update below...
Ola Bini pointed out that I'm not clearing the prerequisites. Clearing the prerequisites is something you can do without any modifications to rake.
Also, if you want prerequisites cleared as part of overwrite or abandon, you can easily add it to both tasks.
By default Rake Tasks append behavior every time they are defined. The following example shows that both definitions are executed.
task :the_task do
p "one"
end
task :the_task do
p "two"
end
Rake::Task[:the_task].invoke
# >> "one"
# >> "two"
I like this behavior, but sometimes you want to overwrite a task instead of appending to what's already there.
When you no longer want the existing behavior the
overwrite method can come in handy.
@actions.clear
enhance(&block)
end
end
task :the_task do
p "one"
end
Rake::Task[:the_task].overwrite do
p "two"
end
Rake::Task[:the_task].invoke
# >> "two"
The overwrite method is good, but sometimes you want to redefine the task using one of the specialized Rake tasks. I recently wanted to redefine the test task, so I created the
abandon method to remove the existing definition.
@actions.clear
end
end
task :the_task do
p "one"
end
Rake::Task[:the_task].abandon
Rake::TestTask.new(:the_task) do |t|
t.libs << "test"
t.pattern = 'test/**/*_test.rb'
t.verbose = true
end
Rake::Task[:the_task].invoke
# >> Expectations ..................
# >> Finished in 0.00442 seconds
# >>
# >> Success: 18 fulfilled
Hopefully you wont need this type of thing very often, but it can be handy when you want to overwrite a task that has been previously by a framework you've included.
Update below...
Ola Bini pointed out that I'm not clearing the prerequisites. Clearing the prerequisites is something you can do without any modifications to rake.
Rake::Task[:task_name].prerequisites.clearAlso, if you want prerequisites cleared as part of overwrite or abandon, you can easily add it to both tasks.
@actions.clear
prerequisites.clear
enhance(&block)
end
prerequisites.clear
@actions.clear
end
endLabels: rake
Sunday, February 17, 2008
Shared Ruby Code
It's no secret that I think plugins are unnecessary. Unfortunately, they are also popular, largely due to to how easy it is to create a plugin and put it in your Rails project.
It's also easy to create a new gem. In 2006 Dr. Nic released the newgem gem. This was a great step in 2006, but it's 2008 now and it doesn't feel like we've moved much in the way of shared ruby code.
We're all to blame.
The vast majority of Ruby work is working with Rails. I do believe the next step for RubyGems is to play seamlessly with Rails. Since RubyGems are great for any Ruby code, it doesn't make sense to add Rails specifics to RubyGems. However, RubyGems could be more opinionated about how gems are structured. Right now, RubyGems library code can be structured in almost any way that you'd like. If the structure followed a stricter convention then auto gem loading code could be written for Rails.
I like newgem, but I think the features provided by newgem should actually be part of RubyGems itself. If the gem command could create a project skeleton it could be opinionated about the file structure without creating a larger barrier to entry.
There is another change that needs to happen. Everyone knows that you should vendor everything for your Rails projects. But, there's no single step to go from uninstalled gem to vendored gem. The path is generally to install it locally and them unpack it in whatever folder you've created that gets autoloaded.
I'm pretty sure a remote unpack is fairly simple, which is why we are all to blame. RubyGems is an open source project. Anyone can contribute patches to make it better.
Lastly, if gems become the standard way to share Ruby/Rails code then we'll need a few rake tasks added to Rails. For example, who wouldn't want a rake command to upgrade to a later version of a gem.
I'm hoping 2008 is the year that shared ruby code becomes synonymous with RubyGems and the year Rails Plugins become unnecessary.
It's also easy to create a new gem. In 2006 Dr. Nic released the newgem gem. This was a great step in 2006, but it's 2008 now and it doesn't feel like we've moved much in the way of shared ruby code.
We're all to blame.
The vast majority of Ruby work is working with Rails. I do believe the next step for RubyGems is to play seamlessly with Rails. Since RubyGems are great for any Ruby code, it doesn't make sense to add Rails specifics to RubyGems. However, RubyGems could be more opinionated about how gems are structured. Right now, RubyGems library code can be structured in almost any way that you'd like. If the structure followed a stricter convention then auto gem loading code could be written for Rails.
I like newgem, but I think the features provided by newgem should actually be part of RubyGems itself. If the gem command could create a project skeleton it could be opinionated about the file structure without creating a larger barrier to entry.
There is another change that needs to happen. Everyone knows that you should vendor everything for your Rails projects. But, there's no single step to go from uninstalled gem to vendored gem. The path is generally to install it locally and them unpack it in whatever folder you've created that gets autoloaded.
I'm pretty sure a remote unpack is fairly simple, which is why we are all to blame. RubyGems is an open source project. Anyone can contribute patches to make it better.
Lastly, if gems become the standard way to share Ruby/Rails code then we'll need a few rake tasks added to Rails. For example, who wouldn't want a rake command to upgrade to a later version of a gem.
I'm hoping 2008 is the year that shared ruby code becomes synonymous with RubyGems and the year Rails Plugins become unnecessary.
Friday, February 15, 2008
Implementing an internal DSL in Ruby
On Christmas Day 2007 I released the 0.0.1 version of expectations. Expectations is the result of several months of designing what I wanted the Domain Specific Language (DSL) to look like, and a few days of making it executable. To design the DSL, I created what I was looking for without worrying about implementation. This entry is going to focus on what it took to take the DSL from looking good to actually executing.
The following code is an example of what I decided I wanted the DSL to look like (for a state based test, expectations also has support for behavior based testing).
When making a DSL executable I generally begin by building up objects with the values expressed in the DSL. For example, the above code creates a test suite containing 1 expectation where the expected value is :expected and the actual value can be determined by evaluating the block. While designing expectations I tested it using expectations, but that's probably more complex than is necessary for this example, so I'll simply print values instead.
The first step is ensuring that an Expectations::Suite is created when Expectations is called with a block. The following code demonstrates that an Expectations::Suite is created successfully. The code executes since the block given to Expectations is never executed.
note: Expectations::Suite is a singleton so that it can be appended to multiple times and later used for execution without being stored within another object.
The next step is to verify the number of expectations the suite stores for later execution. The following code verifies that one object is stored in the expectations array.
The final step is to create an object for each expectation and verify the values of both the expected and the actual.
note: expectations actually defers determining the value of actual until the suite is executed, but that's outside the scope of this example.
The bottom two lines of the last example show that the Expectations::Expectation instance is being populated successfully.
That's it, at this point the DSL can successfully be parsed. Of course, making the expectations run to verify that the values match still needs to be done, but that's no different than working with any plain old ruby code. The DSL can be evaluated and the framework can work with the objects that have been created.
As the example demonstrates, designing an internal DSL in Ruby is very easy largely due to open classes, class methods, and the ability to eval. The above code for evaluating the DSL is less than 30 lines of code.
The following code is an example of what I decided I wanted the DSL to look like (for a state based test, expectations also has support for behavior based testing).
Expectations do
expect :expected do
:expected
end
endWhen making a DSL executable I generally begin by building up objects with the values expressed in the DSL. For example, the above code creates a test suite containing 1 expectation where the expected value is :expected and the actual value can be determined by evaluating the block. While designing expectations I tested it using expectations, but that's probably more complex than is necessary for this example, so I'll simply print values instead.
The first step is ensuring that an Expectations::Suite is created when Expectations is called with a block. The following code demonstrates that an Expectations::Suite is created successfully. The code executes since the block given to Expectations is never executed.
; end
include Singleton
end
Expectations::Suite.instance
end
Expectations do
expect :expected do
:expected
end
end
Expectations::Suite.instance # => #<Expectations::Suite:0x1d4fc>
note: Expectations::Suite is a singleton so that it can be appended to multiple times and later used for execution without being stored within another object.
The next step is to verify the number of expectations the suite stores for later execution. The following code verifies that one object is stored in the expectations array.
; end
include Singleton
attr_accessor :expectations
self.expectations = []
end
expectations << :expectation
end
end
Expectations::Suite.instance.instance_eval(&block)
end
Expectations do
expect :expected do
:expected
end
end
Expectations::Suite.instance # => #<Expectations::Suite:0x1c82c @expectations=[:expectation]>
Expectations::Suite.instance.expectations.size # => 1
The final step is to create an object for each expectation and verify the values of both the expected and the actual.
; end
include Singleton
attr_accessor :expectations
self.expectations = []
end
expectations << Expectations::Expectation.new(expected, actual)
end
end
attr_accessor :expected, :actual
self.expected, self.actual = expected, actual.call
end
end
Expectations::Suite.instance.instance_eval(&block)
end
Expectations do
expect :expected do
:expected
end
end
Expectations::Suite.instance # => #<Expectations::Suite:0x1b184 @expectations=[#<Expectations::Expectation:0x1b0e4 @expected=:expected, @actual=:expected>]>
Expectations::Suite.instance.expectations.size # => 1
Expectations::Suite.instance.expectations.first.expected # => :expected
Expectations::Suite.instance.expectations.first.actual # => :expected
note: expectations actually defers determining the value of actual until the suite is executed, but that's outside the scope of this example.
The bottom two lines of the last example show that the Expectations::Expectation instance is being populated successfully.
That's it, at this point the DSL can successfully be parsed. Of course, making the expectations run to verify that the values match still needs to be done, but that's no different than working with any plain old ruby code. The DSL can be evaluated and the framework can work with the objects that have been created.
As the example demonstrates, designing an internal DSL in Ruby is very easy largely due to open classes, class methods, and the ability to eval. The above code for evaluating the DSL is less than 30 lines of code.
Labels: DSL, internal DSL, ruby
Thursday, February 14, 2008
Pair Programming thoughts
It's very rare that I go to a client and pair programming is not an issue. Developers love their desks, personal keyboard configurations, and music. I like that stuff too; unfortunately, any personal space benefits or keyboard configurations cannot make you more productive than a team of effectively pairing developers of the same skill level. I can't prove that, but you can't prove that I'm wrong either. Of course, to begin (a futile) attempt to prove which is more effective we would need to decide on what we were measuring. Building software isn't only about the number of features delivered, it's also about the quality of the software delivered. It's also about having a team that can maintain an application as (inevitable) turn over occurs.
That's what's so great about Pair Programming, it improves the quality of software while spreading knowledge and best practices at the same time. Everyone benefits from the sharing of knowledge from small things such as efficiency gaining key short cuts to large things such as understanding the big picture of the project and spreading common patterns throughout the system.
Adopting pair programming isn't as hard as you might think. In May of 2005 I wrote that setup is important. I was wrong, setup is not important, it's the most important step towards adoption. Anyone looking to bring pair programming into their organization doesn't need to do much. Get tables large enough for several team members to sit in the same area comfortably. Get the latest and greatest desktops for the pairing area. (My current client got me a beautiful Mac Pro). Get a desktop with dual video out if possible, or get a video splitter. Buy 2 keyboards, 2 mice, and 2 (large) monitors for each pairing station. If the pairing stations are the best systems to work on, developers will naturally migrate to those machines.
70% of ensuring adoption success is work station set up.
I suggest not mandating pairing, let it happen naturally. I also suggest allowing the developers to keep their desks. Everyone needs an area to escape to and read slashdot when they need a break. If you just bought everyone 30 inch monitors you may need to move those to the pairing stations and give everyone small monitors for their personal desks. The idea is to allow people to have their space, but encourage them to use the pairing stations by providing a superior set up.
20% of ensuring adoption is ping pong pair programming.
Ping pong pair programming is fantastic at keeping everyone involved and given the opportunity to drive.
The remaining 10% will vary by organization, but if you handle the first 90% the rest should just fall into place.
That's what's so great about Pair Programming, it improves the quality of software while spreading knowledge and best practices at the same time. Everyone benefits from the sharing of knowledge from small things such as efficiency gaining key short cuts to large things such as understanding the big picture of the project and spreading common patterns throughout the system.
Adopting pair programming isn't as hard as you might think. In May of 2005 I wrote that setup is important. I was wrong, setup is not important, it's the most important step towards adoption. Anyone looking to bring pair programming into their organization doesn't need to do much. Get tables large enough for several team members to sit in the same area comfortably. Get the latest and greatest desktops for the pairing area. (My current client got me a beautiful Mac Pro). Get a desktop with dual video out if possible, or get a video splitter. Buy 2 keyboards, 2 mice, and 2 (large) monitors for each pairing station. If the pairing stations are the best systems to work on, developers will naturally migrate to those machines.
70% of ensuring adoption success is work station set up.
I suggest not mandating pairing, let it happen naturally. I also suggest allowing the developers to keep their desks. Everyone needs an area to escape to and read slashdot when they need a break. If you just bought everyone 30 inch monitors you may need to move those to the pairing stations and give everyone small monitors for their personal desks. The idea is to allow people to have their space, but encourage them to use the pairing stations by providing a superior set up.
20% of ensuring adoption is ping pong pair programming.
Ping pong pair programming is fantastic at keeping everyone involved and given the opportunity to drive.
The remaining 10% will vary by organization, but if you handle the first 90% the rest should just fall into place.
Labels: pair programming
Tests reflect code quality
As a consultant for ThoughtWorks I get to see a lot of different codebases. A pattern that I've seen in almost every codebase is that you can usually tell how well a codebase is written by the tests that cover the codebase.
The first indicator of quality is how covered the code is. In my experience the coverage percentage is generally representative of the confidence the business has in the software. Unfortunately, a low coverage percentage can show probable lack of quality, but a high coverage percentage does not ensure high quality. If you have less than 80% coverage, you probably have software that is broken in places. However, if you have better than 80% coverage it only means that you have better than 80% coverage. Again, coverage does not ensure quality.
The second indicator of quality is how easily new tests can be added to the existing suite. Working with someone for 10 minutes is usually enough time to see how much effort is required to add new tests (and features). If the developers believe in TDD, but are consistently frustrated with the level of effort it takes to write a test, you probably have a problem. Of course, if the developers do not believe in TDD, raising the barrier to entry with code that is hard to test only compounds the problem.
New tests can also be fairly easy to write, but very hard for new team members to understand. For example, special test superclasses designed to minimize the pain are also warning signs that the system may be unnecessarily complex.
Every project has a ratio of lines of application code and lines of test code. This ratio is effected by the language decision. The ratio does not need to be 1:1. In fact, language isn't the only factor in determining what the correct ratio is. For example, an internal application for a cafe that is not mission critical should not be as thoroughly tested as the command system for NASA's shuttles. The point is not to set some language standard ratio, but every project should recognize that if their desired ratio is 1:4 and it takes 8 times longer to write the tests than it takes to write the feature, something is wrong. The time to implement a feature versus the time to write the tests should be roughly equivalent to the application loc to test loc ratio.
While you do not need to strive for a 1:1 ratio, the farther you stray from 1:1 the more you should be looking at the reason for doing so. If your language requires a larger number, so be it. If your project demands more than the normal amount of tests, fair enough. But, you should always be on the look out for your ratio growing unjustifiably. It's always good to have at least one skeptic who believes that 1:1 is what you should be striving for. If you need 8+ lines of setup code to test a one line method you have a problem, and it pays to have a guy who is not afraid to point that out.
Conversely, I've rarely found a codebase that was easy to test that wasn't well written. I believe this is true largely because systems that are easily testable are generally very loosely coupled. Generally, loosely coupled applications are written fairly well to begin with. However, even if a component is poorly written, it will be easy to refactor or rewrite since it can be done independently of the rest of the system.
Along the same lines, when people ask me how to improve a legacy, monster codebase the first thing I recommend is breaking as many dependencies as possible. Once an application is broken into components it's possible to make educated decisions on what to refactor and what to redo from scratch.
Getting tests around a legacy codebase is always painful; however, it can also give you direction on where the application can be logically broken up. While writing tests for a legacy codebase you should keep track of dependencies that need to be setup, mocked or stubbed but have nothing to do with the current functionality you are focusing on. In general, these are the pieces that should be broken out into components that are easily stubbed (ideally in 1 or 0 lines).
Well written tests generally require loosely coupled, single responsibility code. The same type of code generally increases the quality of the codebase as a whole. Thus, tests that are readable and maintainable generally ensure the same type of codebase.
The first indicator of quality is how covered the code is. In my experience the coverage percentage is generally representative of the confidence the business has in the software. Unfortunately, a low coverage percentage can show probable lack of quality, but a high coverage percentage does not ensure high quality. If you have less than 80% coverage, you probably have software that is broken in places. However, if you have better than 80% coverage it only means that you have better than 80% coverage. Again, coverage does not ensure quality.
The second indicator of quality is how easily new tests can be added to the existing suite. Working with someone for 10 minutes is usually enough time to see how much effort is required to add new tests (and features). If the developers believe in TDD, but are consistently frustrated with the level of effort it takes to write a test, you probably have a problem. Of course, if the developers do not believe in TDD, raising the barrier to entry with code that is hard to test only compounds the problem.
New tests can also be fairly easy to write, but very hard for new team members to understand. For example, special test superclasses designed to minimize the pain are also warning signs that the system may be unnecessarily complex.
Every project has a ratio of lines of application code and lines of test code. This ratio is effected by the language decision. The ratio does not need to be 1:1. In fact, language isn't the only factor in determining what the correct ratio is. For example, an internal application for a cafe that is not mission critical should not be as thoroughly tested as the command system for NASA's shuttles. The point is not to set some language standard ratio, but every project should recognize that if their desired ratio is 1:4 and it takes 8 times longer to write the tests than it takes to write the feature, something is wrong. The time to implement a feature versus the time to write the tests should be roughly equivalent to the application loc to test loc ratio.
While you do not need to strive for a 1:1 ratio, the farther you stray from 1:1 the more you should be looking at the reason for doing so. If your language requires a larger number, so be it. If your project demands more than the normal amount of tests, fair enough. But, you should always be on the look out for your ratio growing unjustifiably. It's always good to have at least one skeptic who believes that 1:1 is what you should be striving for. If you need 8+ lines of setup code to test a one line method you have a problem, and it pays to have a guy who is not afraid to point that out.
Conversely, I've rarely found a codebase that was easy to test that wasn't well written. I believe this is true largely because systems that are easily testable are generally very loosely coupled. Generally, loosely coupled applications are written fairly well to begin with. However, even if a component is poorly written, it will be easy to refactor or rewrite since it can be done independently of the rest of the system.
Along the same lines, when people ask me how to improve a legacy, monster codebase the first thing I recommend is breaking as many dependencies as possible. Once an application is broken into components it's possible to make educated decisions on what to refactor and what to redo from scratch.
Getting tests around a legacy codebase is always painful; however, it can also give you direction on where the application can be logically broken up. While writing tests for a legacy codebase you should keep track of dependencies that need to be setup, mocked or stubbed but have nothing to do with the current functionality you are focusing on. In general, these are the pieces that should be broken out into components that are easily stubbed (ideally in 1 or 0 lines).
Well written tests generally require loosely coupled, single responsibility code. The same type of code generally increases the quality of the codebase as a whole. Thus, tests that are readable and maintainable generally ensure the same type of codebase.
Labels: testing
Tuesday, February 12, 2008
Testing: Expect literals
When writing a state based test you must specify an expected value and an actual value. Whether you are using RSpec, Test::Unit, or any other framework, there is always an expected and an actual value.
Expected values (:the_hotel in our example) can be any object; however, using literals (strings, integers, symbols) for expected values is advantageous for readability and traceability. To be clear, the expected value should be the literal itself, not a variable holding a literal that was created earlier in the test.
Tests that use literals are easier to read because they allow you to focus on the actual value, since the expected value is simply the literal. The following 3 tests verify the same thing, but the 3rd example uses a literal for the expected value. Since the 3rd example uses a literal, it's easy to see what the value of the actual should be without scanning the body of the test.
note: these tests are fairly clean by design, the value of expecting literals can be even greater as the tests become more complex, thus harder to follow.
Expecting literals also makes your life easier when a test breaks. If neither your expected or actual values are literals, you will need to determine the value of both of them when a test breaks. Conversely, if your expected is a literal, only the actual value will need to be traced when a test is broken. For example the following tests both verify the result of the popularity method; however, the 2nd version is explicit in the value in expects.
In the above example, if the second test breaks it's because the calculation changed in some way. When the calculation changes, it's valuable to get feedback that it changed (by way of a breaking test). The first test doesn't use any literals, but it will not break if the value of WEIGHT is changed. Some people might consider this valuable or perhaps even more maintainable, I'd consider it a bug waiting to happen.
In reality, it's not likely that the programmer is responsible for setting the value of WEIGHT. Most businesses would have a subject matter expert that would give the value of weight and express (via requirements) the formula for popularity. The programmer would write the actual Ruby code, but using literal numbers for the expected value would allow the programmer to verify the functionality with the business. If in the future the business wants to change the value of WEIGHT, the programmers and the business could first change the tests to represent the new expected values, and then they could change the implementation. When you change code, it's always valuable to have a breaking test. Even if the business didn't sit with the programmer, the programmer could request the expected values as acceptance criteria. The tests will be easier to read, but they will also protect the business from a mistaken change to WEIGHT.
There are times when it's not possible to use a literal as the expected value. For example, when you need to assert a time or date you'll need to use a Time or Date object respectively. You can handle these situations in various ways. I prefer to use expected values that are as close to literals as I can get. This means using a literal as an argument to a method or calling to_s on the actual and expecting a string. The following two examples illustrate both of these techniques.
It's not always possible to use literals for expected values, but if you strive to use literals whenever possible your tests will be more readable and traceable in the future.
# RSpec
presenter.hotel.should == :the_hotel
# Test::Unit
assert_equal :the_hotel, presenter.hotelExpected values (:the_hotel in our example) can be any object; however, using literals (strings, integers, symbols) for expected values is advantageous for readability and traceability. To be clear, the expected value should be the literal itself, not a variable holding a literal that was created earlier in the test.
Tests that use literals are easier to read because they allow you to focus on the actual value, since the expected value is simply the literal. The following 3 tests verify the same thing, but the 3rd example uses a literal for the expected value. Since the 3rd example uses a literal, it's easy to see what the value of the actual should be without scanning the body of the test.
EXPECTED = :expected
expected = :expected
suite = Expectations::Suite.new
suite.expect(1) {2 }
suite.expect(expected) {2 }
assert_equal expected, suite.expectations_for(__LINE__ - 1).first.expected
end
suite = Expectations::Suite.new
suite.expect(1) {2 }
suite.expect(EXPECTED) {2 }
assert_equal EXPECTED, suite.expectations_for(__LINE__ - 1).first.expected
end
suite = Expectations::Suite.new
suite.expect(1) {2 }
suite.expect(:expected) {2 }
assert_equal :expected, suite.expectations_for(__LINE__ - 1).first.expected
end
endnote: these tests are fairly clean by design, the value of expecting literals can be even greater as the tests become more complex, thus harder to follow.
Expecting literals also makes your life easier when a test breaks. If neither your expected or actual values are literals, you will need to determine the value of both of them when a test breaks. Conversely, if your expected is a literal, only the actual value will need to be traced when a test is broken. For example the following tests both verify the result of the popularity method; however, the 2nd version is explicit in the value in expects.
WEIGHT = 1.14
@votes = votes
end
WEIGHT * @votes * @votes
end
end
votes = 2
assert_equal WEIGHT * votes * votes, Topic.new(votes).popularity
end
assert_equal 4.56, Topic.new(2).popularity
end
endIn the above example, if the second test breaks it's because the calculation changed in some way. When the calculation changes, it's valuable to get feedback that it changed (by way of a breaking test). The first test doesn't use any literals, but it will not break if the value of WEIGHT is changed. Some people might consider this valuable or perhaps even more maintainable, I'd consider it a bug waiting to happen.
In reality, it's not likely that the programmer is responsible for setting the value of WEIGHT. Most businesses would have a subject matter expert that would give the value of weight and express (via requirements) the formula for popularity. The programmer would write the actual Ruby code, but using literal numbers for the expected value would allow the programmer to verify the functionality with the business. If in the future the business wants to change the value of WEIGHT, the programmers and the business could first change the tests to represent the new expected values, and then they could change the implementation. When you change code, it's always valuable to have a breaking test. Even if the business didn't sit with the programmer, the programmer could request the expected values as acceptance criteria. The tests will be easier to read, but they will also protect the business from a mistaken change to WEIGHT.
There are times when it's not possible to use a literal as the expected value. For example, when you need to assert a time or date you'll need to use a Time or Date object respectively. You can handle these situations in various ways. I prefer to use expected values that are as close to literals as I can get. This means using a literal as an argument to a method or calling to_s on the actual and expecting a string. The following two examples illustrate both of these techniques.
assert_equal Date.parse("2/14/2007"), ValentinesDay.last_year
end
assert_equal "2007-02-14", ValentinesDay.last_year.to_s
end
endIt's not always possible to use literals for expected values, but if you strive to use literals whenever possible your tests will be more readable and traceable in the future.
Labels: testing, Testing Refactorings
Saturday, February 09, 2008
Designing a Domain Specific Language
I've worked on and with various Domain Specific Languages (DSLs), but I've found designing them to be the same process no matter what type of DSL is being created.
In March of 2005 I started one of the more technically interesting projects of my life. ThoughtWorks had been hired to design a system that would allow the director of finance for the client bank to edit, test, and deploy the business rules of the system without any assistance from IT. It was a bold step, but the return on investment was realized within weeks, not months (or worse, years).
One of the major challenges of designing this type of system is determining what the language should look like. Being new to the idea, we started out by telling the business what was possible. That was a mistake. The early versions of the language were easy for IT to work with, but were clunky for the business. The result was clearly suboptimal. As we continued evolving the language for new features we took a different approach, we asked the director what he wanted the language to look like. The more he shaped the language the easier it was for him to take ownership of the domain logic.
We figured out that we didn't need to write the domain logic in our language, we needed to make the domain language executable. The resulting project was a huge success, which led to me coming up with the term Business Natural Languages and presenting the idea at several conferences*.
Almost 3 years later, I decided I needed a new framework for unit testing. The primary motivation for designing a new framework was my desire for a more syntactically elegant way of defining tests. This time I knew I wanted to design what the language looked like and then worry about making it work at a later time. In fact, when writing expectations, I had a small suite of example tests before I had any way to execute them.
None of the languages I've helped design are flawless. But, I've seen significantly more success with the languages that were designed by a subject matter expert concerned with the language, instead of a language designer who was concerned with how to make the language execute.
* If you'd like to catch the Business Natural Language presentation you can see it at QCon London 2008 or rupy.eu 2008.
In March of 2005 I started one of the more technically interesting projects of my life. ThoughtWorks had been hired to design a system that would allow the director of finance for the client bank to edit, test, and deploy the business rules of the system without any assistance from IT. It was a bold step, but the return on investment was realized within weeks, not months (or worse, years).
One of the major challenges of designing this type of system is determining what the language should look like. Being new to the idea, we started out by telling the business what was possible. That was a mistake. The early versions of the language were easy for IT to work with, but were clunky for the business. The result was clearly suboptimal. As we continued evolving the language for new features we took a different approach, we asked the director what he wanted the language to look like. The more he shaped the language the easier it was for him to take ownership of the domain logic.
We figured out that we didn't need to write the domain logic in our language, we needed to make the domain language executable. The resulting project was a huge success, which led to me coming up with the term Business Natural Languages and presenting the idea at several conferences*.
Almost 3 years later, I decided I needed a new framework for unit testing. The primary motivation for designing a new framework was my desire for a more syntactically elegant way of defining tests. This time I knew I wanted to design what the language looked like and then worry about making it work at a later time. In fact, when writing expectations, I had a small suite of example tests before I had any way to execute them.
None of the languages I've helped design are flawless. But, I've seen significantly more success with the languages that were designed by a subject matter expert concerned with the language, instead of a language designer who was concerned with how to make the language execute.
* If you'd like to catch the Business Natural Language presentation you can see it at QCon London 2008 or rupy.eu 2008.
Labels: DSL
ThoughtWorks DSL Podcast (part 2)
Part 2 of the DSL podcast Martin Fowler, Rebecca Parsons, Neal Ford and I participated in is now available. The podcast is once again being released as part of the ThoughtWorks IT Matters series and is available at ThoughtWorks: What we say.
Labels: DSL, podcast, ThoughtWorks
Friday, February 08, 2008
Subversion: Rolling back revisions
Earlier this week someone mistakenly checked in some experimental code and wanted to back that code out. If you've never needed to do this, you might think that Subversion gives you a command to make this happen easily, it doesn't. In fact, I've found that to be a fairly common misconception.
There may be easier ways, but the following steps are the ones I take to roll back a specific revision.
Find the revision that you wish to back out. This can be done with various tools, but 'svn log --limit 10' will probably do the trick. Once you have the offending revision you are ready to get started. (Revision X in the example is the revision that needs to be rolled back and revision Y is the revision that came immediately before X [i.e. X-1])
Again, there is probably an easier way to do this (which is part of my motivation for writing this entry), and obviously this solution depends on having 'patch' available.
There may be easier ways, but the following steps are the ones I take to roll back a specific revision.
Find the revision that you wish to back out. This can be done with various tools, but 'svn log --limit 10' will probably do the trick. Once you have the offending revision you are ready to get started. (Revision X in the example is the revision that needs to be rolled back and revision Y is the revision that came immediately before X [i.e. X-1])
- svn diff -rX:Y > a.patch
- patch -p0 < a.patch
- svn commit
Again, there is probably an easier way to do this (which is part of my motivation for writing this entry), and obviously this solution depends on having 'patch' available.
Labels: patch, subversion
Monday, February 04, 2008
Static typing considered harmful
Given a good test suite the return on investment simply does not justify the use of static typing.
The obvious answer is to write more tests. As you write more tests, you can verify independent pieces of the application and integration of the different pieces of the application. Currently, you know that your code compiles; but that is to general to be considered sufficient for verifying system behavior. Specific tests can verify specific behavior. You may start out testing that 1 + 1 is a number, but eventually you will be better off testing that 1 + 1 is 2. The test that 1 + 1 is a number may have given you some confidence that your application was working, but once you add a test to verify that 1 + 1 is 2, the test that verifies that 1 + 1 is a number no longer provides you with any additional confidence. Since the test that verifies 1 + 1 is a number no longer provides any value, it can safely be removed.
In 5 years, we'll view compilation as the weakest form of unit testing. -- Stuart HallowayType verification provides very little confidence that an application works. The little confidence it does provide comes at the cost of being confined by the type system. Verifying types is better than testing nothing at all. But, only verifying types is insufficient for application development.
The obvious answer is to write more tests. As you write more tests, you can verify independent pieces of the application and integration of the different pieces of the application. Currently, you know that your code compiles; but that is to general to be considered sufficient for verifying system behavior. Specific tests can verify specific behavior. You may start out testing that 1 + 1 is a number, but eventually you will be better off testing that 1 + 1 is 2. The test that 1 + 1 is a number may have given you some confidence that your application was working, but once you add a test to verify that 1 + 1 is 2, the test that verifies that 1 + 1 is a number no longer provides you with any additional confidence. Since the test that verifies 1 + 1 is a number no longer provides any value, it can safely be removed.
Labels: dynamic typing, static typing
Sunday, February 03, 2008
Behavior Based Testing
Behavior based testing is when you expect specific interactions to occur between objects when certain methods are executed. Behavior based testers (Mockists) utilize mocks to verify expected interaction between objects of a system. Behavior based testing is about specifying how the system should behave rather than specifying the expected result of running the system.
Behavior based testing has been criticized by state based advocates because it can cause test failures when implementation changes are made. In it's worst form, behavior based testing can be fragile. However, behavior based tests that avoid high implementation specification can effectively verify behavior without sacrificing maintainability.
The following test verifies the interaction between a Hotel instance and the ReservationService without the need to specify any other implementation detail. A change to book_for method will only cause the test to fail if the interaction between the two objects is changed, in which case a failing test is appropriate.
Behavior based tests are easy to spot because they focus on mock verification to ensure a system behaves as expected.
Developers who generally rely on behavior based tests can be considered Mockists. Martin Fowler has a great article, Mocks Aren't Stubs, that expands greatly on the ideas that are briefly mentioned here.
Behavior based testing has been criticized by state based advocates because it can cause test failures when implementation changes are made. In it's worst form, behavior based testing can be fragile. However, behavior based tests that avoid high implementation specification can effectively verify behavior without sacrificing maintainability.
The following test verifies the interaction between a Hotel instance and the ReservationService without the need to specify any other implementation detail. A change to book_for method will only cause the test to fail if the interaction between the two objects is changed, in which case a failing test is appropriate.
# implementation
end
attr_accessor :booked
reservation = ReservationService.reserve_for(customer, self)
self.booked = true
MaidService.notify(reservation) if reservation.tomorrow?
VipService.notify(reservation) if reservation.for_vip?
reservation.confirmation_number
end
end
room = HotelRoom.new
ReservationService.expects(:reserve_for).with(:customer, room).returns(stub_everything)
room.book_for(:customer)
end
endBehavior based tests are easy to spot because they focus on mock verification to ensure a system behaves as expected.
Developers who generally rely on behavior based tests can be considered Mockists. Martin Fowler has a great article, Mocks Aren't Stubs, that expands greatly on the ideas that are briefly mentioned here.
Labels: behavior based, testing
State Based Testing
State based testing is when you exercise one or many methods of an object and then assert the expected state of the object. Many test-driven developers prefer state based tests because they specify as little implementation detail as possible. Tests that specify no implementation details should continue to pass even if the implementation of the methods being tested are changed. The following state based delegation test will continue to be valid for either of the following implementations.
State based tests can contain stubs, but they are generally avoided where possible. State based tests are easy to spot because they rely on assertions to verify the state of the application.
Developers who generally rely on state based tests can be considered Classicists. Martin Fowler has a great article, Mocks Aren't Stubs, that expands greatly on the ideas that are briefly mentioned here.
employee = Employee.new("Shane Harvie")
assert_equal "Shane Harvie", ShowPresenter.new(employee).name
end
end
(:name); end
(:employee)
employee.name
end
end
(:employee)
extend Forwardable
def_delegator :employee, :name
endState based tests can contain stubs, but they are generally avoided where possible. State based tests are easy to spot because they rely on assertions to verify the state of the application.
Developers who generally rely on state based tests can be considered Classicists. Martin Fowler has a great article, Mocks Aren't Stubs, that expands greatly on the ideas that are briefly mentioned here.
Labels: state based, testing
Saturday, February 02, 2008
Ruby: Did TDD make Ruby a viable option?
Some people ask if Test Driven Development (TDD) is what caused Ruby to become popular. Other people smarter than me claim that TDD is in fact the reason that dynamic languages are now viable options.
I'm sorry, but I disagree.
Can you write a non-trivial application in Ruby without tests and have confidence in it? No. Can you write a non-trivial application in Java without tests and have confidence in it? Again, no.
A compiler lets you know that some things are correct, but should not give you confidence that your application behaves as expected. A trivial example is, I need to know that the calculate_tax (that's calculateTax for you Java fans) method returns 56, not that the result is a Fixnum (or Integer in Java).
As much as it should be, TDD is not mainstream. There's no TDD Conference, but RailsConf sells out every year.
I would actually credit David Heinemeier Hansson with making Ruby viable by creating a framework that drove mass adoption. But, I bet of you looked at the majority of Rails applications you would find empty test folders (or only the generated tests, which are never run). I'm quite sure that's true because I expect the conferences to attract the best of the Ruby developers, and several of the people I talk to at those conferences "simply don't have time" to write tests.
I'm sorry, but I disagree.
Can you write a non-trivial application in Ruby without tests and have confidence in it? No. Can you write a non-trivial application in Java without tests and have confidence in it? Again, no.
A compiler lets you know that some things are correct, but should not give you confidence that your application behaves as expected. A trivial example is, I need to know that the calculate_tax (that's calculateTax for you Java fans) method returns 56, not that the result is a Fixnum (or Integer in Java).
As much as it should be, TDD is not mainstream. There's no TDD Conference, but RailsConf sells out every year.
I would actually credit David Heinemeier Hansson with making Ruby viable by creating a framework that drove mass adoption. But, I bet of you looked at the majority of Rails applications you would find empty test folders (or only the generated tests, which are never run). I'm quite sure that's true because I expect the conferences to attract the best of the Ruby developers, and several of the people I talk to at those conferences "simply don't have time" to write tests.
Testing: Mocha's stub_everything method
The Mocha mocking framework provides a method, stub_everything, that returns an object that will respond 'nil' to any message sent to it. The stub_everything method is indispensable for creating an object that is needed for testing, but the interactions are completely unimportant.
For example, in the following test the ReservationService::reserve_for method needs to return a duck that responds to the same methods that are defined on the (not shown) Reservation class. However, the focus of the test has nothing to do with the behavior of the reservation duck, so stub_everything is the perfect duck because it requires no additional set up and responds to all the methods required without error.
The stub_everything method is also nice because it can take parameters. For example you may want to test that the book_for method returns a confirmation number. The following test specifies the value of the confirmation_number value, but it uses stub_everything so that each other method call will return nil (without causing an error).
Another usage of stub_everything that might not be immediately apparent is that it can be used as both a stub and a mock. The following test creates a stub and then sets an expectation on it. As a result, the expectation is verified (which is the focus of the test) and all other calls to the same object simply do nothing.
Using the stub_everything method of mocha can remove noise from tests and also make them more robust by ignoring all messages that are unimportant to the current test.
For example, in the following test the ReservationService::reserve_for method needs to return a duck that responds to the same methods that are defined on the (not shown) Reservation class. However, the focus of the test has nothing to do with the behavior of the reservation duck, so stub_everything is the perfect duck because it requires no additional set up and responds to all the methods required without error.
# implementation
end
# implementation
end
# implementation
end
reservation = ReservationService.reserve_for(customer, self)
MaidService.notify(reservation) if reservation.tomorrow?
VipService.notify(reservation) if reservation.for_vip?
reservation.confirmation_number
end
end
room = HotelRoom.new
ReservationService.expects(:reserve_for).with(:customer, room).returns(stub_everything)
room.book_for(:customer)
end
endThe stub_everything method is also nice because it can take parameters. For example you may want to test that the book_for method returns a confirmation number. The following test specifies the value of the confirmation_number value, but it uses stub_everything so that each other method call will return nil (without causing an error).
# implementation
end
# implementation
end
# implementation
end
reservation = ReservationService.reserve_for(customer, self)
MaidService.notify(reservation) if reservation.tomorrow?
VipService.notify(reservation) if reservation.for_vip?
reservation.confirmation_number
end
end
reservation = stub_everything(:confirmation_number => "confirmation number")
ReservationService.stubs(:reserve_for).returns(reservation)
assert_equal "confirmation number", HotelRoom.new.book_for(:customer)
end
endAnother usage of stub_everything that might not be immediately apparent is that it can be used as both a stub and a mock. The following test creates a stub and then sets an expectation on it. As a result, the expectation is verified (which is the focus of the test) and all other calls to the same object simply do nothing.
hotel = stub_everything
hotel.expects(:name=).with "Marriott"
Hotel.stubs(:new).returns hotel
Hotel.parse("Marriott||")
end
end
hotel = self.new
hotel.name, hotel.location, hotel.capacity = attributes.split("|")
hotel
end
endUsing the stub_everything method of mocha can remove noise from tests and also make them more robust by ignoring all messages that are unimportant to the current test.
Labels: mocha, stub, testing, Testing Refactorings
Testing: High Implementation Specification Smell
At the end of my last blog entry, I touched briefly on the testing smell: High Implementation Specification. I got a fair amount of good feedback and based on several requests I decided it was worth creating a blog entry focused entirely on the smell.
Well written tests specify as little implementation detail as possible.
Developers new to mocking and stubbing tend to overuse the capabilities provided by mocking frameworks. I consider each call to a method of a mocking framework to be 1 implementation specification point. While using mocha I count a point for each of: stub, stub_everything, stubs, mock, expects, with, and returns. Any test that has an implementation specification score of 5 or more has a high implementation specification smell.
High Implementation Specification can stem from misusing mocking frameworks in various ways such as verifying parameters while stubbing methods, returning values that are never used, and specifying every interaction instead of using an object that will respond to any message.
Most mock frameworks provide the ability to verify the parameters sent to a mocked or stubbed method. When setting up a mock expectation, specifying the parameters can be useful for verifying interaction; however, when stubbing methods that are not the focus of the test, adding the additional parameter verification makes the test unnecessarily brittle.
The following code provides an example where the MainService notify method stub could verify the parameters, but the test is focused on testing the ReservationService. Given that the test is verifying the ReservationService there's no need for it to return a value and there's no need to verify the parameters sent to MaidService.notify. If the test did specify the parameters send to MaidService it would fail if those parameters changed. Since the test is verifying ReservationService, it would be disappointing if it failed due to a change in how MadService is called.
Returning a value from a mocked method can be necessary, but should be avoided in general for two reasons: Returning a value signifies to someone reading a test that the value is necessary for some reason. Returning a value that is later verified with an assertion can cause false positives.
In the above example, the ReservationService does not provide a return value because that value isn't used anywhere within the test. If you read the above test and saw that a return value was specified you would probably want to spend time finding where and why it was used. Worse, if the above test returned a value and then later asserted equality the test would pass, but the implementation could change the return value in the future and the test would not break.
At times a mock or stub must be exercised in various ways within one test due to how the original method was written. However, specifying all the interactions within one test leads to a test that breaks with every implementation change. A better solution is to utilize an object that can respond to anything and only specify the behavior that the test focuses on.
The following example uses a stub_everything object to ensure that the location and capacity setting methods can be called without being specified, but it also expects that the name is set correctly. Using a stub_everything also ensures that if more attributes are set in the future the test will continue to pass without any intervention.
Well written tests specify as little implementation detail as possible.
Developers new to mocking and stubbing tend to overuse the capabilities provided by mocking frameworks. I consider each call to a method of a mocking framework to be 1 implementation specification point. While using mocha I count a point for each of: stub, stub_everything, stubs, mock, expects, with, and returns. Any test that has an implementation specification score of 5 or more has a high implementation specification smell.
High Implementation Specification can stem from misusing mocking frameworks in various ways such as verifying parameters while stubbing methods, returning values that are never used, and specifying every interaction instead of using an object that will respond to any message.
Most mock frameworks provide the ability to verify the parameters sent to a mocked or stubbed method. When setting up a mock expectation, specifying the parameters can be useful for verifying interaction; however, when stubbing methods that are not the focus of the test, adding the additional parameter verification makes the test unnecessarily brittle.
The following code provides an example where the MainService notify method stub could verify the parameters, but the test is focused on testing the ReservationService. Given that the test is verifying the ReservationService there's no need for it to return a value and there's no need to verify the parameters sent to MaidService.notify. If the test did specify the parameters send to MaidService it would fail if those parameters changed. Since the test is verifying ReservationService, it would be disappointing if it failed due to a change in how MadService is called.
# implementation
end
# implementation
end
attr_accessor :booked
reservation = ReservationService.reserve_for(customer, self)
MaidService.notify(reservation)
end
end
room = HotelRoom.new
ReservationService.expects(:reserve_for).with(:customer, room)
MaidService.stubs(:notify)
room.book_for(:customer)
end
endReturning a value from a mocked method can be necessary, but should be avoided in general for two reasons: Returning a value signifies to someone reading a test that the value is necessary for some reason. Returning a value that is later verified with an assertion can cause false positives.
In the above example, the ReservationService does not provide a return value because that value isn't used anywhere within the test. If you read the above test and saw that a return value was specified you would probably want to spend time finding where and why it was used. Worse, if the above test returned a value and then later asserted equality the test would pass, but the implementation could change the return value in the future and the test would not break.
At times a mock or stub must be exercised in various ways within one test due to how the original method was written. However, specifying all the interactions within one test leads to a test that breaks with every implementation change. A better solution is to utilize an object that can respond to anything and only specify the behavior that the test focuses on.
The following example uses a stub_everything object to ensure that the location and capacity setting methods can be called without being specified, but it also expects that the name is set correctly. Using a stub_everything also ensures that if more attributes are set in the future the test will continue to pass without any intervention.
hotel = stub_everything
hotel.expects(:name=).with "Marriott"
Hotel.stubs(:new).returns hotel
Hotel.parse("Marriott||")
end
end
hotel = self.new
hotel.name, hotel.location, hotel.capacity = attributes.split("|")
hotel
end
endLabels: testing, Testing Refactorings


