Below I’ve pasted a ./script/story command I’ve been using for about a
week. It has three modes of operation:
- ./script/story with no arguments will run all *.story files in the
story path - ./script/story with a path or glob will run the specified stories
- If input is passed in via STDIN, it runs that text as a story. This
opens up story running via a simple interface (Unix pipes) for
integration with other tools.
This deals with plain text stories only, and does NOT require an
associated .rb runner file for them. Without Ruby runner code for the
stories, the problem becomes figuring out what steps to use with a
given plain text story file. The solution I put together is quite
simple.
I declare groups of step matchers in a separate directory, taking care
to match the step group name to the file name. When a story file is
run, if any steps match a part of the path, they are included. For
example, my story in
RAILS_ROOT/stories/scenarios/schedule_formatting/weekly.story will
attempt to use the step groups “schedule_formatting” and “weekly” if
they exist.
For cases where that convention is not flexible enough, I added
support for including a comment line in the .story file with
instructions for which steps it should be run with. I think about this
kind of like a she-bang line. So the first line of a .story file could
be:
steps: navigations, more_steps
And that will override the default behavior.
I’m posting this because I hope it might be of immediate use to others
using plain text stories right now, and also because I would like to
consider possibilities for getting this functionality into RSpec core.
WDYT?
–
Bryan H.
http://brynary.com – My blog
#!/usr/bin/env ruby
class StoryCommand
ROOT_PATH = File.expand_path(File.dirname(FILE) + “/…”)
STORIES_PATH = “#{ROOT_PATH}/stories/scenarios”
STEP_MATCHERS_PATH = “#{ROOT_PATH}/stories/steps”
HELPER_PATH = “#{ROOT_PATH}/stories/helper”
def self.run
self.new.run
end
def run
if ARGV.empty? && first_char = using_stdin?
setup_and_run_story((first_char + STDIN.read).split("\n"))
elsif ARGV.empty?
run_story_files(all_story_files)
else
run_story_files(ARGV)
end
end
def all_story_files
Dir["#{STORIES_PATH}/**/*.story"].uniq
end
def using_stdin?
char = nil
begin
char = STDIN.read_nonblock(1)
rescue Errno::EAGAIN
return false
end
return char
end
def clean_story_paths(paths)
paths.map! { |path| File.expand_path(path) }
paths.map! { |path| path.gsub(/.story$/, “”) }
paths.map! { |path| path.gsub(/#{STORIES_PATH}//, “”) }
end
def run_story_files(stories)
clean_story_paths(stories).each do |story|
setup_and_run_story(File.readlines("#{STORIES_PATH}/#{story}.story"),
story)
end
end
def setup_and_run_story(lines, story_name = nil)
require HELPER_PATH
steps = steps_for_story(lines, story_name)
steps.reject! { |step|
!File.exist?("#{STEP_MATCHERS_PATH}/#{step}.rb") }
steps.each { |step| require “#{STEP_MATCHERS_PATH}/#{step}” }
run_story(lines, steps)
end
def steps_for_story(lines, story_name)
if lines.first =~ /^# steps: /
lines.first.gsub(/^# steps: /, “”).split(",").map(&:strip)
else
story_name.to_s.split("/")
end
end
def run_story(lines, steps)
tempfile = Tempfile.new(“story”)
lines.each do |line|
tempfile.puts line
end
tempfile.close
with_steps_for(*steps.map(&:to_sym)) do
run tempfile.path, :type => RailsStory
end
end
end
StoryCommand.run