ActiveTest: Examination, Part II: Abstracting Without Basis

Posted by Mathew Abonyi Mon, 01 Jan 2007 04:37:50 GMT

My first mistake started the following cascade effect:

  • move all assert_x_success and assert_x_failure to class methods
  • begin defining any test case as a class method
  • extract the definition of an instance method to the superclass level (ActiveTest::Controller)
  • extract those superclass methods to abstract methods (ActiveTest::Subject)
  • fragment assert conditions to be passed up the hierarchy (define_behavioural)
  • extract fragments to the class level (assert_restful_get_success, method_missing, etc.)
  • rinse & repeat for each Subject

Rather than inheriting from a single class with all its assertions self-contained, I now inherited from ActiveTest::Base -> Subject -> Controller. Along the way there were sprinkled methods on the class and eigenclass level which helped define the test cases (ActiveTest::Subject.define_behavioral) or provide extra assertions. I had extracted so much that it was a nightmare to understand what was actually happening (a rare problem when writing in Ruby).

An Extraction Case Study from 0.1.0 Beta (abridged)

module ActiveTest
  class Controller < Subject

    class << self

      def succeeds_on(action, options = {})
        define_behavioral(:succeed_on, action, options) do
          send("call_#{action}_action", options)
          send("assert_#{action}_success")
        end
      end

    end

    ...

    def assert_restful_get_success(action, options = {})
      assert_response :success
      assert_template action.to_s
    end

    ...

    def method_missing(method, *args)
      options = args.last.is_a?(Hash) ? args.dup.pop : {}
      if method.to_s =~ /^assert_(.*)_(success|failure)$/
        action, sf = $1, $2
        if action !~ /get|post|put|delete/
          request_method = self.class.determine_request_method(action)
          send("assert_restful_#{request_method}_#{sf}", action, options)
        end
      elsif method.to_s =~ /^call_(.*)_action$/
        request_method = self.class.determine_request_method(action = $1)
        send("call_request_method", request_method, action, options)
      else
        super
      end
    end

    def call_request_method(request_method, action, options = {})
      options = expand_options(options)
      send(request_method, action, options[:parameters])
    end

  end

end

As you can see, I left out quite a few secondary methods which are being called. The first problem here is in the design of defining a behaviour: it gives too many hooks to the user that allow them to customise. If I am trying to address common cases, this should have been a red flag to a bull—I was blind. The result was a ton of method_missing calls because I extracted every call into a dynamic method, including the assertions in a dynamically defined test case (twice, in fact). Awful.

What Was I Thinking?

When I first began writing ActiveTest, I focused solely on making test cases a one-line business. ActiveTest contained just a simple class method on ActiveTest::Subject to define any kind of test case and a number of classes inheriting from it that defined common groups of assertions. Then, I abstracted the entire process into defining ‘behaviours’, approaching each test case as a behaviour request-response pair (in fact, it is a context if you use BDD terminology). I went through a number of phases looking for the cleanest way to implement this idea of behaviours (I aped RSpec, Test::Rails and others). None of them seemed appropriate, so my own metalanguage was written that looked clean (but only when I did not provide parameters). I thought the metaprogramming of tests with a descriptive class method call would be self-explanatory enough.[1]

Take away from this one single maxim, by which you should live and die when you refactor: do not abstract without real-life need. If you don’t base your abstraction on something real, you are theorizing—and we all know that the problem with theories is that they have not been tested.

Coming Up Next: Code Bloat…

Footnotes

1 Recently I encountered a situation where parameters were impossible to pass. This unforeseen situation (where I needed RFuzz:http://rfuzz.rubyforge.org/) contributed to my realisation that ActiveTest’s current design is useless for anything moderately dynamic or complex.

Posted in , , ,  | no comments | 4 trackbacks

Comments

Trackbacks

Use the following link to trackback from your own site:
http://www.mathewabonyi.com/articles/trackback/33

  1. From Ephedrine pills.
    Extracting pseudo ephedrine from tablets.
    Ephedrine faq.
  2. From Cheap flight canada.
    Cheap flight.
    Air cheap cheap flight latin ticket. Cheap flight. Air business cheap class flight leh ticket. Cheap business flight. Cheap flight tickets. Air airfare business cheap class flight kalibo.
  3. From Viagra.
    Buy viagra online.
    Viagra. Viagra canada. Generic viagra. Viagra side effects. Free viagra. Viagra uk medix plus forum.
  4. From How to get underage porn.
    Underage asian lesbian porn.
    Underage porn. Free underage porn.

(leave url/email »)

   Comment Markup Help Preview comment