After reading the title Embedding a Test Suite in a Single-file Ruby App (Part 1) I thought: aha! finally somebody has done it and used ruby data block to store tests. However I was disappointed as there was another approach, which I think is less elegant.
Below I show you a proof of concept of embedding tests in ruby code.
Why?
Because tests and code are in the same file:
- easier to refactor
- easier to navigate
- documentation from tests near the code
- does not affect code performance
- reminds you to write tests
How
Ruby has special feature which I call data blocks. In short you can embed any data after your ruby code in the same file and when you run that file that data will be available as DATA
constant. All you need is to separate it with __END__
.
For example
$ cat t.rb
puts DATA.gets
__END__
hello world!
$ ruby t.rb
hello world!
Putting all together
Below is my proof of concept to run rspec tests from the same file.
# drink.rb
class Drink
attr_reader :type
def initialize
@type = "water"
end
end
__END__
require 'spec_helper'
RSpec.describe Drink do
it 'is has default type water' do
expect(Drink.new.type).to eq('water')
end
end
In order for that to work we need to put small patch to rspec so it reads the file correctly.
# spec_helper.rb
class DataBlockLoader
module RspecConfig
def load_spec_files
super
files_to_run.uniq.each do |f|
file = File.expand_path(f)
DataBlockLoader.load file
end
end
end
END_TOKEN = /^__END__\r?\n/
def self.load file_path
io = File.open(file_path, 'r' )
loop do
line = io.gets
break if line.nil?
break if line.match(END_TOKEN)
end
line_no = io.lineno
eval io.read, nil, file_path, line_no + 1
end
end
RSpec::Core::Configuration.prepend DataBlockLoader::RspecConfig
Finally
Now we can just run that file with rspec:
$ rspec drink.rb
1/1 |========================= 100 ==========================>| Time: 00:00:01
Finished in 0.86 seconds (files took 0.70232 seconds to load)
1 example, 0 failures
This is just a small proof of concept which requires more work on adopting rspec internals to work with data blocks. Let's me know if you'll take a challenge on that.