spinach 0.0.6 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/Gemfile +1 -0
  2. data/Readme.md +3 -0
  3. data/bin/spinach +3 -1
  4. data/features/steps/error_reporting.rb +4 -8
  5. data/features/steps/exit_status.rb +0 -1
  6. data/features/steps/feature_name_guessing.rb +1 -2
  7. data/features/support/spinach_runner.rb +0 -2
  8. data/lib/spinach/capybara.rb +8 -8
  9. data/lib/spinach/cli.rb +30 -18
  10. data/lib/spinach/config.rb +29 -10
  11. data/lib/spinach/dsl.rb +45 -21
  12. data/lib/spinach/exceptions.rb +21 -8
  13. data/lib/spinach/feature.rb +8 -4
  14. data/lib/spinach/parser.rb +13 -7
  15. data/lib/spinach/reporter/stdout.rb +251 -53
  16. data/lib/spinach/reporter.rb +43 -26
  17. data/lib/spinach/runner/feature.rb +39 -26
  18. data/lib/spinach/runner/scenario.rb +43 -28
  19. data/lib/spinach/runner.rb +40 -20
  20. data/lib/spinach/support.rb +8 -7
  21. data/lib/spinach/version.rb +2 -1
  22. data/lib/spinach.rb +17 -9
  23. data/spinach.gemspec +2 -1
  24. data/test/spinach/capybara_test.rb +32 -3
  25. data/test/spinach/cli_test.rb +32 -11
  26. data/test/spinach/config_test.rb +9 -6
  27. data/test/spinach/dsl_test.rb +8 -1
  28. data/test/spinach/feature_test.rb +14 -11
  29. data/test/spinach/parser_test.rb +33 -20
  30. data/test/spinach/reporter/stdout_test.rb +364 -103
  31. data/test/spinach/reporter_test.rb +145 -20
  32. data/test/spinach/runner/feature_test.rb +26 -51
  33. data/test/spinach/runner/scenario_test.rb +64 -35
  34. data/test/spinach/runner_test.rb +41 -32
  35. data/test/spinach/support_test.rb +2 -10
  36. data/test/spinach_test.rb +14 -14
  37. data/test/test_helper.rb +13 -5
  38. metadata +36 -28
  39. data/examples/steps/user_logs_in.rb +0 -23
  40. data/examples/user_logs_in.feature +0 -6
  41. data/examples/user_logs_in.rb +0 -23
@@ -1,22 +1,38 @@
1
+ require 'hooks'
2
+
1
3
  module Spinach
2
- # Spinach's runner gets the parsed data from the feature and performs the
3
- # actual calls to the feature classes.
4
+ # Runner gets the parsed data from the feature and performs the actual calls
5
+ # to the feature classes.
4
6
  #
5
7
  class Runner
8
+ include Hooks
9
+
10
+ # The feature files to run
11
+ attr_reader :filenames
12
+
13
+ # The default path where the steps are located
14
+ attr_reader :step_definitions_path
15
+
16
+ # The default path where the support files are located
17
+ attr_reader :support_path
18
+
19
+ define_hook :before_run
20
+ define_hook :after_run
6
21
 
7
22
  # Initializes the runner with a parsed feature
8
23
  #
9
24
  # @param [Array<String>] filenames
10
- # an array of feature filenames to run
25
+ # A list of feature filenames to run
11
26
  #
12
27
  # @param [Hash] options
13
28
  #
14
29
  # @option options [String] :step_definitions_path
15
- # The path in which step definitions are found
30
+ # The path in which step definitions are found.
16
31
  #
17
32
  # @option options [String] :support_path
18
- # The path with the support ruby files
33
+ # The path with the support ruby files.
19
34
  #
35
+ # @api public
20
36
  def initialize(filenames, options = {})
21
37
  @filenames = filenames
22
38
 
@@ -25,13 +41,9 @@ module Spinach
25
41
 
26
42
  @support_path = options.delete(:support_path ) ||
27
43
  Spinach.config.support_path
28
-
29
- end
30
-
31
- def reporter
32
- @reporter ||= Spinach::config.default_reporter
33
44
  end
34
45
 
46
+ # The feature files to run
35
47
  attr_reader :filenames
36
48
 
