rspec-gherkin 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|