cuke_writer 0.0.1
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.
- data/.gitignore +20 -0
- data/Gemfile +4 -0
- data/README.md +222 -0
- data/Rakefile +15 -0
- data/cuke_writer.gemspec +28 -0
- data/features/background.feature +61 -0
- data/features/cuke_writer.feature +98 -0
- data/features/good_job_bob.feature +39 -0
- data/features/scenario_outline.feature +64 -0
- data/features/step_definitions/cuke_writer_steps.rb +17 -0
- data/features/step_tables.feature +41 -0
- data/features/support/env.rb +4 -0
- data/lib/cucumber_ast_feature.rb +10 -0
- data/lib/cuke_writer.rb +62 -0
- data/lib/cuke_writer/version.rb +3 -0
- data/lib/serial_number.rb +24 -0
- data/lib/step_collector.rb +29 -0
- data/spec/lib/serial_number_spec.rb +36 -0
- data/spec/lib/step_collector_spec.rb +52 -0
- metadata +137 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,222 @@
|
|
1
|
+
CukeWriter
|
2
|
+
==========
|
3
|
+
|
4
|
+
[](http://Travis-ci.org/joelhelbling/cuke_writer)
|
5
|
+
|
6
|
+
|
7
|
+
What is it?
|
8
|
+
-----------
|
9
|
+
|
10
|
+
CukeWriter is a custom Cucumber formatter which collects steps and generates
|
11
|
+
serialized sets of Cucumber features.
|
12
|
+
|
13
|
+
To use it:
|
14
|
+
|
15
|
+
cucumber --format CukeWriter::Formatter features
|
16
|
+
|
17
|
+
CukeWriter basically creates features and scenarios which correspond directly
|
18
|
+
to the features and scenarios that generated them. You'll need to instantiate
|
19
|
+
a step collector:
|
20
|
+
|
21
|
+
@step_collector = StepCollector.new
|
22
|
+
|
23
|
+
Now suppose we have a feature called features/awesome.feature:
|
24
|
+
|
25
|
+
Feature: Let's write some cukes
|
26
|
+
|
27
|
+
Scenario: Just one step, but boy, what a step
|
28
|
+
Given I put all my eggs in one basket
|
29
|
+
|
30
|
+
Then, in any of your step definitions, you do summa this:
|
31
|
+
|
32
|
+
Given /^I put all my eggs in one basket$/ do
|
33
|
+
@step_collector.add "Given I have hadoop report \"R#{SerialNumber}.html.gz\""
|
34
|
+
@step_collector.add "When I uncompress the hadoop report"
|
35
|
+
@step_collector.add "Then I should see \"All files processed\""
|
36
|
+
end
|
37
|
+
|
38
|
+
Each collected step is added to a scenario/feature which has the same name as the scenario
|
39
|
+
which had the step which collected that step. So in this example, CukeWriter would create
|
40
|
+
a feature called features/generated_features/20101122131415/awesome.cw.feature (where the
|
41
|
+
serial number for this run was 20101122131415):
|
42
|
+
|
43
|
+
Feature: Let's write some cukes
|
44
|
+
[generated from features/awesome.feature]
|
45
|
+
|
46
|
+
Scenario: Just one step, but boy, what a step
|
47
|
+
[from features/awesome.feature:3]
|
48
|
+
Given I have hadoop report "R20101122131415.html.gz"
|
49
|
+
When I uncompress the hadoop report
|
50
|
+
Then I should see "All files processed"
|
51
|
+
|
52
|
+
If a scenario did not have any collected steps, it will not be added to its corresponding
|
53
|
+
generated feature. If a feature did not have any generated scenarios, it will not generate
|
54
|
+
a CukeWriter feature.
|
55
|
+
|
56
|
+
_This just in! If your *generating* feature has a Background section which has steps which
|
57
|
+
generate steps, then the *generated* feature will also have a Background section which
|
58
|
+
contains those generated background steps._
|
59
|
+
|
60
|
+
Why Would You Want This?
|
61
|
+
------------------------
|
62
|
+
|
63
|
+
Suppose you are using Cucumber to test an asynchronous system which generates artifacts which won't
|
64
|
+
be available for some time after your Cucumber tests run. CukeWriter allows you to generate a
|
65
|
+
serial-number-tagged set of Cucumber features which can be run at some later time.
|
66
|
+
|
67
|
+
For example, suppose you wish to test an email messaging system which uses a third-party email
|
68
|
+
service provider which takes its sweet ole time getting those messages into an actual email inbox.
|
69
|
+
Each time you run your main features, you send a new batch of emails out. You could tag each email
|
70
|
+
with the serial number of that particular run, so that when you run the generated features, they
|
71
|
+
will test the specific set of emails which were generated in the corresponding execution of the
|
72
|
+
main features.
|
73
|
+
|
74
|
+
Wait, What About Scenario Outlines?
|
75
|
+
-----------------------------
|
76
|
+
|
77
|
+
So glad you asked. When CukeWriter encounters a scenario outline/example table in your
|
78
|
+
feature, it generates a separate regular ole Scenario:™ for each row in your examples
|
79
|
+
table, and references the line number of the corresponding example table row.
|
80
|
+
|
81
|
+
Early on, I figured I'd have CukeWriter just output a corresponding Scenario Outline:™,
|
82
|
+
but as I thought about it, I realized that to assume the steps you generate from one example
|
83
|
+
row could very well be different from the steps generated from another example row. For
|
84
|
+
example:
|
85
|
+
|
86
|
+
Feature: As a music fan,
|
87
|
+
In order to make the most of a rock star spotting,
|
88
|
+
I want to adapt to each rock star differently
|
89
|
+
So that I can get an autograph
|
90
|
+
|
91
|
+
Scenario Outline: This is it
|
92
|
+
Given I see "<rock_star>"
|
93
|
+
|
94
|
+
Examples:
|
95
|
+
| rock_star |
|
96
|
+
| Ringo |
|
97
|
+
| Sting |
|
98
|
+
| Ted Nugent |
|
99
|
+
|
100
|
+
And suppose you wanted this to generate different steps for different stars? Here's an
|
101
|
+
example of what we _can_ do:
|
102
|
+
|
103
|
+
Feature: As a music fan,
|
104
|
+
In order to make the most of a rock star spotting,
|
105
|
+
I want to adapt to each rock star differently
|
106
|
+
So that I can get an autograph
|
107
|
+
[generated from features/rock_star_recognition.feature]
|
108
|
+
|
109
|
+
Scenario: This is it
|
110
|
+
[from features/rock_star_recognition.feature:8]
|
111
|
+
Given "Ringo" sees me
|
112
|
+
Then I say "Cheers!"
|
113
|
+
|
114
|
+
Scenario: This is it
|
115
|
+
[from features/rock_star_recognition.feature:9]
|
116
|
+
Given Sting is in an elevator
|
117
|
+
When I say "De do do do, de da da da!"
|
118
|
+
Then Sting says "I just remembered, I've got some business on 85."
|
119
|
+
|
120
|
+
Scenario: This is it
|
121
|
+
[from features/rock_star_recognition.feature:10]
|
122
|
+
Given Ted Nugent sees me
|
123
|
+
Then I run the other way
|
124
|
+
|
125
|
+
All this unique handling of each brush with greatness becomes impossible if we just generate
|
126
|
+
a single scenario outline.
|
127
|
+
|
128
|
+
Step Tables
|
129
|
+
-----------
|
130
|
+
|
131
|
+
Sometimes you just want to have CukeWriter generate a step which has a table attached to it. And
|
132
|
+
that's ok. `StepCollector#add` now accepts a hash as an optional second parameter, and in that
|
133
|
+
hash you can send over a `:table` (in the form of a `Cucumber::Ast::Table`). Behold:
|
134
|
+
|
135
|
+
Given I have a step with the following table:
|
136
|
+
| THAT | THOSE |
|
137
|
+
| thing | things |
|
138
|
+
| there | overthere |
|
139
|
+
|
140
|
+
...and supposing your step defintion did this:
|
141
|
+
|
142
|
+
Given /^I have a step with the following table:$/ |table|
|
143
|
+
@step_collector.add "Then this table goes here:", {:table => table}
|
144
|
+
end
|
145
|
+
|
146
|
+
...then your generated cuke would have this step (and attendant table):
|
147
|
+
|
148
|
+
Then this table goes here:
|
149
|
+
| THAT | THOSE |
|
150
|
+
| thing | things |
|
151
|
+
| there | overthere |
|
152
|
+
|
153
|
+
Of course, you don't have to just pass a table straight through. You can certainly just generate
|
154
|
+
your own table, e.g.
|
155
|
+
|
156
|
+
table = Cucumber::Ast::Table.new [['GOO', 'GAH'], ['boo', 'bah']]
|
157
|
+
|
158
|
+
Now that you can generate step tables, why, go forth, and multiply!
|
159
|
+
|
160
|
+
Keep 'Em Seperated
|
161
|
+
------------------
|
162
|
+
|
163
|
+
If you're using CukeWriter to solve some kind of temporal distortion problem (a.k.a. an asynchronous
|
164
|
+
problem) then chances good that when you want to run the features that use CukeWriter to write
|
165
|
+
features, you _don't_ want to run the features that CukeWriter has generated...and vice versa.
|
166
|
+
Accordingly, CukeWriter puts a "@cuke_writer" tag on each feature that it generates. This makes it
|
167
|
+
easy to tell cucumber which kind of Cuke is in season now. When it's time to plant nice neat rows
|
168
|
+
of burpless seedlings, you can make Cucumber skip any CukeWriter-generated cukes with this:
|
169
|
+
|
170
|
+
cucumber --tags ~@cuke_writer
|
171
|
+
|
172
|
+
And when it's time to turn our attention to the nice new crop of CukeWriter-written cukes, you can
|
173
|
+
load 'em up for the county fair with this:
|
174
|
+
|
175
|
+
cucumber --tags @cuke_writer
|
176
|
+
|
177
|
+
Thus CukeWriter uses Cucumber tags to heal rifts in the time-space continuum, which is pretty
|
178
|
+
freakin' sweet.
|
179
|
+
|
180
|
+
Suggestion: you might be able to simplify your personal journey by putting all this `--tags ~@cuke_writer`
|
181
|
+
and `--tags @cuke_writer` stuff into your cucumber.yml. For instance:
|
182
|
+
|
183
|
+
default: --tags ~@cuke_writer # avoid running CW-generated cukes
|
184
|
+
cuke_writer: --tags @cuke_writer # run *only* CW-generated cukes
|
185
|
+
|
186
|
+
This seems like a very helpful suggestion if I do say so myself. Someday I will make CukeWriter
|
187
|
+
glance at your cucumber.yml and suggest this very same suggestion which I have suggested. Unless, of
|
188
|
+
course, you have already followed this suggestion, in which case CukeWriter will wink at you and
|
189
|
+
smile knowingly, like this: ;)
|
190
|
+
|
191
|
+
What CukeWriter Won't Do
|
192
|
+
------------------------
|
193
|
+
|
194
|
+
_I would do anything for love, but I won't do that. --Meatloaf_
|
195
|
+
|
196
|
+
CukeWriter will not kick off the generated batch of cucumber tests automatically. This is because
|
197
|
+
the mechanism for doing that will likely be different depending on the requirements of your project
|
198
|
+
and operating environment in which CukeWriter is used: it could be triggered by cron, your CI server,
|
199
|
+
or a Windows scheduled task. It could be scheduled for five minutes from now, or fifty years from
|
200
|
+
now. Or it could be run manually whenever you darn well please. So let's leave the "when" and
|
201
|
+
"how" details in your hands.
|
202
|
+
|
203
|
+
It also won't generate the step definitions you'll need for running your generated features. You'll
|
204
|
+
want to write those yourself since they're so important!
|
205
|
+
|
206
|
+
What It _Will_ Do Soon
|
207
|
+
--------------------
|
208
|
+
|
209
|
+
Here's a nice, shallow backlog:
|
210
|
+
|
211
|
+
* _Output "Background" section (only if background steps generate steps)_ ...[DONE]
|
212
|
+
* _Handle scenario outlines_ ...[DONE]
|
213
|
+
* _Handle step tables_ ...[DONE]
|
214
|
+
* _Need a test which actually runs generated features to ensure they are kopasetic._ ...[DONE]
|
215
|
+
* _Need a special tag (e.g. @cuke_writer) which is added to each generated feature. This will allow
|
216
|
+
us to (re)run the main features while omitting the generated features and vice versa._ ...[DONE]
|
217
|
+
* Have CW glance at cucumber.yml and suggest a couple of changes for working with/around
|
218
|
+
the @cw tag (unluss cucumber.yml already does so).
|
219
|
+
* Generate a nice new rake task for each serialized batch of generated features.
|
220
|
+
* Create a nice rake task which cleans up old batches of generated features.
|
221
|
+
* Turn CukeWriter into a gem.
|
222
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'cucumber/rake/task'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
require 'bundler'
|
4
|
+
|
5
|
+
task :default => [:rspec_tests, :cucumber_tests]
|
6
|
+
|
7
|
+
Cucumber::Rake::Task.new(:cucumber_tests) do |t|
|
8
|
+
t.cucumber_opts = ['--no-source']
|
9
|
+
end
|
10
|
+
|
11
|
+
RSpec::Core::RakeTask.new(:rspec_tests) do |t|
|
12
|
+
t.rspec_opts = ['--format', 'nested']
|
13
|
+
end
|
14
|
+
|
15
|
+
Bundler::GemHelper.install_tasks
|
data/cuke_writer.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "cuke_writer/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "cuke_writer"
|
7
|
+
s.version = CukeWriter::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Joel Helbling"]
|
10
|
+
s.email = ["joel@joelhelbling.com"]
|
11
|
+
s.homepage = "http://github.com/joelhelbling/cuke_writer"
|
12
|
+
s.summary = %q{A Cucumber formatter which writes Cucumber features.}
|
13
|
+
s.description = %q{A custom Cucumber formatter which collects steps and generates serialized sets of Cucumber features. Meta FTW!}
|
14
|
+
|
15
|
+
s.rubyforge_project = "cuke_writer"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
s.add_dependency 'cucumber', '>= 0.10.0'
|
23
|
+
|
24
|
+
s.add_development_dependency 'rake', '>= 0.9.2'
|
25
|
+
s.add_development_dependency 'rspec', '>= 2.6.0'
|
26
|
+
s.add_development_dependency 'rspec-core', '>= 2.6.4'
|
27
|
+
s.add_development_dependency 'aruba', '>= 0.3.0'
|
28
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
@announce
|
2
|
+
Feature: As a tester, I want to generate steps from steps which are part of a background.
|
3
|
+
|
4
|
+
Background:
|
5
|
+
Given a typical support/env.rb file
|
6
|
+
And a file named "features/step_definitions/steps.rb" with:
|
7
|
+
"""
|
8
|
+
Given /^I don't do much$/ do
|
9
|
+
:do_relatively_nothing
|
10
|
+
end
|
11
|
+
Given /^I add a step "([^"]*)"$/ do |step_tag|
|
12
|
+
@step_collector ||= StepCollector.new
|
13
|
+
@step_collector.add "Given I do \"#{step_tag}\""
|
14
|
+
end
|
15
|
+
"""
|
16
|
+
|
17
|
+
Scenario: A feature with background doesn't generate a feature with a background
|
18
|
+
if background doesn't add any steps.
|
19
|
+
Given a file named "features/generate_sans_background.feature" with:
|
20
|
+
"""
|
21
|
+
Feature:
|
22
|
+
|
23
|
+
Background: This background doesn't generate steps
|
24
|
+
Given I don't do much
|
25
|
+
|
26
|
+
Scenario: But the scenario does generate steps
|
27
|
+
Given I add a step "foo"
|
28
|
+
"""
|
29
|
+
When I run the "generate_sans_background" feature
|
30
|
+
Then the file "features/generated_features/P123456/generate_sans_background.cw.feature" should contain "Feature:"
|
31
|
+
And the file "features/generated_features/P123456/generate_sans_background.cw.feature" should contain "Scenario:"
|
32
|
+
But the file "features/generated_features/P123456/generate_sans_background.cw.feature" should not contain "Background:"
|
33
|
+
|
34
|
+
Scenario: A feature with background does generate a feature with background
|
35
|
+
if background does add steps
|
36
|
+
Given a file named "features/generate_sans_background.feature" with:
|
37
|
+
"""
|
38
|
+
Feature:
|
39
|
+
|
40
|
+
Background: This background does generate steps
|
41
|
+
Given I add a step "bar"
|
42
|
+
|
43
|
+
Scenario: And the scenario also generates steps
|
44
|
+
Given I add a step "foo"
|
45
|
+
"""
|
46
|
+
When I run the "generate_sans_background" feature
|
47
|
+
Then the file "features/generated_features/P123456/generate_sans_background.cw.feature" should contain exactly:
|
48
|
+
"""
|
49
|
+
@cuke_writer
|
50
|
+
Feature:
|
51
|
+
[generated from features/generate_sans_background.feature]
|
52
|
+
|
53
|
+
Background: This background does generate steps
|
54
|
+
[from features/generate_sans_background.feature:3]
|
55
|
+
Given I do "bar"
|
56
|
+
|
57
|
+
Scenario: And the scenario also generates steps
|
58
|
+
[from features/generate_sans_background.feature:6]
|
59
|
+
Given I do "foo"
|
60
|
+
|
61
|
+
"""
|
@@ -0,0 +1,98 @@
|
|
1
|
+
@announce
|
2
|
+
Feature: features with simple, straightforward scenarios
|
3
|
+
|
4
|
+
Background:
|
5
|
+
Given a typical support/env.rb file
|
6
|
+
And a file named "features/step_definitions/steps.rb" with:
|
7
|
+
"""
|
8
|
+
Given /^I do this$/ do
|
9
|
+
@step_collector ||= StepCollector.new
|
10
|
+
@step_collector.add "Given I do that"
|
11
|
+
end
|
12
|
+
"""
|
13
|
+
|
14
|
+
Scenario: A basic single-scenario feature with indentation
|
15
|
+
Given a file named "features/cuke_writer_test.feature" with:
|
16
|
+
"""
|
17
|
+
Feature: Testing out CukeWriter
|
18
|
+
|
19
|
+
Scenario: A really basic scenario
|
20
|
+
Given I do this
|
21
|
+
"""
|
22
|
+
When I run the "cuke_writer_test" feature
|
23
|
+
Then it should pass with:
|
24
|
+
"""
|
25
|
+
1 scenario (1 passed)
|
26
|
+
1 step (1 passed)
|
27
|
+
"""
|
28
|
+
And the following directories should exist:
|
29
|
+
| features/generated_features/P123456 |
|
30
|
+
And the following files should exist:
|
31
|
+
| features/generated_features/P123456/cuke_writer_test.cw.feature |
|
32
|
+
And the file "features/generated_features/P123456/cuke_writer_test.cw.feature" should contain exactly:
|
33
|
+
"""
|
34
|
+
@cuke_writer
|
35
|
+
Feature: Testing out CukeWriter
|
36
|
+
[generated from features/cuke_writer_test.feature]
|
37
|
+
|
38
|
+
Scenario: A really basic scenario
|
39
|
+
[from features/cuke_writer_test.feature:3]
|
40
|
+
Given I do that
|
41
|
+
|
42
|
+
"""
|
43
|
+
|
44
|
+
Scenario: When no steps are added, don't write a scenario
|
45
|
+
Given a file named "features/suppress_empty_scenarios.feature" with:
|
46
|
+
"""
|
47
|
+
Feature: Testing out CukeWriter
|
48
|
+
|
49
|
+
Scenario: that doesn't write a step
|
50
|
+
Given I don't do much
|
51
|
+
|
52
|
+
Scenario: that does write a step
|
53
|
+
Given I do this
|
54
|
+
"""
|
55
|
+
And I append to "features/step_definitions/steps.rb" with:
|
56
|
+
"""
|
57
|
+
|
58
|
+
Given /^I don't do much$/ do
|
59
|
+
:do_relatively_nothing
|
60
|
+
end
|
61
|
+
|
62
|
+
"""
|
63
|
+
When I run the "suppress_empty_scenarios" feature
|
64
|
+
Then the file "features/generated_features/P123456/suppress_empty_scenarios.cw.feature" should contain exactly:
|
65
|
+
"""
|
66
|
+
@cuke_writer
|
67
|
+
Feature: Testing out CukeWriter
|
68
|
+
[generated from features/suppress_empty_scenarios.feature]
|
69
|
+
|
70
|
+
Scenario: that does write a step
|
71
|
+
[from features/suppress_empty_scenarios.feature:6]
|
72
|
+
Given I do that
|
73
|
+
|
74
|
+
"""
|
75
|
+
|
76
|
+
Scenario: When no scenarios are written, don't write a feature
|
77
|
+
Given a file named "features/suppress_empty_features.feature" with:
|
78
|
+
"""
|
79
|
+
Feature: Look Ma, no steps!
|
80
|
+
|
81
|
+
Scenario: this guy don't do nuthin'
|
82
|
+
Given I don't do much
|
83
|
+
|
84
|
+
Scenario: this guy doesn't do anything either
|
85
|
+
Given I don't do much
|
86
|
+
"""
|
87
|
+
And I append to "features/step_definitions/steps.rb" with:
|
88
|
+
"""
|
89
|
+
|
90
|
+
Given /^I don't do much$/ do
|
91
|
+
:do_relatively_nothing
|
92
|
+
end
|
93
|
+
|
94
|
+
"""
|
95
|
+
When I run the "suppress_empty_scenarios" feature
|
96
|
+
Then the following files should not exist:
|
97
|
+
| features/supress_empty_features.feature |
|
98
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
@announce
|
2
|
+
Feature: Here is a feature (let's call him "Bob") who uses aruba
|
3
|
+
to write and then run a feature which uses CukeWriter to write
|
4
|
+
another feature which Bob then runs and verifies its output.
|
5
|
+
Great job, Bob!
|
6
|
+
|
7
|
+
Background:
|
8
|
+
Given a typical support/env.rb file
|
9
|
+
And a file named "features/step_definitions/steps.rb" with:
|
10
|
+
"""
|
11
|
+
Given /^I wrote this with aruba$/ do
|
12
|
+
@step_collector ||= StepCollector.new
|
13
|
+
@step_collector.add "Then I wrote this with CukeWriter"
|
14
|
+
end
|
15
|
+
Then /^I wrote this with CukeWriter$/ do
|
16
|
+
puts "Hi, Bob!"
|
17
|
+
end
|
18
|
+
"""
|
19
|
+
|
20
|
+
Scenario: Bob writes and runs a feature, and then runs the feature written by that feature (via CukeWriter)
|
21
|
+
Given a file named "features/bobs_end_to_end.feature" with:
|
22
|
+
"""
|
23
|
+
Feature: Bob writes a feature that writes a feature
|
24
|
+
|
25
|
+
Scenario: here is Bob's scenario
|
26
|
+
Given I wrote this with aruba
|
27
|
+
|
28
|
+
"""
|
29
|
+
When I run the "bobs_end_to_end" feature
|
30
|
+
And I run `cucumber features/generated_features/P123456/bobs_end_to_end.cw.feature --no-color --no-source -r features/`
|
31
|
+
Then it should pass with:
|
32
|
+
"""
|
33
|
+
1 scenario (1 passed)
|
34
|
+
1 step (1 passed)
|
35
|
+
"""
|
36
|
+
And the output should contain:
|
37
|
+
"""
|
38
|
+
Hi, Bob!
|
39
|
+
"""
|
@@ -0,0 +1,64 @@
|
|
1
|
+
@announce
|
2
|
+
Feature: Features which have scenario outlines should generate a separate scenario
|
3
|
+
for each pass through the scenario outlines steps (i.e. through each row in the
|
4
|
+
examples table).
|
5
|
+
|
6
|
+
Background:
|
7
|
+
Given a typical support/env.rb file
|
8
|
+
And a file named "features/step_definitions/steps.rb" with:
|
9
|
+
"""
|
10
|
+
Given /^I don't do much$/ do
|
11
|
+
:do_relatively_nothing
|
12
|
+
end
|
13
|
+
Given /^I add a step "([^"]*)"$/ do |step_tag|
|
14
|
+
@step_collector ||= StepCollector.new
|
15
|
+
@step_collector.add "Given I do \"#{step_tag}\""
|
16
|
+
end
|
17
|
+
"""
|
18
|
+
|
19
|
+
Scenario: a feature with a scenario outline generates a separate scenario for each example
|
20
|
+
Given a file named "features/has_a_scenario_outline.feature" with:
|
21
|
+
"""
|
22
|
+
Feature: Messin' wit' scenario outlines
|
23
|
+
|
24
|
+
Scenario Outline: This is, well, you know
|
25
|
+
Given I don't do much
|
26
|
+
But I add a step "<step_name>"
|
27
|
+
|
28
|
+
Examples:
|
29
|
+
| step_name |
|
30
|
+
| foo |
|
31
|
+
| bar |
|
32
|
+
| baz |
|
33
|
+
|
34
|
+
Scenario: Another scenario just to prove there are no side effects
|
35
|
+
Given I add a step "have fun!"
|
36
|
+
"""
|
37
|
+
When I run the "has_a_scenario_outline" feature
|
38
|
+
Then the file "features/generated_features/P123456/has_a_scenario_outline.cw.feature" should not contain "Scenario Outline:"
|
39
|
+
And the file "features/generated_features/P123456/has_a_scenario_outline.cw.feature" should not contain "Examples:"
|
40
|
+
And the file "features/generated_features/P123456/has_a_scenario_outline.cw.feature" should not contain "Scenarios:"
|
41
|
+
But the file "features/generated_features/P123456/has_a_scenario_outline.cw.feature" should contain exactly:
|
42
|
+
"""
|
43
|
+
@cuke_writer
|
44
|
+
Feature: Messin' wit' scenario outlines
|
45
|
+
[generated from features/has_a_scenario_outline.feature]
|
46
|
+
|
47
|
+
Scenario: This is, well, you know
|
48
|
+
[from features/has_a_scenario_outline.feature:9]
|
49
|
+
Given I do "foo"
|
50
|
+
|
51
|
+
Scenario: This is, well, you know
|
52
|
+
[from features/has_a_scenario_outline.feature:10]
|
53
|
+
Given I do "bar"
|
54
|
+
|
55
|
+
Scenario: This is, well, you know
|
56
|
+
[from features/has_a_scenario_outline.feature:11]
|
57
|
+
Given I do "baz"
|
58
|
+
|
59
|
+
Scenario: Another scenario just to prove there are no side effects
|
60
|
+
[from features/has_a_scenario_outline.feature:13]
|
61
|
+
Given I do "have fun!"
|
62
|
+
|
63
|
+
"""
|
64
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
Given /^a serial number "([^"]*)"$/ do |serial_number|
|
2
|
+
SerialNumber.number = serial_number
|
3
|
+
end
|
4
|
+
|
5
|
+
Given /^a typical support\/env.rb file$/ do
|
6
|
+
Given "a file named \"features/support/env.rb\" with:", <<-EOF
|
7
|
+
require File.dirname(__FILE__) + '/../../../../lib/cuke_writer'
|
8
|
+
require File.dirname(__FILE__) + '/../../../../lib/step_collector'
|
9
|
+
require File.dirname(__FILE__) + '/../../../../lib/serial_number'
|
10
|
+
|
11
|
+
SerialNumber.number = "P123456"
|
12
|
+
EOF
|
13
|
+
end
|
14
|
+
|
15
|
+
When /^I run the "([^"]*)" feature$/ do |feature_name|
|
16
|
+
When "I run \`cucumber features/#{feature_name}.feature -f CukeWriter::Formatter -o cuke_writer.txt -f progress\`"
|
17
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
@announce
|
2
|
+
Feature: a step table may be passed to the step_collector to be
|
3
|
+
appended after the corresponding step.
|
4
|
+
|
5
|
+
Background:
|
6
|
+
Given a typical support/env.rb file
|
7
|
+
And a file named "features/step_definitions/steps.rb" with:
|
8
|
+
"""
|
9
|
+
Given /^I do "([^"]*)" with:$/ do |step_tag, table|
|
10
|
+
@step_collector ||= StepCollector.new
|
11
|
+
@step_collector.add "Then I put \"#{step_tag}\" on the table:", {:table => table}
|
12
|
+
end
|
13
|
+
"""
|
14
|
+
|
15
|
+
Scenario:
|
16
|
+
Given a file named "features/has_a_step_table.feature" with:
|
17
|
+
"""
|
18
|
+
Feature: Messin' wit step tables!
|
19
|
+
|
20
|
+
Scenario: Here we go
|
21
|
+
Given I do "this" with:
|
22
|
+
| that | those |
|
23
|
+
| thing | things |
|
24
|
+
| there | overthere |
|
25
|
+
"""
|
26
|
+
When I run the "has_a_step_table" feature
|
27
|
+
Then the file "features/generated_features/P123456/has_a_step_table.cw.feature" should contain exactly:
|
28
|
+
"""
|
29
|
+
@cuke_writer
|
30
|
+
Feature: Messin' wit step tables!
|
31
|
+
[generated from features/has_a_step_table.feature]
|
32
|
+
|
33
|
+
Scenario: Here we go
|
34
|
+
[from features/has_a_step_table.feature:3]
|
35
|
+
Then I put "this" on the table:
|
36
|
+
| that | those |
|
37
|
+
| thing | things |
|
38
|
+
| there | overthere |
|
39
|
+
|
40
|
+
"""
|
41
|
+
|
data/lib/cuke_writer.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require File.dirname(__FILE__) + '/step_collector'
|
3
|
+
require File.dirname(__FILE__) + '/serial_number'
|
4
|
+
require File.dirname(__FILE__) + '/cucumber_ast_feature'
|
5
|
+
|
6
|
+
module CukeWriter
|
7
|
+
class Formatter
|
8
|
+
|
9
|
+
def initialize(step_mother, path_or_io, options)
|
10
|
+
@options = options
|
11
|
+
@meta_dir = 'generated_features'
|
12
|
+
@step_collector = StepCollector.new
|
13
|
+
@current_scenario_outline_heading = nil
|
14
|
+
@scenario_outline_name = nil
|
15
|
+
@currently_in_examples_table = false
|
16
|
+
end
|
17
|
+
|
18
|
+
def after_feature(feature)
|
19
|
+
if @step_collector.steps.size > 0
|
20
|
+
FileUtils.mkdir_p(output_directory) unless File.directory?(output_directory)
|
21
|
+
File.open("#{output_directory}/#{feature.filename}", 'w') do |fh|
|
22
|
+
fh.write "@cuke_writer\nFeature: #{feature.name}\n [generated from #{feature.file}]\n"
|
23
|
+
fh.write @step_collector.steps.join("\n") + "\n"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
@step_collector.reset
|
27
|
+
end
|
28
|
+
|
29
|
+
def scenario_name(keyword, name, file_colon_line, source_indent)
|
30
|
+
if keyword == 'Scenario Outline'
|
31
|
+
@scenario_outline_info = {:name => name, :file => file_colon_line.gsub(/:\d+$/, '')}
|
32
|
+
else
|
33
|
+
@step_collector.add_scenario "#{keyword}: #{name}\n [from #{file_colon_line}]", {:indent => 2}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def background_name(keyword, name, file_colon_line, source_indent)
|
38
|
+
@step_collector.add_scenario "#{keyword}: #{name}\n [from #{file_colon_line}]", {:indent => 2}
|
39
|
+
end
|
40
|
+
|
41
|
+
def examples_name(*args)
|
42
|
+
@currently_in_examples_table = true
|
43
|
+
end
|
44
|
+
|
45
|
+
def after_examples(*args)
|
46
|
+
@scenario_outline_name = nil
|
47
|
+
@currently_in_examples_table = false
|
48
|
+
end
|
49
|
+
|
50
|
+
def before_table_row(table_row)
|
51
|
+
if @currently_in_examples_table
|
52
|
+
@step_collector.add_scenario("Scenario: #{@scenario_outline_info[:name]}\n [from #{@scenario_outline_info[:file]}:#{table_row.line}]", {:indent => 2})
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def output_directory
|
59
|
+
"features/#{@meta_dir}/#{SerialNumber}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
class SerialNumber
|
4
|
+
@@prefix = ''
|
5
|
+
@@number = nil
|
6
|
+
|
7
|
+
def self.number
|
8
|
+
@@number ||= generate_new_number
|
9
|
+
"#{@@prefix}#{@@number}"
|
10
|
+
end
|
11
|
+
def self.number=(new_number)
|
12
|
+
@@number = new_number
|
13
|
+
end
|
14
|
+
def self.prefix=(prefix)
|
15
|
+
@@prefix = prefix
|
16
|
+
end
|
17
|
+
def self.generate_new_number
|
18
|
+
DateTime.now.strftime('%Y%m%d%H%M%S')
|
19
|
+
end
|
20
|
+
def self.to_s
|
21
|
+
number
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class StepCollector
|
2
|
+
@@scenarios = []
|
3
|
+
|
4
|
+
def add_scenario(scenario, p={})
|
5
|
+
indent = p[:indent] || 2
|
6
|
+
spaces = "\n" + " " * indent
|
7
|
+
@@scenarios << [ spaces + scenario ]
|
8
|
+
end
|
9
|
+
|
10
|
+
def add(step, p={})
|
11
|
+
table = p[:table]
|
12
|
+
indent = p[:indent] || 4
|
13
|
+
spaces = " " * indent
|
14
|
+
@@scenarios.last << spaces + step + prettified(table).to_s
|
15
|
+
end
|
16
|
+
|
17
|
+
def steps
|
18
|
+
@@scenarios.select { |scenario| scenario.size > 1 }.flatten
|
19
|
+
end
|
20
|
+
|
21
|
+
def reset
|
22
|
+
@@scenarios = []
|
23
|
+
end
|
24
|
+
|
25
|
+
def prettified(table, indent=4)
|
26
|
+
return nil unless table
|
27
|
+
table.to_s(:color => false, :indent => indent + 2).gsub(/\| +/, "| ").gsub(/\s+$/, '')
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'date'
|
2
|
+
require File.dirname(__FILE__) + '/../../lib/serial_number.rb'
|
3
|
+
|
4
|
+
describe 'SerialNumber' do
|
5
|
+
before do
|
6
|
+
@time_piece = DateTime.now.strftime('%Y%m%d%H%M')
|
7
|
+
end
|
8
|
+
it "has a number (autogenerated!)" do
|
9
|
+
SerialNumber.number.should_not be_nil
|
10
|
+
end
|
11
|
+
specify "the autogenerated serial number should be date-time based" do
|
12
|
+
SerialNumber.number.should match(@time_piece)
|
13
|
+
end
|
14
|
+
it "accepts a serial number" do
|
15
|
+
lambda { SerialNumber.number = 123456 }.should_not raise_error
|
16
|
+
end
|
17
|
+
specify "the newly set number should be the serial number" do
|
18
|
+
SerialNumber.number = 987654
|
19
|
+
SerialNumber.number.should == "987654"
|
20
|
+
end
|
21
|
+
it "has a to_s method" do
|
22
|
+
SerialNumber.number = 112233445566
|
23
|
+
SerialNumber.to_s.should == '112233445566'
|
24
|
+
end
|
25
|
+
specify "this is cool: evaluate in string context, and class name returns the serial number!" do
|
26
|
+
SerialNumber.number = 333444555
|
27
|
+
"(#{SerialNumber})".should == '(333444555)'
|
28
|
+
end
|
29
|
+
it "accepts a prefix" do
|
30
|
+
lambda { SerialNumber.prefix = 'bar' }.should_not raise_error
|
31
|
+
end
|
32
|
+
specify "the number should begin with the prefix" do
|
33
|
+
SerialNumber.prefix = 'baz'
|
34
|
+
SerialNumber.number.should match(/^baz/)
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../../lib/step_collector'
|
2
|
+
|
3
|
+
describe StepCollector do
|
4
|
+
before do
|
5
|
+
@step_collector = StepCollector.new
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should return its steps" do
|
9
|
+
@step_collector.steps.should == []
|
10
|
+
end
|
11
|
+
|
12
|
+
it "adds scenarios" do
|
13
|
+
lambda { @step_collector.add_scenario "Scenario: We do stuff" }.should_not raise_error
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should have an add method" do
|
17
|
+
lambda { @step_collector.add "Given my mama didn't raise no fool" }.should_not raise_error
|
18
|
+
end
|
19
|
+
|
20
|
+
specify "default indent for scenario headers is two spaces" do
|
21
|
+
@step_collector.steps.first.should == "\n Scenario: We do stuff"
|
22
|
+
end
|
23
|
+
|
24
|
+
specify "default indent should be 4 spaces" do
|
25
|
+
@step_collector.add "When I indent things look nicer"
|
26
|
+
@step_collector.steps.last.should == " When I indent things look nicer"
|
27
|
+
end
|
28
|
+
|
29
|
+
it "accumulates steps" do
|
30
|
+
@step_collector.add "When I spin around like this"
|
31
|
+
@step_collector.steps.should == [
|
32
|
+
"\n Scenario: We do stuff",
|
33
|
+
" Given my mama didn't raise no fool",
|
34
|
+
" When I indent things look nicer",
|
35
|
+
" When I spin around like this" ]
|
36
|
+
end
|
37
|
+
|
38
|
+
it "doesn't report scenarios which have no steps" do
|
39
|
+
@step_collector.add_scenario "Scenario: We do some more stuff (but not really)"
|
40
|
+
@step_collector.steps.should == [
|
41
|
+
"\n Scenario: We do stuff",
|
42
|
+
" Given my mama didn't raise no fool",
|
43
|
+
" When I indent things look nicer",
|
44
|
+
" When I spin around like this" ]
|
45
|
+
end
|
46
|
+
|
47
|
+
it "has no steps after resetting" do
|
48
|
+
@step_collector.reset
|
49
|
+
@step_collector.steps.should == []
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
metadata
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cuke_writer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Joel Helbling
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-06-08 00:00:00 -04:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: cucumber
|
18
|
+
prerelease: false
|
19
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
|
+
none: false
|
21
|
+
requirements:
|
22
|
+
- - ">="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 0.10.0
|
25
|
+
type: :runtime
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
prerelease: false
|
30
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
31
|
+
none: false
|
32
|
+
requirements:
|
33
|
+
- - ">="
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: 0.9.2
|
36
|
+
type: :development
|
37
|
+
version_requirements: *id002
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: rspec
|
40
|
+
prerelease: false
|
41
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 2.6.0
|
47
|
+
type: :development
|
48
|
+
version_requirements: *id003
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: rspec-core
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: 2.6.4
|
58
|
+
type: :development
|
59
|
+
version_requirements: *id004
|
60
|
+
- !ruby/object:Gem::Dependency
|
61
|
+
name: aruba
|
62
|
+
prerelease: false
|
63
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.3.0
|
69
|
+
type: :development
|
70
|
+
version_requirements: *id005
|
71
|
+
description: A custom Cucumber formatter which collects steps and generates serialized sets of Cucumber features. Meta FTW!
|
72
|
+
email:
|
73
|
+
- joel@joelhelbling.com
|
74
|
+
executables: []
|
75
|
+
|
76
|
+
extensions: []
|
77
|
+
|
78
|
+
extra_rdoc_files: []
|
79
|
+
|
80
|
+
files:
|
81
|
+
- .gitignore
|
82
|
+
- Gemfile
|
83
|
+
- README.md
|
84
|
+
- Rakefile
|
85
|
+
- cuke_writer.gemspec
|
86
|
+
- features/background.feature
|
87
|
+
- features/cuke_writer.feature
|
88
|
+
- features/good_job_bob.feature
|
89
|
+
- features/scenario_outline.feature
|
90
|
+
- features/step_definitions/cuke_writer_steps.rb
|
91
|
+
- features/step_tables.feature
|
92
|
+
- features/support/env.rb
|
93
|
+
- lib/cucumber_ast_feature.rb
|
94
|
+
- lib/cuke_writer.rb
|
95
|
+
- lib/cuke_writer/version.rb
|
96
|
+
- lib/serial_number.rb
|
97
|
+
- lib/step_collector.rb
|
98
|
+
- spec/lib/serial_number_spec.rb
|
99
|
+
- spec/lib/step_collector_spec.rb
|
100
|
+
has_rdoc: true
|
101
|
+
homepage: http://github.com/joelhelbling/cuke_writer
|
102
|
+
licenses: []
|
103
|
+
|
104
|
+
post_install_message:
|
105
|
+
rdoc_options: []
|
106
|
+
|
107
|
+
require_paths:
|
108
|
+
- lib
|
109
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
110
|
+
none: false
|
111
|
+
requirements:
|
112
|
+
- - ">="
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: "0"
|
115
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
116
|
+
none: false
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: "0"
|
121
|
+
requirements: []
|
122
|
+
|
123
|
+
rubyforge_project: cuke_writer
|
124
|
+
rubygems_version: 1.6.2
|
125
|
+
signing_key:
|
126
|
+
specification_version: 3
|
127
|
+
summary: A Cucumber formatter which writes Cucumber features.
|
128
|
+
test_files:
|
129
|
+
- features/background.feature
|
130
|
+
- features/cuke_writer.feature
|
131
|
+
- features/good_job_bob.feature
|
132
|
+
- features/scenario_outline.feature
|
133
|
+
- features/step_definitions/cuke_writer_steps.rb
|
134
|
+
- features/step_tables.feature
|
135
|
+
- features/support/env.rb
|
136
|
+
- spec/lib/serial_number_spec.rb
|
137
|
+
- spec/lib/step_collector_spec.rb
|