37
49
  # The default path where the steps are located
@@ -42,45 +54,53 @@ module Spinach
42
54
 
43
55
  # Runs this runner and outputs the results in a colorful manner.
44
56
  #
57
+ # @return [true, false]
58
+ # Whether the run was succesful.
59
+ #
60
+ # @api public
45
61
  def run
46
62
  require_dependencies
47
63
 
64
+ successful = true
65
+
48
66
  filenames.each do |filename|
49
- success = Feature.new(filename, reporter).run
50
- @failed = true unless success
67
+ success = Feature.new(filename).run
68
+ successful = false unless success
51
69
  end
52
- reporter.end
53
- @failed ? false : true
70
+
71
+ run_hook :after_run, successful
72
+
73
+ successful
54
74
  end
55
75
 
56
- # Requires step definitions and support files
76
+ # Loads step definitions and support files.
57
77
  #
78
+ # @api public
58
79
  def require_dependencies
59
80
  (support_files + step_definition_files).each do |file|
60
81
  require file
61
82
  end
62
83
  end
63
84
 
64
- # List of step definition files
65
- #
66
85
  # @return [Array<String>] files
86
+ # The step definition files.
67
87
  #
88
+ # @api public
68
89
  def step_definition_files
69
90
  Dir.glob(
70
91
  File.expand_path File.join(step_definitions_path, '**', '*.rb')
71
92
  )
72
93
  end
73
94
 
74
- # List of support files
75
- #
76
95
  # @return [Array<String>] files
96
+ # The support files.
77
97
  #
98
+ # @api public
78
99
  def support_files
79
100
  Dir.glob(
80
101
  File.expand_path File.join(support_path, '**', '*.rb')
81
102
  )
82
103
  end
83
-
84
104
  end
85
105
  end
86
106
 
@@ -1,19 +1,20 @@
1
- require 'active_support/inflector/methods'
2
-
3
1
  module Spinach
4
- # A module to offer common helpers
2
+ # A module to offer helpers for string mangling.
3
+ #
5
4
  module Support
6
- # A helper to camelize string that proxies ActiveSupport#camelize
7
- #
8
5
  # @param [String] name
6
+ # The name to camelize.
9
7
  #
10
- # @returns [String]
8
+ # @return [String]
9
+ # The +name+ in camel case.
11
10
  #
12
11
  # @example
13
12
  # Spinach::Support.camelize('User authentication')
14
13
  # => 'UserAuthentication'
14
+ #
15
+ # @api public
15
16
  def self.camelize(name)
16
- ActiveSupport::Inflector.camelize(name.to_s.downcase.strip.squeeze(' ').gsub(' ','_'))
17
+ name.to_s.strip.split(/[^a-z0-9]/i).map{|w| w.capitalize}.join
17
18
  end
18
19
  end
19
20
  end
@@ -1,3 +1,4 @@
1
1
  module Spinach
2
- VERSION = "0.0.6"
2
+ # Spianch version.
3
+ VERSION = "0.1.0"
3
4
  end
data/lib/spinach.rb CHANGED
@@ -9,35 +9,43 @@ require_relative 'spinach/feature'
9
9
  require_relative 'spinach/reporter'
10
10
  require_relative 'spinach/cli'
11
11
 
12
-
13
- # Spinach is a BDD framework in top of gherkin. Its main goals are:
14
- # * No magic: All features are implemented using actual classes.
15
- # * Reusability: Steps are methods, so they can be put inside modules.
12
+ # Spinach is a BDD framework leveraging the great Gherkin language. This
13
+ # language is the one used defining features in Cucumber, the BDD framework
14
+ # Spinach is inspired upon.
15
+ #
16
+ # Its main design goals are:
17
+ #
18
+ # * No magic: All features are implemented using normal Ruby classes.
19
+ # * Reusability: Steps are methods, so they can be reused using modules, a
20
+ # common, normal practice among rubyists.
16
21
  # * Proper encapsulation: No conflicts between steps from different
17
22
  # scenarios.
18
23
  #
19
24
  module Spinach
20
25
  @@features = []
21
26
 
22
- # @return [Array<Spinach::Feature>]
23
- # all the registered features
27
+ # @return [Array<Feature>]
28
+ # All the registered features.
24
29
  #
