TDD Beyond Basics : How to Use Fakes to Speed Up Tests
- To learn how to use fakes to speed up tests.
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
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.