spinach 0.0.6 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -0
- data/Readme.md +3 -0
- data/bin/spinach +3 -1
- data/features/steps/error_reporting.rb +4 -8
- data/features/steps/exit_status.rb +0 -1
- data/features/steps/feature_name_guessing.rb +1 -2
- data/features/support/spinach_runner.rb +0 -2
- data/lib/spinach/capybara.rb +8 -8
- data/lib/spinach/cli.rb +30 -18
- data/lib/spinach/config.rb +29 -10
- data/lib/spinach/dsl.rb +45 -21
- data/lib/spinach/exceptions.rb +21 -8
- data/lib/spinach/feature.rb +8 -4
- data/lib/spinach/parser.rb +13 -7
- data/lib/spinach/reporter/stdout.rb +251 -53
- data/lib/spinach/reporter.rb +43 -26
- data/lib/spinach/runner/feature.rb +39 -26
- data/lib/spinach/runner/scenario.rb +43 -28
- data/lib/spinach/runner.rb +40 -20
- data/lib/spinach/support.rb +8 -7
- data/lib/spinach/version.rb +2 -1
- data/lib/spinach.rb +17 -9
- data/spinach.gemspec +2 -1
- data/test/spinach/capybara_test.rb +32 -3
- data/test/spinach/cli_test.rb +32 -11
- data/test/spinach/config_test.rb +9 -6
- data/test/spinach/dsl_test.rb +8 -1
- data/test/spinach/feature_test.rb +14 -11
- data/test/spinach/parser_test.rb +33 -20
- data/test/spinach/reporter/stdout_test.rb +364 -103
- data/test/spinach/reporter_test.rb +145 -20
- data/test/spinach/runner/feature_test.rb +26 -51
- data/test/spinach/runner/scenario_test.rb +64 -35
- data/test/spinach/runner_test.rb +41 -32
- data/test/spinach/support_test.rb +2 -10
- data/test/spinach_test.rb +14 -14
- data/test/test_helper.rb +13 -5
- metadata +36 -28
- data/examples/steps/user_logs_in.rb +0 -23
- data/examples/user_logs_in.feature +0 -6
- data/examples/user_logs_in.rb +0 -23
data/lib/spinach/runner.rb
CHANGED
@@ -1,22 +1,38 @@
|
|
1
|
+
require 'hooks'
|
2
|
+
|
1
3
|
module Spinach
|
2
|
-
#
|
3
|
-
#
|
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
|
-
#
|
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
|
50
|
-
|
67
|
+
success = Feature.new(filename).run
|
68
|
+
successful = false unless success
|
51
69
|
end
|
52
|
-
|
53
|
-
|
70
|
+
|
71
|
+
run_hook :after_run, successful
|
72
|
+
|
73
|
+
successful
|
54
74
|
end
|
55
75
|
|
56
|
-
#
|
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
|
|
data/lib/spinach/support.rb
CHANGED
@@ -1,19 +1,20 @@
|
|
1
|
-
require 'active_support/inflector/methods'
|
2
|
-
|
3
1
|
module Spinach
|
4
|
-
# A module to offer
|
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
|
-
# @
|
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
|
-
|
17
|
+
name.to_s.strip.split(/[^a-z0-9]/i).map{|w| w.capitalize}.join
|
17
18
|
end
|
18
19
|
end
|
19
20
|
end
|
data/lib/spinach/version.rb
CHANGED
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
|
-
#
|
14
|
-
#
|
15
|
-
#
|
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<
|
23
|
-
#
|
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
|
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
|
-
#
|
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 '
|
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
|
-
|
27
|
+
|
28
|
+
it 'includes capybara into all features' do
|
21
29
|
@feature.kind_of? Capybara
|
22
30
|
end
|
23
|
-
|
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
|
data/test/spinach/cli_test.rb
CHANGED
@@ -1,38 +1,59 @@
|
|
1
1
|
require_relative '../test_helper'
|
2
2
|
|
3
3
|
describe Spinach::Cli do
|
4
|
-
describe
|
5
|
-
it
|
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
|
-
|
10
|
+
|
11
|
+
describe 'backtrace' do
|
11
12
|
%w{-b --backtrace}.each do |opt|
|
12
|
-
it
|
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
|
-
|
21
|
-
|
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
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
35
|
-
|
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'])
|
data/test/spinach/config_test.rb
CHANGED
@@ -1,20 +1,23 @@
|
|
1
1
|
require_relative '../test_helper'
|
2
2
|
|
3
3
|
describe Spinach::Config do
|
4
|
-
describe
|
5
|
-
it
|
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
|
-
|
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
|
-
|
14
|
-
|
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
|
-
|
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
|
data/test/spinach/dsl_test.rb
CHANGED
@@ -8,7 +8,7 @@ describe Spinach::DSL do
|
|
8
8
|
end
|
9
9
|
|
10
10
|
describe 'class methods' do
|
11
|
-
describe
|
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
|
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
|
-
|
30
|
-
|
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
|
-
|
40
|
+
feature.execute_step('I go to the toilet')
|
41
41
|
|
42
|
-
|
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
|
-
|
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
|
54
|
-
it
|
55
|
-
feature = Feature(
|
53
|
+
describe 'Object#Feature' do
|
54
|
+
it 'creates a feature class' do
|
55
|
+
feature = Feature('Hola') do
|
56
56
|
attr_accessor :test
|
57
|
-
When
|
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(
|
65
|
+
instance.execute_step('Test')
|
66
|
+
|
64
67
|
instance.test.must_equal true
|
65
68
|
end
|
66
69
|
end
|
data/test/spinach/parser_test.rb
CHANGED
@@ -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(
|
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
|
-
|
7
|
+
|
8
|
+
let(:parsed) { @parser.parse }
|
9
|
+
|
10
|
+
describe '#parse' do
|
15
11
|
before do
|
16
|
-
@
|
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
|
-
|
19
|
-
|
20
|
+
|
21
|
+
it 'parses the feature name' do
|
22
|
+
parsed['name'].must_equal 'User authentication'
|
20
23
|
end
|
21
|
-
|
24
|
+
|
25
|
+
describe 'scenario' do
|
22
26
|
before do
|
23
|
-
@scenario =
|
27
|
+
@scenario = parsed['elements'][0]
|
24
28
|
end
|
25
|
-
|
26
|
-
|
29
|
+
|
30
|
+
it 'parses the scenario name' do
|
31
|
+
@scenario['name'].must_equal 'User logs in'
|
27
32
|
end
|
28
|
-
|
29
|
-
|
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
|
-
|
37
|
+
'I fill in the login form and press \'login\''
|
32
38
|
)
|
33
|
-
@scenario['steps'][2]['name'].must_equal
|
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
|