30
+ # @api public
25
31
  def self.features
26
32
  @@features
27
33
  end
28
34
 
29
- # Resets Spinach to a pristine state, as if any feature was registered.
35
+ # Resets Spinach to a pristine state, as if no feature was ever registered.
30
36
  # Mostly useful in Spinach's own testing.
31
37
  #
38
+ # @api semipublic
32
39
  def self.reset_features
33
40
  @@features = []
34
41
  end
35
42
 
36
- # Finds a feature given a feature name
43
+ # Finds a feature given a feature name.
37
44
  #
38
45
  # @param [String] name
39
- # the feature name
46
+ # The feature name.
40
47
  #
48
+ # @api public
41
49
  def self.find_feature(name)
42
50
  klass = Spinach::Support.camelize(name)
43
51
  @@features.detect do |feature|
data/spinach.gemspec CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |gem|
12
12
  gem.add_runtime_dependency 'gherkin'
13
13
  gem.add_runtime_dependency 'minitest'
14
14
  gem.add_runtime_dependency 'colorize'
15
- gem.add_runtime_dependency 'activesupport'
15
+ gem.add_runtime_dependency 'hooks'
16
16
  gem.add_development_dependency 'purdytest'
17
17
  gem.add_development_dependency 'rake'
18
18
  gem.add_development_dependency 'mocha'
@@ -20,6 +20,7 @@ Gem::Specification.new do |gem|
20
20
  gem.add_development_dependency 'capybara'
21
21
  gem.add_development_dependency 'aruba'
22
22
  gem.add_development_dependency 'pry'
23
+ gem.add_development_dependency 'simplecov'
23
24
 
24
25
  gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
25
26
  gem.files = `git ls-files`.split("\n")
@@ -9,19 +9,48 @@ describe Spinach::Feature::Capybara do
9
9
  'Hello world!'
10
10
  end
11
11
  end
12
+
12
13
  Capybara.app = @sinatra_app
14
+
13
15
  @feature = Class.new(Spinach::Feature) do
16
+ include Spinach::Feature::Capybara
17
+ Given 'Hello' do
18
+ end
19
+ Then 'Goodbye' do
20
+ end
14
21
  def go_home
15
- visit "/"
22
+ visit '/'
16
23
  page
17
24
  end
18
25
  end.new
19
26
  end
20
- it "includes capybara into all features" do
27
+
28
+ it 'includes capybara into all features' do
21
29
  @feature.kind_of? Capybara
22
30
  end
23
- it "goes to a capybara page and returns its result" do
31
+
32
+ it 'goes to a capybara page and returns its result' do
24
33
  page = @feature.go_home
25
34
  page.has_content?('Hello world').must_equal true
26
35
  end
