TDD Beyond Basics : How to Use Fakes to Speed Up Tests

Objective


  • To learn how to use fakes to speed up tests.

Discussion


Let's write a program that ignores comments in the ruby program file and reads only statements that is not a comment.

The Ugly Before Version


This version is stolen from : The Well Grounded Rubyist by David Black. Given a test_file.rb with the following contents :

# This is a comment
This is not a comment
# Another comment

We want to read only the line 'This is not a comment'. Here is the uncommenter_spec.rb:

require_relative 'uncommenter'

describe Uncommenter do
  it "should uncomment a given file" do
    infile = File.new(Dir.pwd + "/uncommenter/test_file.rb")
    outfile = File.new(Dir.pwd + "/uncommenter/test_file.rb.out", "w")

    Uncommenter.uncomment(infile, outfile)
    outfile.close

    resultfile = File.open(Dir.pwd + "/uncommenter/test_file.rb.out","r")
    result_string = resultfile.read
    result_string.should == "This is not a comment\n"
    resultfile.close
  end
end

Here is the uncommenter.rb

class Uncommenter
  def self.uncomment(infile, outfile)
    infile.each do |line|
      outfile.print line unless line =~ /\A\s*#/
    end
  end
end

This works but it requires manual deleting of the file test_file.rb.out after test run. You could delete it at the end of the test. That does not change the fact that whenever you access a file system, it's not a unit test anymore. It will run slow. It becomes an integration test and requires setup and cleanup of external resources.

The Sexy After Version


Here is the uncommenter_spec.rb that runs fast:

require_relative 'uncommenter'
require 'stringio'

describe Uncommenter do
  it "should uncomment a given file" do
    input = <<-EOM
    # This is a comment.
      This is not a comment.
    # This is another comment
    EOM
    infile = StringIO.new(input)
    outfile = StringIO.new("")

    Uncommenter.uncomment(infile, outfile)

    result_string = outfile.string
    result_string.strip.should == "This is not a comment."
  end
end

This example illustrates using Ruby builtin StringIO as a Fake object. A better name for StringIO is VirtualFile. File accessing is involved. It requires the right read or write mode. It also requires closing and opening the file at the appropriate times.

StringIO is a ruby builtin class that mimics the interface of the file. This version of spec runs faster than the file accessing version. The spec is also smaller. In this case, StringIO is a real object acting as a Fake object. You don't have to manually write and maintain a Fake object for file processing. Just use the StringIO.

Run the spec as follows:

rspec uncommenter/uncommenter_spec.rb --format doc --color

Summary


In this article you learned about using Ruby's builtin StringIO as a Fake object for any file accessing operations. Since StringIO mimics the file interface we were able to use it to avoid accessing the file system and we were able to speed up the test.

This concept can be used for writing tests that needs to access external systems. Ideally the external service provider like Twitter, Stripe etc must provide us a Fake version of their API that it can be used in our tests. Our tests will not have to go over the network and it will run very fast. It should be the responsibility of the vendors to maintain the Fake implementation for different versions of their API.


Related Articles


Create your own user feedback survey