rspec-gherkin 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.rspec +3 -0
- data/.travis.yml +11 -0
- data/Gemfile +2 -0
- data/README.md +125 -0
- data/Rakefile +1 -0
- data/features/no_scenario.feature +1 -0
- data/features/scenario_outline.feature +10 -0
- data/features/simple_feature.feature +16 -0
- data/features/updated_feature.feature +12 -0
- data/features/updated_scenario.feature +12 -0
- data/lib/rspec-gherkin/builder.rb +157 -0
- data/lib/rspec-gherkin/rspec-dsl.rb +112 -0
- data/lib/rspec-gherkin/rspec-loader.rb +56 -0
- data/lib/rspec-gherkin/version.rb +3 -0
- data/lib/rspec-gherkin.rb +68 -0
- data/rspec-gherkin.gemspec +22 -0
- data/spec/builder_spec.rb +21 -0
- data/spec/features/no_feature_spec.rb +7 -0
- data/spec/features/no_scenario_spec.rb +5 -0
- data/spec/features/scenario_outline_spec.rb +10 -0
- data/spec/features/simple_feature_spec.rb +26 -0
- data/spec/features/updated_feature_spec.rb +11 -0
- data/spec/features/updated_scenario_spec.rb +11 -0
- data/spec/integration_spec.rb +104 -0
- data/spec/rspec-gherkin_spec.rb +56 -0
- data/spec/spec_helper.rb +0 -0
- metadata +128 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8c9bab3b667c903dfa4cf5449becaa7778099a1e
|
4
|
+
data.tar.gz: fe2a11c885696386a5adcc938dde4204fb571266
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c0fda6e78aa39013889fdb409ec4810ecad15e3757952e0661cbccb7e60a04a8149fcebcc56c16d257fc299f9c2cdb6af20b48845e117b06fd26c250f8b8d0f6
|
7
|
+
data.tar.gz: 871433b8d10a664de7be4147d9b4fdf4745508c9eb7aba85834cab997988d93326050cfbffe1bbda8ab946e3c335cf8b2d7601727c6728ea5a272b99ce46db47
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
# RSpec Gherkin [![Build Status][travis-img-url]][travis-url]
|
2
|
+
|
3
|
+
[travis-img-url]: https://travis-ci.org/sheerun/rspec-gherkin.png
|
4
|
+
[travis-url]: https://travis-ci.org/rspec-gherkin
|
5
|
+
|
6
|
+
Different approach to Gherkin features in RSpec. It is based on two premises:
|
7
|
+
|
8
|
+
1. Requirements are written by **business** in semi-formal, human-readable Gherkin.
|
9
|
+
2. Automation of those is done by **programmers** in formal, machine-readable RSpec.
|
10
|
+
|
11
|
+
It resigns from the idea of regexp-parseable Cucumber features. As Uncle Bob [noticed in his article](http://blog.8thlight.com/uncle-bob/2013/09/26/AT-FAIL.html):
|
12
|
+
|
13
|
+
> I mean, the point was to get the *business* to provide a formal specification of the system so that the *programmers* could understand it. What in the name of heaven is the point of having the *programmers* write the formal specification so that the *programmers* can then understand it?
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
Add this gem to `test` group in `Gemfile`:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
group :test do
|
21
|
+
gem 'rspec-gherkin'
|
22
|
+
end
|
23
|
+
```
|
24
|
+
|
25
|
+
In your `spec_helper` include environment and `rspec-gherkin`:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
require File.expand_path('../../config/environment', __FILE__)
|
29
|
+
require 'capybara/rails' # only for Rails
|
30
|
+
require 'rspec-gherkin'
|
31
|
+
```
|
32
|
+
|
33
|
+
## Basic Usage
|
34
|
+
|
35
|
+
1. Put your requirements in `features` directory under application's root path:
|
36
|
+
|
37
|
+
```
|
38
|
+
features/manage_articles.feature
|
39
|
+
```
|
40
|
+
|
41
|
+
```
|
42
|
+
Feature: Manage Articles
|
43
|
+
In order to make a blog
|
44
|
+
As an author
|
45
|
+
I want to create and manage articles
|
46
|
+
|
47
|
+
Scenario: Articles List
|
48
|
+
Given I have articles titled Pizza, Breadsticks
|
49
|
+
When I go to the list of articles
|
50
|
+
Then I should see "Pizza"
|
51
|
+
And I should see "Breadsticks"
|
52
|
+
```
|
53
|
+
|
54
|
+
2. Put specs for for those features in `spec/features` directory:
|
55
|
+
|
56
|
+
```
|
57
|
+
spec/features/manage_articles_spec.rb
|
58
|
+
```
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
require 'spec_helper'
|
62
|
+
|
63
|
+
feature 'Manage Articles' do
|
64
|
+
scenario 'Articles List' do
|
65
|
+
create(:article, :title => "Pizza")
|
66
|
+
create(:article, :title => "Breadsticks")
|
67
|
+
visit articles_path
|
68
|
+
expect(page).to have_content 'Pizza'
|
69
|
+
expect(page).to have_content 'Breadsticks'
|
70
|
+
end
|
71
|
+
end
|
72
|
+
```
|
73
|
+
|
74
|
+
In specs you can use Capybara, FactoryGirl, helpers, and whatever you want.
|
75
|
+
|
76
|
+
You can run both `*.feature` files and `_spec.rb` spec as usual.
|
77
|
+
|
78
|
+
```sh
|
79
|
+
# Run all features
|
80
|
+
rspec features
|
81
|
+
rspec spec/features
|
82
|
+
rspec --tag feature
|
83
|
+
|
84
|
+
# Run individual features
|
85
|
+
rspec features/manage_articles.feature
|
86
|
+
rspec spec/features/manage_articles_spec.rb
|
87
|
+
```
|
88
|
+
|
89
|
+
You may want to add `--tag ~feature` to your `.rspec` file to not run
|
90
|
+
slow features specs by default.
|
91
|
+
|
92
|
+
## Configuration
|
93
|
+
|
94
|
+
By default features in `features` directory are mapped to specs in `spec/features`.
|
95
|
+
|
96
|
+
Also each feature has an additional metadata: `{ :type => :feature, :feature => true }`.
|
97
|
+
|
98
|
+
You can change this by adding configuration options in `spec_helper`. Here are the defaults:
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
RSpec.configure do |config|
|
102
|
+
config.feature_mapping = {
|
103
|
+
:feature => 'features/**/*.feature',
|
104
|
+
:spec => 'spec/features/**/*_spec.rb'
|
105
|
+
}
|
106
|
+
config.feature_metadata = { :type => :feature, :feature => :true }
|
107
|
+
end
|
108
|
+
```
|
109
|
+
|
110
|
+
## FAQ
|
111
|
+
|
112
|
+
*How it differs from `capybara/rspec`*
|
113
|
+
|
114
|
+
It is an extension to it. `rspec-gherin` among others:
|
115
|
+
|
116
|
+
1. Focuses on strong mapping between features and specs for them
|
117
|
+
2. Allows for running feature files directly
|
118
|
+
3. Notifies if any features/scenarios have pending specs
|
119
|
+
4. Notifies if any specs have no matching features/scenarios
|
120
|
+
5. Marks specs as pending if matchinf feature has been tagged as `@updated`
|
121
|
+
6. Provides RSpec messages, indicating location of feature and spec files.
|
122
|
+
|
123
|
+
## License
|
124
|
+
|
125
|
+
This gem is MIT-licensed. You are awesome.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1 @@
|
|
1
|
+
Feature: Missing scenario
|
@@ -0,0 +1,10 @@
|
|
1
|
+
Feature: using scenario outlines
|
2
|
+
Scenario Outline: a simple outline
|
3
|
+
Given there is a monster with <hp> hitpoints
|
4
|
+
When I attack the monster and do <damage> points damage
|
5
|
+
Then the monster should be <state>
|
6
|
+
|
7
|
+
Examples:
|
8
|
+
| hp | damage | state | happy |
|
9
|
+
| 10.0 | 13 | dead | false |
|
10
|
+
| 8.0 | 5 | alive | true |
|
@@ -0,0 +1,16 @@
|
|
1
|
+
Feature: A simple feature
|
2
|
+
Background:
|
3
|
+
Given we live in monster world
|
4
|
+
Scenario: A simple scenario
|
5
|
+
Given there is a monster
|
6
|
+
When I attack it
|
7
|
+
Then it should die
|
8
|
+
Scenario: Raising error
|
9
|
+
When Running this scenario
|
10
|
+
Then Error should be raisen
|
11
|
+
Scenario: Diferant metadata type
|
12
|
+
When Running this scenario
|
13
|
+
Then Type of this scenario should be :controller
|
14
|
+
Scenario: Custom metadata tag
|
15
|
+
When Running this scenario
|
16
|
+
Then Metadata should contain :custom key with 'foobar' value
|
@@ -0,0 +1,12 @@
|
|
1
|
+
@updated
|
2
|
+
Feature: Updated feature
|
3
|
+
Scenario: Attack a monster with cool tag
|
4
|
+
Given there is a monster
|
5
|
+
When I attack it
|
6
|
+
Then it should die
|
7
|
+
|
8
|
+
Scenario: Attack another monster
|
9
|
+
Given there is a strong monster
|
10
|
+
When I attack it
|
11
|
+
And I attack it
|
12
|
+
Then it should die
|
@@ -0,0 +1,12 @@
|
|
1
|
+
Feature: Feature with updated scenario
|
2
|
+
@updated
|
3
|
+
Scenario: Updated scenario
|
4
|
+
Given there is a monster
|
5
|
+
When I attack it
|
6
|
+
Then it should die
|
7
|
+
|
8
|
+
Scenario: Attack another monster
|
9
|
+
Given there is a strong monster
|
10
|
+
When I attack it
|
11
|
+
And I attack it
|
12
|
+
Then it should die
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require "gherkin"
|
2
|
+
|
3
|
+
module RSpecGherkin
|
4
|
+
class Builder
|
5
|
+
module Tags
|
6
|
+
def tags
|
7
|
+
@raw.tags.map { |tag| tag.name.sub(/^@/, '') }
|
8
|
+
end
|
9
|
+
|
10
|
+
def tags_hash
|
11
|
+
Hash[tags.map { |t| [t.to_sym, true] }]
|
12
|
+
end
|
13
|
+
|
14
|
+
def metadata_hash
|
15
|
+
tags_hash
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module Name
|
20
|
+
def name
|
21
|
+
@raw.name
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Feature
|
26
|
+
include Tags
|
27
|
+
include Name
|
28
|
+
|
29
|
+
attr_reader :scenarios, :backgrounds
|
30
|
+
attr_accessor :feature_tag
|
31
|
+
|
32
|
+
def initialize(raw)
|
33
|
+
@raw = raw
|
34
|
+
@scenarios = []
|
35
|
+
@backgrounds = []
|
36
|
+
end
|
37
|
+
|
38
|
+
def line
|
39
|
+
@raw.line
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Background
|
44
|
+
def initialize(raw)
|
45
|
+
@raw = raw
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class Scenario
|
50
|
+
include Tags
|
51
|
+
include Name
|
52
|
+
|
53
|
+
attr_accessor :arguments
|
54
|
+
|
55
|
+
def initialize(raw)
|
56
|
+
@raw = raw
|
57
|
+
@arguments = []
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class Step < Struct.new(:description, :extra_args, :line)
|
62
|
+
# 1.9.2 support hack
|
63
|
+
def split(*args)
|
64
|
+
self.to_s.split(*args)
|
65
|
+
end
|
66
|
+
|
67
|
+
def to_s
|
68
|
+
description
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
attr_reader :features
|
73
|
+
|
74
|
+
class << self
|
75
|
+
def build(feature_file)
|
76
|
+
RSpecGherkin::Builder.new.tap do |builder|
|
77
|
+
parser = Gherkin::Parser::Parser.new(builder, true)
|
78
|
+
parser.parse(File.read(feature_file), feature_file, 0)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def initialize
|
84
|
+
@features = []
|
85
|
+
end
|
86
|
+
|
87
|
+
def background(background)
|
88
|
+
@current_step_context = Background.new(background)
|
89
|
+
@current_feature.backgrounds << @current_step_context
|
90
|
+
end
|
91
|
+
|
92
|
+
def feature(feature)
|
93
|
+
@current_feature = Feature.new(feature)
|
94
|
+
@features << @current_feature
|
95
|
+
end
|
96
|
+
|
97
|
+
def scenario(scenario)
|
98
|
+
@current_step_context = Scenario.new(scenario)
|
99
|
+
@current_feature.scenarios << @current_step_context
|
100
|
+
end
|
101
|
+
|
102
|
+
def scenario_outline(outline)
|
103
|
+
@current_scenario_template = outline
|
104
|
+
end
|
105
|
+
|
106
|
+
def examples(examples)
|
107
|
+
rows_to_array(examples.rows).each do |arguments|
|
108
|
+
scenario = Scenario.new(@current_scenario_template)
|
109
|
+
scenario.arguments = arguments.map do |argument|
|
110
|
+
if numeric?(argument)
|
111
|
+
integer?(argument) ? argument.to_i : argument.to_f
|
112
|
+
elsif boolean?(argument)
|
113
|
+
to_bool(argument)
|
114
|
+
else
|
115
|
+
argument
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
@current_feature.scenarios << scenario
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def step(*)
|
124
|
+
end
|
125
|
+
|
126
|
+
def uri(*)
|
127
|
+
end
|
128
|
+
|
129
|
+
def eof
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
133
|
+
|
134
|
+
def integer?(string)
|
135
|
+
return true if string =~ /^\d+$/
|
136
|
+
end
|
137
|
+
|
138
|
+
def numeric?(string)
|
139
|
+
return true if string =~ /^\d+$/
|
140
|
+
true if Float(string) rescue false
|
141
|
+
end
|
142
|
+
|
143
|
+
def boolean?(string)
|
144
|
+
string =~ (/(true|t|yes|y)$/i) ||
|
145
|
+
string =~ (/(false|f|no|n)$/i)
|
146
|
+
end
|
147
|
+
|
148
|
+
def to_bool(string)
|
149
|
+
return true if string =~ (/(true|t|yes|y|1)$/i)
|
150
|
+
return false if string =~ (/(false|f|no|n|0)$/i)
|
151
|
+
end
|
152
|
+
|
153
|
+
def rows_to_array(rows)
|
154
|
+
rows.map { |row| row.cells(&:value) }.drop(1)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require "capybara/rspec" rescue nil
|
2
|
+
|
3
|
+
class << self
|
4
|
+
def feature(name = nil, new_metadata = {}, &block)
|
5
|
+
raise ArgumentError.new("requires a name") if name.nil?
|
6
|
+
|
7
|
+
new_metadata = ::RSpec.configuration.feature_metadata.merge(new_metadata)
|
8
|
+
matching_feature = find_feature(name)
|
9
|
+
|
10
|
+
if matching_feature
|
11
|
+
if matching_feature.tags.include?('updated')
|
12
|
+
pending_feature(name, new_metadata, block.source_location, [
|
13
|
+
"Feature has been marked as updated",
|
14
|
+
"Update specs for this feature and remove the @updated tag",
|
15
|
+
"Feature file: '#{feature_path(block.source_location)}'"
|
16
|
+
])
|
17
|
+
else
|
18
|
+
describe("Feature: #{name}", new_metadata.merge(:current_feature => matching_feature), &block)
|
19
|
+
end
|
20
|
+
else
|
21
|
+
pending_feature(name, new_metadata, block.source_location,
|
22
|
+
"No such feature in '#{feature_path(block.source_location)}'"
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def find_feature(name)
|
30
|
+
RSpecGherkin.features.find do |feature|
|
31
|
+
feature.name == name
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def feature_path(spec_location)
|
36
|
+
RSpecGherkin.spec_to_feature(spec_location.first, false)
|
37
|
+
end
|
38
|
+
|
39
|
+
def pending_feature(name, new_metadata, spec_location, reason)
|
40
|
+
describe "Feature: #{name}", new_metadata do
|
41
|
+
it do
|
42
|
+
example.metadata.merge!(
|
43
|
+
file_path: spec_location[0],
|
44
|
+
line_number: spec_location[1]
|
45
|
+
)
|
46
|
+
|
47
|
+
pending [*reason].join("\n # ")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
module RSpecGherkin
|
54
|
+
module DSL
|
55
|
+
module Rspec
|
56
|
+
def background(&block)
|
57
|
+
before(:each, &block)
|
58
|
+
end
|
59
|
+
|
60
|
+
def scenario(name = nil, new_metadata = {}, &block)
|
61
|
+
raise ArgumentError.new("requires a name") if name.nil?
|
62
|
+
|
63
|
+
matching_scenario = find_scenario(self.metadata[:current_feature], name)
|
64
|
+
|
65
|
+
if matching_scenario
|
66
|
+
if matching_scenario.tags.include?('updated')
|
67
|
+
pending_scenario(name, new_metadata, block.source_location, [
|
68
|
+
"Scenario has been marked as updated",
|
69
|
+
"Update specs for this scenario and remove the @updated tag",
|
70
|
+
"Feature file: '#{feature_path(block.source_location)}'"
|
71
|
+
])
|
72
|
+
elsif matching_scenario.arguments
|
73
|
+
specify "Scenario: #{name}", new_metadata do
|
74
|
+
instance_exec(*matching_scenario.arguments, &block)
|
75
|
+
end
|
76
|
+
else
|
77
|
+
specify("Scenario: #{name}", new_metadata, &block)
|
78
|
+
end
|
79
|
+
else
|
80
|
+
pending_scenario(name, new_metadata, block.source_location,
|
81
|
+
"No such scenario in '#{feature_path(block.source_location)}'"
|
82
|
+
)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def find_scenario(feature, name)
|
89
|
+
feature.scenarios.find do |scenario|
|
90
|
+
scenario.name == name
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def feature_path(spec_location)
|
95
|
+
RSpecGherkin.spec_to_feature(spec_location.first, false)
|
96
|
+
end
|
97
|
+
|
98
|
+
def pending_scenario(name, new_metadata, spec_location, reason)
|
99
|
+
specify name, new_metadata do
|
100
|
+
example.metadata.merge!(
|
101
|
+
file_path: spec_location[0],
|
102
|
+
line_number: spec_location[1]
|
103
|
+
)
|
104
|
+
example.metadata[:example_group].merge!(
|
105
|
+
description_args: ["Scenario:"]
|
106
|
+
)
|
107
|
+
pending [*reason].join("\n # ")
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require "rspec-gherkin"
|
2
|
+
require "rspec"
|
3
|
+
|
4
|
+
module RSpecGherkin
|
5
|
+
module RSpec
|
6
|
+
module Loader
|
7
|
+
def load(*paths, &block)
|
8
|
+
|
9
|
+
# Override feature exclusion filter if running features
|
10
|
+
if paths.any? { |path| RSpecGherkin.feature?(path) }
|
11
|
+
::RSpec.configuration.filter_manager.exclusions.reject! do |key, value|
|
12
|
+
key == :feature || (key == :type && value == 'feature')
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
paths = paths.map do |path|
|
17
|
+
if RSpecGherkin.feature?(path)
|
18
|
+
spec_path = RSpecGherkin.feature_to_spec(path)
|
19
|
+
if File.exist?(spec_path)
|
20
|
+
spec_path
|
21
|
+
else
|
22
|
+
RSpecGherkin::Builder.build(path).features.each do |feature|
|
23
|
+
::RSpec::Core::ExampleGroup.describe(
|
24
|
+
"Feature: #{feature.name}", :type => :feature, :feature => true
|
25
|
+
) do
|
26
|
+
it do
|
27
|
+
example.metadata[:file_path] = spec_path
|
28
|
+
example.metadata[:line_number] = 1
|
29
|
+
pending('Not yet implemented')
|
30
|
+
end
|
31
|
+
end.register
|
32
|
+
end
|
33
|
+
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
else
|
37
|
+
path
|
38
|
+
end
|
39
|
+
end.compact
|
40
|
+
|
41
|
+
# Load needed features to RSpecGherkin.features array
|
42
|
+
paths.each do |path|
|
43
|
+
if RSpecGherkin.feature_spec?(path)
|
44
|
+
feature_path = RSpecGherkin.spec_to_feature(path)
|
45
|
+
|
46
|
+
if File.exists?(feature_path)
|
47
|
+
RSpecGherkin.features += RSpecGherkin::Builder.build(feature_path).features
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
super(*paths, &block) if paths.size > 0
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require "rspec-gherkin/builder"
|
2
|
+
require "rspec-gherkin/rspec-dsl"
|
3
|
+
require "rspec-gherkin/rspec-loader"
|
4
|
+
require "rspec-gherkin/version"
|
5
|
+
|
6
|
+
module RSpecGherkin extend self
|
7
|
+
|
8
|
+
class << self
|
9
|
+
attr_accessor :features
|
10
|
+
end
|
11
|
+
|
12
|
+
class Pending < StandardError; end
|
13
|
+
class Malformed < StandardError; end
|
14
|
+
class Ambiguous < StandardError; end
|
15
|
+
|
16
|
+
def feature?(path)
|
17
|
+
!!path.match(mask_to_pattern(feature_mask))
|
18
|
+
end
|
19
|
+
|
20
|
+
def feature_spec?(path)
|
21
|
+
!!path.match(mask_to_pattern(spec_mask))
|
22
|
+
end
|
23
|
+
|
24
|
+
def feature_to_spec(path, prefix = true)
|
25
|
+
path = path.match(mask_to_pattern(feature_mask))[0] unless prefix
|
26
|
+
path.sub(mask_to_pattern(feature_mask), mask_to_replacement(spec_mask))
|
27
|
+
end
|
28
|
+
|
29
|
+
def spec_to_feature(path, prefix = true)
|
30
|
+
path = path.match(mask_to_pattern(spec_mask))[0] unless prefix
|
31
|
+
path.sub(mask_to_pattern(spec_mask), mask_to_replacement(feature_mask))
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
def spec_mask
|
37
|
+
::RSpec.configuration.feature_mapping[:spec]
|
38
|
+
end
|
39
|
+
|
40
|
+
def feature_mask
|
41
|
+
::RSpec.configuration.feature_mapping[:feature]
|
42
|
+
end
|
43
|
+
|
44
|
+
def mask_to_pattern(mask)
|
45
|
+
Regexp.new("#{Regexp.escape(mask).sub("\\*\\*/\\*", '(.+)')}$")
|
46
|
+
end
|
47
|
+
|
48
|
+
def mask_to_replacement(mask)
|
49
|
+
"#{mask.sub('**/*'){ '\\1' }}"
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
RSpecGherkin.features = []
|
55
|
+
|
56
|
+
::RSpec.configure do |config|
|
57
|
+
config.extend RSpecGherkin::DSL::Rspec
|
58
|
+
config.pattern << ",**/*.feature"
|
59
|
+
config.add_setting :feature_mapping
|
60
|
+
config.add_setting :feature_metadata
|
61
|
+
config.feature_mapping = {
|
62
|
+
:feature => 'features/**/*.feature',
|
63
|
+
:spec => 'spec/features/**/*_spec.rb'
|
64
|
+
}
|
65
|
+
config.feature_metadata = { :type => :feature, :feature => :true }
|
66
|
+
end
|
67
|
+
|
68
|
+
::RSpec::Core::Configuration.send(:include, RSpecGherkin::RSpec::Loader)
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "rspec-gherkin/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "rspec-gherkin"
|
7
|
+
s.version = RSpecGherkin::VERSION
|
8
|
+
s.authors = ["Adam Stankiewicz"]
|
9
|
+
s.email = ["sheerun@sher.pl"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{Different approach to Gherkin features in RSpec}
|
12
|
+
s.description = %q{Different approach to Gherkin features in RSpec}
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
|
19
|
+
s.add_runtime_dependency "rspec", "~> 2.0"
|
20
|
+
s.add_runtime_dependency "gherkin", ">= 2.5"
|
21
|
+
s.add_development_dependency "rake"
|
22
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RSpecGherkin::Builder do
|
4
|
+
context "with scenario outlines" do
|
5
|
+
let(:feature_file) { File.expand_path('../features/scenario_outline.feature', File.dirname(__FILE__)) }
|
6
|
+
let(:builder) { RSpecGherkin::Builder.build(feature_file) }
|
7
|
+
let(:feature) { builder.features.first }
|
8
|
+
|
9
|
+
it "extracts scenario" do
|
10
|
+
feature.scenarios.map(&:name).should eq([
|
11
|
+
'a simple outline',
|
12
|
+
'a simple outline'
|
13
|
+
])
|
14
|
+
end
|
15
|
+
|
16
|
+
it "add additional arguments to scenarios" do
|
17
|
+
feature.scenarios[0].arguments.should eq([ 10.0, 13, "dead", false ])
|
18
|
+
feature.scenarios[1].arguments.should eq([ 8.0, 5, "alive", true ])
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
feature 'using scenario outlines' do
|
4
|
+
scenario 'a simple outline' do |hp, damage, state, happy|
|
5
|
+
expect(hp).to be_a(Float)
|
6
|
+
expect(damage).to be_a(Fixnum)
|
7
|
+
expect(state).to be_a(String)
|
8
|
+
expect([true, false]).to include happy
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
feature 'A simple feature' do
|
4
|
+
# ensure background is executed as before(:each)
|
5
|
+
background do
|
6
|
+
@number ||= 41
|
7
|
+
@number += 1
|
8
|
+
end
|
9
|
+
|
10
|
+
scenario 'A simple scenario' do
|
11
|
+
expect(@number).to eq(42)
|
12
|
+
end
|
13
|
+
|
14
|
+
scenario 'Raising error' do
|
15
|
+
raise ArgumentError.new("Your argument is invalid!")
|
16
|
+
end
|
17
|
+
|
18
|
+
scenario 'Diferant metadata type', :type => :controller, :feature => false do
|
19
|
+
expect(example.metadata[:type]).to eq(:controller)
|
20
|
+
expect(example.metadata[:feature]).to eq(false)
|
21
|
+
end
|
22
|
+
|
23
|
+
scenario 'Custom metadata tag', :custom => "foobar" do
|
24
|
+
expect(example.metadata[:custom]).to eq("foobar")
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# This feature should be marked as pending
|
2
|
+
# because it is tagged as @updated in gherkin
|
3
|
+
feature 'Updated feature' do
|
4
|
+
scenario 'Attack a monster with cool tag' do
|
5
|
+
expect(1).to eq(1)
|
6
|
+
end
|
7
|
+
|
8
|
+
scenario 'Attack another monster' do
|
9
|
+
expect(1).to eq(1)
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# This feature should be marked as pending
|
2
|
+
# because it is tagged as @updated in gherkin
|
3
|
+
feature 'Feature with updated scenario' do
|
4
|
+
scenario 'Updated scenario' do
|
5
|
+
expect(1).to eq(1)
|
6
|
+
end
|
7
|
+
|
8
|
+
scenario 'Attack another monster' do
|
9
|
+
expect(1).to eq(1)
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'The CLI', :type => :integration do
|
4
|
+
context 'runing features from features directory' do
|
5
|
+
it 'ignores --tag ~feature flag when running features' do
|
6
|
+
expect(%x(rspec features --tag ~feature 2>&1)).
|
7
|
+
to include('9 examples, 1 failure, 3 pending')
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'ignores --tag ~type:feature flag when running features' do
|
11
|
+
expect(%x(rspec features --tag ~type:feature 2>&1)).
|
12
|
+
to include('9 examples, 1 failure, 3 pending')
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'runing features specs on their own' do
|
17
|
+
before(:all) do
|
18
|
+
@result = %x(rspec --tag feature --format documentation 2>&1)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'passes all specs' do
|
22
|
+
expect(@result).to include('9 examples, 1 failure, 4 pending')
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'prepends features with "Feature: " prefix' do
|
26
|
+
expect(@result).to include('Feature: A simple feature')
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'prepends scenarios with "Scenario: " prefix' do
|
30
|
+
expect(@result).to include('Scenario: A simple scenario')
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'non-existing scenario' do
|
34
|
+
it 'shows name of non-existing scenario' do
|
35
|
+
expect(@result).to include(
|
36
|
+
"Scenario: Non-existing scenario"
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'shows that spec implements non-existing scenario' do
|
41
|
+
expect(@result).to include(
|
42
|
+
"No such scenario in 'features/no_scenario.feature'"
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'shows line number where missing scenario is mentioned' do
|
47
|
+
expect(@result).to include(
|
48
|
+
"./spec/features/no_scenario_spec.rb:2"
|
49
|
+
)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'non-existing feature' do
|
54
|
+
it 'shows name of non-existing feature' do
|
55
|
+
expect(@result).to include(
|
56
|
+
"Feature: Missing feature"
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'shows that spec implements non-existing feature' do
|
61
|
+
expect(@result).to include(
|
62
|
+
"No such feature in 'features/no_feature.feature'"
|
63
|
+
)
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'shows line number where missing scenario is mentioned' do
|
67
|
+
expect(@result).to include(
|
68
|
+
"./spec/features/no_feature_spec.rb:3"
|
69
|
+
)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'updated features and scenarios' do
|
74
|
+
it 'recognizes and notifies when feature is marked as @updated' do
|
75
|
+
expect(@result).to include(
|
76
|
+
'Feature has been marked as updated'
|
77
|
+
)
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'recognizes and notifies when scenario is marked as @updated' do
|
81
|
+
expect(@result).to include(
|
82
|
+
'Scenario has been marked as updated'
|
83
|
+
)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# it "shows the correct description" do
|
89
|
+
# @result.should include('A simple feature')
|
90
|
+
# @result.should include('is a simple feature')
|
91
|
+
# end
|
92
|
+
|
93
|
+
# it "prints out failures and successes" do
|
94
|
+
# @result.should include('35 examples, 3 failures, 5 pending')
|
95
|
+
# end
|
96
|
+
|
97
|
+
# it "includes features in backtraces" do
|
98
|
+
# @result.should include('examples/errors.feature:5:in `raise error')
|
99
|
+
# end
|
100
|
+
|
101
|
+
# it "includes the right step name when steps call steps" do
|
102
|
+
# @result.should include("No such step: 'this is an unimplemented step'")
|
103
|
+
# end
|
104
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
describe RSpecGherkin do
|
2
|
+
let(:feature_path) { '/project/features/awesome-def_file.feature' }
|
3
|
+
let(:spec_path) { '/project/spec/features/awesome-def_file_spec.rb' }
|
4
|
+
|
5
|
+
let(:short_feature_path) { 'features/awesome-def_file.feature' }
|
6
|
+
let(:short_spec_path) { 'spec/features/awesome-def_file_spec.rb' }
|
7
|
+
|
8
|
+
context '#feature?' do
|
9
|
+
it 'recognizes if path is feature path' do
|
10
|
+
expect(RSpecGherkin.feature?(feature_path)).to eq(true)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'recognizes if path is not feature path' do
|
14
|
+
expect(RSpecGherkin.feature?(spec_path)).to eq(false)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context '#feature_spec?' do
|
19
|
+
it 'recognizes if path is spec path' do
|
20
|
+
expect(RSpecGherkin.feature_spec?(spec_path)).to eq(true)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'recognizes if path is not spec path' do
|
24
|
+
expect(RSpecGherkin.feature_spec?(feature_path)).to eq(false)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context '#feature_to_spec' do
|
29
|
+
it 'properly translates feature file to spec file' do
|
30
|
+
expect(RSpecGherkin.feature_to_spec(feature_path)).to eq(spec_path)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'excludes prefix when requested' do
|
34
|
+
expect(RSpecGherkin.feature_to_spec(feature_path, false)).
|
35
|
+
to eq(short_spec_path)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context '#spec_to_feature' do
|
40
|
+
it 'properly translates spec file to feature file' do
|
41
|
+
expect(RSpecGherkin.spec_to_feature(spec_path)).
|
42
|
+
to eq(feature_path)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'excludes prefix when requested' do
|
46
|
+
expect(RSpecGherkin.spec_to_feature(spec_path, false)).
|
47
|
+
to eq(short_feature_path)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
specify '#spec_to_feature and #feature_to_spec should be idempotent' do
|
52
|
+
p1 = RSpecGherkin.feature_to_spec(RSpecGherkin.spec_to_feature(spec_path))
|
53
|
+
p2 = RSpecGherkin.feature_to_spec(feature_path)
|
54
|
+
expect(p1).to eq(p2)
|
55
|
+
end
|
56
|
+
end
|
data/spec/spec_helper.rb
ADDED
File without changes
|
metadata
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rspec-gherkin
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Adam Stankiewicz
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-10-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: gherkin
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.5'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.5'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: Different approach to Gherkin features in RSpec
|
56
|
+
email:
|
57
|
+
- sheerun@sher.pl
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- .gitignore
|
63
|
+
- .rspec
|
64
|
+
- .travis.yml
|
65
|
+
- Gemfile
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- features/no_scenario.feature
|
69
|
+
- features/scenario_outline.feature
|
70
|
+
- features/simple_feature.feature
|
71
|
+
- features/updated_feature.feature
|
72
|
+
- features/updated_scenario.feature
|
73
|
+
- lib/rspec-gherkin.rb
|
74
|
+
- lib/rspec-gherkin/builder.rb
|
75
|
+
- lib/rspec-gherkin/rspec-dsl.rb
|
76
|
+
- lib/rspec-gherkin/rspec-loader.rb
|
77
|
+
- lib/rspec-gherkin/version.rb
|
78
|
+
- rspec-gherkin.gemspec
|
79
|
+
- spec/builder_spec.rb
|
80
|
+
- spec/features/no_feature_spec.rb
|
81
|
+
- spec/features/no_scenario_spec.rb
|
82
|
+
- spec/features/scenario_outline_spec.rb
|
83
|
+
- spec/features/simple_feature_spec.rb
|
84
|
+
- spec/features/updated_feature_spec.rb
|
85
|
+
- spec/features/updated_scenario_spec.rb
|
86
|
+
- spec/integration_spec.rb
|
87
|
+
- spec/rspec-gherkin_spec.rb
|
88
|
+
- spec/spec_helper.rb
|
89
|
+
homepage: ''
|
90
|
+
licenses: []
|
91
|
+
metadata: {}
|
92
|
+
post_install_message:
|
93
|
+
rdoc_options: []
|
94
|
+
require_paths:
|
95
|
+
- lib
|
96
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - '>='
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - '>='
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
requirements: []
|
107
|
+
rubyforge_project:
|
108
|
+
rubygems_version: 2.0.3
|
109
|
+
signing_key:
|
110
|
+
specification_version: 4
|
111
|
+
summary: Different approach to Gherkin features in RSpec
|
112
|
+
test_files:
|
113
|
+
- features/no_scenario.feature
|
114
|
+
- features/scenario_outline.feature
|
115
|
+
- features/simple_feature.feature
|
116
|
+
- features/updated_feature.feature
|
117
|
+
- features/updated_scenario.feature
|
118
|
+
- spec/builder_spec.rb
|
119
|
+
- spec/features/no_feature_spec.rb
|
120
|
+
- spec/features/no_scenario_spec.rb
|
121
|
+
- spec/features/scenario_outline_spec.rb
|
122
|
+
- spec/features/simple_feature_spec.rb
|
123
|
+
- spec/features/updated_feature_spec.rb
|
124
|
+
- spec/features/updated_scenario_spec.rb
|
125
|
+
- spec/integration_spec.rb
|
126
|
+
- spec/rspec-gherkin_spec.rb
|
127
|
+
- spec/spec_helper.rb
|
128
|
+
has_rdoc:
|