36
+
37
+ it 'resets the capybara session after each scenario' do
38
+ @feature_runner = Spinach::Runner::Feature.new(
39
+ stub_everything)
40
+
41
+ Spinach::Parser.any_instance.stubs(content: '
42
+ Feature: A test feature
43
+ Scenario: A test scenario
44
+ Given Hello
45
+ Then Goodbye
46
+ Scenario: Another test scenario
47
+ Given Hello
48
+ Then Goodbye
49
+ ').at_least_once
50
+
51
+ @feature_runner.stubs(feature: @feature).at_least_once
52
+ Capybara.current_session.expects(:reset!).twice
53
+
54
+ @feature_runner.run
55
+ end
27
56
  end
@@ -1,38 +1,59 @@
1
1
  require_relative '../test_helper'
2
2
 
3
3
  describe Spinach::Cli do
4
- describe "#options" do
5
- it "defaults" do
4
+ describe '#options' do
5
+ it 'sets the default options' do
6
6
  cli = Spinach::Cli.new([])
7
7
  options = cli.options
8
8
  options[:reporter][:backtrace].must_equal false
9
9
  end
10
- describe "backtrace" do
10
+
11
+ describe 'backtrace' do
11
12
  %w{-b --backtrace}.each do |opt|
12
- it "sets the backtrace if #{opt}" do
13
+ it 'sets the backtrace if #{opt}' do
13
14
  cli = Spinach::Cli.new([opt])
14
15
  options = cli.options
15
16
  options[:reporter][:backtrace].must_equal true
16
17
  end
17
18
  end
18
19
  end
20
+ describe "version" do
21
+ %w{-v --version}.each do |opt|
22
+ it "outputs the version" do
23
+ cli = Spinach::Cli.new([opt])
24
+ cli.expects(:exit)
25
+ cli.expects(:puts).with(Spinach::VERSION)
26
+ output = capture_stdout do
27
+ cli.options
28
+ end
29
+ end
30
+ end
31
+ end
19
32
  end
20
- describe "#init_reporter" do
21
- it "inits the default reporter" do
33
+
34
+ describe '#init_reporter' do
35
+ it 'inits the default reporter' do
36
+ cli = Spinach::Cli.new([])
37
+ reporter = stub
38
+ reporter.expects(:bind)
39
+ Spinach::Reporter::Stdout.stubs(new: reporter)
40
+ cli.init_reporter
22
41
  Spinach.config.default_reporter.wont_equal nil
23
42
  end
24
43
  end
25
- describe "#run" do
26
- describe "when a particular feature list is passed" do
27
- it "runs the feature" do
44
+
45
+ describe '#run' do
46
+ describe 'when a particular feature list is passed' do
47
+ it 'runs the feature' do
28
48
  cli = Spinach::Cli.new(['features/some_feature.feature'])
29
49
  Spinach::Runner.expects(:new).with(['features/some_feature.feature']).
30
50
  returns(stub(:run))
31
51
  cli.run
32
52
  end
33
53
  end
34
- describe "when no feature is passed" do
35
- it "runs the feature" do
54
+
55
+ describe 'when no feature is passed' do
56
+ it 'runs the feature' do
36
57
  cli = Spinach::Cli.new([])
37
58
  Dir.expects(:glob).with('features/**/*.feature').
38
59
  returns(['features/some_feature.feature'])
@@ -1,20 +1,23 @@
1
1
  require_relative '../test_helper'
2
2
 
3
3
  describe Spinach::Config do
4
- describe "#step_definitions_path" do
5
- it "returns a default" do
4
+ describe '#step_definitions_path' do
5
+ it 'returns a default' do
6
6
  (Spinach.config[:step_definitions_path].kind_of? String).must_equal true
7
7
  end
8
- it "can be overwritten" do
8
+
9
+ it 'can be overwritten' do
9
10
  Spinach.config[:step_definitions_path] = 'steps'
10
11
  Spinach.config[:step_definitions_path].must_equal 'steps'
11
12
  end
12
13
  end
13
- describe "#support_path" do
14
- it "returns a default" do
14
+
15
+ describe '#support_path' do
16
+ it 'returns a default' do
15
17
  (Spinach.config[:support_path].kind_of? String).must_equal true
16
18
  end
17
- it "can be overwritten" do
19
+
20
+ it 'can be overwritten' do
18
21
  Spinach.config[:support_path] = 'support'
19
22
  Spinach.config[:support_path].must_equal 'support'
20
23
  end
@@ -8,7 +8,7 @@ describe Spinach::DSL do
8
8
  end
9
9
 
10
10
  describe 'class methods' do
11
- describe "#When" do
11
+ describe '#When' do
12
12
  it 'defines a method with the step name' do
13
13
  @feature.When('I say goodbye') do
14
14
  'You say hello'
@@ -32,5 +32,12 @@ describe Spinach::DSL do
32
32
  @feature.feature_name.must_equal 'User salutes'
33
33
  end
34
34
  end
35
+
36
+ describe "#name" do
37
+ it "responds with a feature's name" do
38
+ @feature.feature("A cool feature")
39
+ @feature.new.name.must_equal "A cool feature"
40
+ end
41
+ end
35
42
  end
36
43
  end
@@ -1,7 +1,7 @@
1
1
  require_relative '../test_helper'
2
2
 
3
3
  describe Spinach::Feature do
4
- describe "ancestors" do
4
+ describe 'ancestors' do
5
5
  it 'includes minitest helpers' do
6
6
  Spinach::Feature.ancestors.must_include MiniTest::Assertions
7
7
  end
@@ -26,8 +26,8 @@ describe Spinach::Feature do
26
26
  end
27
27
 
28
28
  describe 'instance methods' do
29
- before do
30
- @feature = Class.new(Spinach::Feature) do
29
+ let(:feature) do
30
+ Class.new(Spinach::Feature) do
31
31
  When 'I go to the toilet' do
32
32
  @pee = true
33
33
  end
@@ -37,30 +37,33 @@ describe Spinach::Feature do
37
37
 
38
38
  describe 'execute_step' do
39
39
  it 'runs defined step correctly' do
40
- @feature.execute_step('I go to the toilet')
40
+ feature.execute_step('I go to the toilet')
41
41
 
42
- @feature.pee.must_equal true
42
+ feature.pee.must_equal true
43
43
  end
44
44
 
45
45
  it 'raises an exception if step is not defined' do
46
46
  proc {
47
- @feature.execute_step 'I am lost'
47
+ feature.execute_step 'I am lost'
48
48
  }.must_raise Spinach::StepNotDefinedException
49
49
  end
50
50
  end
51
51
  end
52
52
 
53
- describe "Object#Feature" do
54
- it "creates a feature class" do
55
- feature = Feature("Hola") do
53
+ describe 'Object#Feature' do
54
+ it 'creates a feature class' do
55
+ feature = Feature('Hola') do
56
56
  attr_accessor :test
57
- When "Test" do
57
+ When 'Test' do
58
58
  self.test = true
59
59
  end
60
60
  end
61
+
61
62
  Spinach.features.must_include feature
63
+
62
64
  instance = feature.new
63
- instance.execute_step("Test")
65
+ instance.execute_step('Test')
66
+
64
67
  instance.test.must_equal true
65
68
  end
66
69
  end
@@ -2,36 +2,49 @@ require_relative '../test_helper'
2
2
 
3
3
  describe Spinach::Parser do
4
4
  before do
5
- @parser = Spinach::Parser.new("feature_definition.feature")
6
- @parser.stubs(:content).returns('
7
- Feature: User authentication
8
- Scenario: User logs in
9
- Given I am on the front page
10
- When I fill in the login form and press "login"
11
- Then I should be on my dashboard
12
- ')
5
+ @parser = Spinach::Parser.new('feature_definition.feature')
13
6
  end
14
- describe "#parse" do
7
+
8
+ let(:parsed) { @parser.parse }
9
+
10
+ describe '#parse' do
15
11
  before do
16
- @parsed = @parser.parse
12
+ @parser.stubs(:content).returns("
13
+ Feature: User authentication
14
+ Scenario: User logs in
15
+ Given I am on the front page
16
+ When I fill in the login form and press 'login'
17
+ Then I should be on my dashboard
18
+ ")
17
19
  end
18
- it "parses the feature name" do
19
- @parsed['name'].must_equal "User authentication"
20
+
21
+ it 'parses the feature name' do
22
+ parsed['name'].must_equal 'User authentication'
20
23
  end
21
- describe "scenario" do
24
+
25
+ describe 'scenario' do
22
26
  before do
23
- @scenario = @parsed['elements'][0]
27
+ @scenario = parsed['elements'][0]
24
28
  end
25
- it "parses the scenario name" do
26
- @scenario['name'].must_equal "User logs in"
29
+
30
+ it 'parses the scenario name' do
31
+ @scenario['name'].must_equal 'User logs in'
27
32
  end
28
- it "parses the scenario steps" do
29
- @scenario['steps'][0]['name'].must_equal "I am on the front page"
33
+
34
+ it 'parses the scenario steps' do
35
+ @scenario['steps'][0]['name'].must_equal 'I am on the front page'
30
36
  @scenario['steps'][1]['name'].must_equal(
31
- "I fill in the login form and press \"login\""
37
+ 'I fill in the login form and press \'login\''
32
38
  )
33
- @scenario['steps'][2]['name'].must_equal "I should be on my dashboard"
39
+ @scenario['steps'][2]['name'].must_equal 'I should be on my dashboard'
34
40
  end
35
41
  end
36
42
  end
43
+
44
+ describe '#content' do
45
+ it 'reads the disk and returns the file content' do
46
+ File.expects(:read).with('feature_definition.feature')
47
+ @parser.content
48
+ end
49
+ end
37
50
  end