I Rather Have Silly Tests Than Silly Code

24 Apr 2012

Over the years I've written a lot about the evolution I've taken with testing. I've also written about the frustration of testing within the confines of static languages. More briefly, I've discussed testing in dynamic languages, and what benefits developers get from that. I think talking about how far dynamic languages can be taken, from a testing point of view, deserves more attention.

None of these are real life examples...they are more about techniques than actual usage.

The most obvious example is the ability to test class/static methods. I've talked about this a few times already, but briefly:

class Audit
  def self.log(message)
    DB.insert(:message => message, :at => Time.now.utc)


it "saves the message with the current time" do
  Time.stub!(:now).and_return(Time.utc(2013, 3, 4, 5, 8, 9))
  DB.should_receive(:insert).with(:message => 'over 9000!', :at => Time.now.utc)
  Audit.log('over 9000!')

There's no silly dependency injection, and we don't have to add any misdirection to properly handle current times or random values or guids.

We can also leverage dynamic typing to and do:

it "loads all users" do
  Users.stub!(:all).and_return("u think I'm crazy?")
  get :index
  assigns[:users].should == "u think I'm crazy"

At first glance, this seems a bit silly. Plus, if you are using some type of testing factory, it won't always buy you much. Nevertheless, I can't think of a compelling reason not to do this. As soon as you stub out Users.all, it really doesn't matter what it returns. What matters, from the point of view of this test, is that whatever it returns, is made available to the view.

Lately, I've also been stubbing out internal implementations:

class Vegeta
  def speak
     calculate_power_leve > 9000 ? "It's over 9000!!" : "meh"
  def calculate_power_level


it "gets excited at high power levels" do
  vegeta = Vegeta.new
  vegeta.speak.should == "It's over 9000!!"

In a way, this feels a lot like testing private members, which you are never supposed to do. But sometimes it just lets you write a dead simple yet important test. Essentially, we aren't limited to only mocking external dependencies. Yes, this can be abused.

As a final example, how would you test:

def process(data)

To do this in a static language, you'd probably end up injecting a TransormationProvider which implements ITransformationProvider go down that familiar rabbit hole. With a dynamic language, you can either stub out each of those methods directly, or you could integrate the lightweight "provider" directly in the class:

class Processor
  def self.transformations
   [:transform1, :transform2, :transform3, :transform4, :tranform5]

  def process(data)
    Processor.transformations.each{|t| send(t, data) }

The outcome is the same..but both the tests and the actual code are simpler. We've introduced a mockable iterable "provider" in 3 lines rather than 2 new types (which fundamentally add no value).

blog comments powered by Disqus