In Design Patterns, the Gang of Four describe the Template Method pattern. In this pattern, a base class method describes the structure of an algorithm or process and calls other methods for the steps. The base class can either force subclasses to implement the step methods or it can contain a default implementation that subclasses can choose to override if necessary.

Consider a very simple unit testing framework. We might create instances of TestCase subclasses with the name of the test method to run.

Simple TestCase Class
class TestCase
def initialize(method_name)
@method_name = method_name
end
def run
self.send @method_name
end
end

Some subclasses might need to do some setup before running the test, or teardown afterwards. That’s easy enough to accomplish using super:

Using super for Setup and Teardown
class MyTestCase < TestCase
def run
setup
super
teardown
end
def setup
# Do my set-up here
end
def teardown
# Do my clean-up here
end
end

This works, but is less than ideal. Every subclass that needs setup or teardown now has to override run and remember to call super. Subclasses that only need setup will have a different variant of run than subclasses that only need teardown. If we decide that we want to do extra work in run, then every subclass needs to be examined to make sure it still works with the new base class implementation.

For example, teardown should be called even if the test itself fails or raises an exception. Given the design above, we’d have to go through every subclass that implements teardown and change the run method.

Instead, we can refactor TestCase to use the Template Method pattern:

Template Method TestCase
class TestCase
def initialize(method_name)
@method_name = method_name
end
def run
setup
self.send @method_name
ensure
teardown
end
def setup
# Hook method for subclasses
end
def teardown
# Hook method for subclasses
end
end

With this version of TestCase, subclasses no longer have to override run. Instead, they just implement whichever of setup or teardown they need. That’s less code to write, less duplication, and more flexibility for the base class.

Consider the run method from Minitest:

Minitest::Test run Method
def run
with_info_handler do
time_it do
capture_exceptions do
before_setup; setup; after_setup
self.send self.name
end
%w{ before_teardown teardown after_teardown }.each do |hook|
capture_exceptions do
self.send hook
end
end
end
end
self
end

By using the Template Method pattern, Minitest has been able to add more features in its run method without forcing all subclasses to be changed to accommodate the new features:

  • It handles the INFO signal to print out information about which test is currently running.
  • It times each test so that it can provide timing information about the tests being run.
  • It captures exceptions in a uniform way and turns them into test failures.
  • It adds more hook methods to the test lifecycle (:before_setup, :after_setup, :before_teardown, and :after_teardown)

The Template Method pattern works really well in concert with the Composed Method pattern. Template Methods should generally be written as Composed Methods.

If you are working on a base class and you find that many of your subclasses are going to have to override the same method and call super, consider refactoring to the Template Method pattern instead.