cuke_iterations 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ pkg/*
4
+ .idea
5
+ features/run.txt
6
+ .DS_Store
data/.travis.yml ADDED
@@ -0,0 +1 @@
1
+ rvm: 1.9.2
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source "http://rubygems.org"
2
+
3
+ group :development do
4
+ gem 'rake', '>= 0.9.2'
5
+ gem 'rspec'
6
+ gem 'rspec-expectations', :git => "git://github.com/rspec/rspec-expectations.git"
7
+ end
8
+
9
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,36 @@
1
+ GIT
2
+ remote: git://github.com/rspec/rspec-expectations.git
3
+ revision: 0b63bf446ce84a613875417121980492571bfa90
4
+ specs:
5
+ rspec-expectations (2.6.0)
6
+ diff-lcs (~> 1.1.2)
7
+
8
+ PATH
9
+ remote: .
10
+ specs:
11
+ cuke_iterations (0.0.1)
12
+ gherkin
13
+
14
+ GEM
15
+ remote: http://rubygems.org/
16
+ specs:
17
+ diff-lcs (1.1.2)
18
+ gherkin (2.4.11)
19
+ json (>= 1.4.6)
20
+ json (1.5.3)
21
+ rake (0.9.2)
22
+ rspec (2.6.0)
23
+ rspec-core (~> 2.6.0)
24
+ rspec-expectations (~> 2.6.0)
25
+ rspec-mocks (~> 2.6.0)
26
+ rspec-core (2.6.4)
27
+ rspec-mocks (2.6.0)
28
+
29
+ PLATFORMS
30
+ ruby
31
+
32
+ DEPENDENCIES
33
+ cuke_iterations!
34
+ rake (>= 0.9.2)
35
+ rspec
36
+ rspec-expectations!
data/README.md ADDED
@@ -0,0 +1,103 @@
1
+ # Cuke Iterations [![Build Status](https://secure.travis-ci.org/jmerrifield/cuke_iterations.png)](http://travis-ci.org/jmerrifield/cuke_iterations)
2
+
3
+ Multiple invocations of your scenarios, without running Cucumber multiple times.
4
+
5
+ ## Why would I want that?
6
+
7
+ In order to run different variations of your tests e.g.
8
+
9
+ * Different browsers/devices (run all my tests in Chrome, Firefox, iPhone simulator etc)
10
+ * Different platforms (run all my tests against a Windows installation, Linux, OS X etc)
11
+ * Some types of multi-tenant scenarios (run all my tests against the Initrode deployment, the Acme deployment etc)
12
+ * Systems which have different, configurable, modes of operation (but behave the same externally) - got a system that can optionally use MySQL, Sqlite or Oracle as the data store?
13
+
14
+ ## Surely there's some way of doing that already?
15
+
16
+ The only way of 'iterating' over a scenario in Cucumber is to provide it with example rows. You certainly don't want a table of 'devices' under every single scenario.
17
+
18
+ ## That would suck! But I can just call Cucumber multiple times from a Rake script!
19
+
20
+ Absolutely. It's not without disadvantages though:
21
+
22
+ * Rake will naturally stop after the first failed command, so you have to code around that fact to have Rake ignore the cucumber failures, and fail at the end if any of the Cucumber runs had failures.
23
+ * No aggregation of results - you have to write your own result aggregation code to get an overall view of how all the variations of your tests did.
24
+ * When your Cucumber suite gets quite large, it can take a significant chunk of time for Cucumber to run your `env.rb` code, and load up all your support and feature files. That overhead, multiplied by the number of variations, can really add up.
25
+
26
+ ## So how does this gem work?
27
+
28
+ It's very simple. Cucumber can accept a file specifying a list of exact scenarios to run, in the format `filename:line_number`. This is used to re-run failed scenarios, usually in conjunction with the 'rerun' formatter. This gem takes advantage of that, to send Cucumber a long list of all your scenarios, repeated for each iteration.
29
+
30
+ ## How do I use it?
31
+
32
+ First, install the gem: `gem install cuke_iterations`
33
+
34
+ ### Configuration - iterations.yml
35
+
36
+ In your `features` folder, create a YAML file to contain your iterations. Each one must have a set of included and excluded tags, e.g:
37
+
38
+ ```yaml
39
+ iphone:
40
+ :include_tags:
41
+ - ! '@mobile'
42
+ - ! '@iphone_only'
43
+ :exclude_tags: []
44
+ android:
45
+ :include_tags:
46
+ - ! '@mobile'
47
+ :exclude_tags:
48
+ - ! '@iphone_only'
49
+ ```
50
+
51
+ At present, you *must* specify all tags that are to be included. Excluded tags have priority over included ones (e.g. an `@iphone_only` scenario would not be included in the `android` iteration, even if it had the `@mobile` tag.
52
+
53
+ ### Running it
54
+
55
+ `cd features`
56
+
57
+ With defaults:
58
+ `cuke_iterations`
59
+
60
+ To see other options:
61
+ `cuke_iterations -h`
62
+
63
+ ### What did it do?
64
+
65
+ First, it created a set of empty directories under the `iterations` folder - one for each iteration that you defined. You should add this folder to your source-control ignore list (unless you're using Git which doesn't track empty folders).
66
+
67
+ Next, it created a run file for Cucumber. If you didn't specify a filename it will be called `run.txt`. Definitely add this to your source-control ignore list.
68
+
69
+ ### Run Cucumber with the run file instead of a features folder
70
+
71
+ `cucumber @run.txt`
72
+
73
+ Note we haven't told Cucumber about our `features` folder like we normally would. This also means it doesn't know about your `support` or `step_definitions` folders, so make sure to add those to the command-line as well.
74
+
75
+ ### Use the iterations inside your scenarios
76
+
77
+ In `env.rb`:
78
+
79
+ ```ruby
80
+ require 'cuke_iterations'
81
+ World(CukeIterations::CucumberHelper)
82
+ ```
83
+
84
+ Create a 'Before' hook:
85
+
86
+ ```ruby
87
+ Before do |scenario|
88
+ iteration = current_iteration(scenario)
89
+ puts "I'm running under the '#{iteration}' iteration!"
90
+ end
91
+ ```
92
+
93
+ ### What should I do now I know which iteration the scenario is running under?
94
+
95
+ You should `puts` the iteration name to distinguish between instances of the scenario in the Cucumber output.
96
+
97
+ Depending upon the nature of your iterations, you might like to:
98
+
99
+ * Switch the browser driver to a device-specific one
100
+ * Change the URL you're running tests against
101
+ * Change the configuration of the system under test
102
+
103
+ Happy cuking!
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ desc "Run all specs"
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task :default => :spec
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.dirname(__FILE__) + "/../lib"
4
+ require 'cuke_iterations'
5
+ require 'trollop'
6
+ require 'fileutils'
7
+
8
+ opts = Trollop::options do
9
+ opt :iteration_file, "Iterations file to use", :default => 'cuke_iterations.yml'
10
+ opt :out, "Name of runfile to write", :default => 'run.txt'
11
+ end
12
+
13
+ Trollop::die "Couldn't find iterations file '#{opts[:iteration_file]}'" unless File.exist?(opts[:iteration_file])
14
+ iterations = YAML.load(File.read(opts[:iteration_file]))
15
+
16
+ all_scenarios = []
17
+ features = CukeIterations::CukeParser.parse_features(Dir.getwd)
18
+ iterations.each do |iteration_name, iteration|
19
+ iteration_dir = File.join('iterations', iteration_name, '..', '..')
20
+ FileUtils.mkdir_p iteration_dir
21
+
22
+ CukeIterations::ScenarioListGenerator.for_iteration(features, iteration).each do |scenario|
23
+ all_scenarios << File.join(iteration_dir, scenario[:filename] + ":#{scenario[:line]}")
24
+ end
25
+ end
26
+
27
+ File.open(opts[:out], 'w') {|f| f.puts all_scenarios}
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "cuke_iterations/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "cuke_iterations"
7
+ s.version = CukeIterations::VERSION
8
+ s.authors = ["Jon Merrifield"]
9
+ s.email = ["jon@jmerrifield.com"]
10
+ s.homepage = "https://github.com/jmerrifield/cuke_iterations"
11
+ s.summary = %q{Multiple iterations for Cucumber features}
12
+ s.description = %q{Run your Cucumber features multiple times within one Cucumber invocation}
13
+
14
+ s.rubyforge_project = "cuke_iterations"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+ s.add_dependency 'gherkin'
21
+ end
@@ -0,0 +1,10 @@
1
+ iphone:
2
+ :include_tags:
3
+ - ! '@tag2'
4
+ - ! '@tag8'
5
+ :exclude_tags: []
6
+ android:
7
+ :include_tags:
8
+ - ! '@tag2'
9
+ :exclude_tags:
10
+ - ! '@tag8'
@@ -0,0 +1,22 @@
1
+ @feature_tag1 @feature_tag2
2
+ Feature: My test feature with tags
3
+ In order to be awesome
4
+ As a cuker
5
+ I need to know how many cukes I have, and what colour they are
6
+
7
+ Background:
8
+ Given I have "10" cukes
9
+
10
+ Scenario: My scenario
11
+ When I check my cukes
12
+ Then I should have "10" cukes
13
+
14
+ Scenario Outline: My scenario outline
15
+ When I check my cukes
16
+ Then I should have '<cukes>' cukes
17
+ And I they should be '<colour>' in colour
18
+
19
+ Examples:
20
+ | cukes | colour |
21
+ | 10 | green |
22
+ | 11 | red |
@@ -0,0 +1,25 @@
1
+ Feature: I love cukes
2
+
3
+ @tag1 @tag2
4
+ Scenario: Scenario with tags
5
+ Given I have "10" cukes
6
+ When I check my cukes
7
+ Then I should have "10" cukes
8
+
9
+ @tag3 @tag4
10
+ Scenario Outline: Scenario outline with tags
11
+ Given I have "<cukes>" cukes
12
+
13
+ Examples:
14
+ | cukes |
15
+ | 10 |
16
+ | 11 |
17
+
18
+ @tag5 @tag6
19
+ Scenario Outline: Scenario outline with tags and tagged example sections
20
+ Given I have "<cukes>" cukes
21
+
22
+ @tag7 @tag8
23
+ Examples:
24
+ | cukes |
25
+ | 10 |
@@ -0,0 +1,3 @@
1
+ require File.dirname(__FILE__) + '/../../../lib/cuke_iterations'
2
+
3
+ World(CukeIterations::CucumberHelper)
@@ -0,0 +1,4 @@
1
+ Before do |scenario|
2
+ iteration = current_iteration(scenario)
3
+ puts "This step is running in the '#{iteration}' iteration!"
4
+ end
@@ -0,0 +1,13 @@
1
+ module CukeIterations
2
+ module CucumberHelper
3
+ def current_iteration(scenario)
4
+ if scenario.is_a? Cucumber::Ast::Scenario
5
+ scenario.feature.file
6
+ elsif scenario.is_a? Cucumber::Ast::OutlineTable::ExampleRow
7
+ scenario.scenario_outline.feature.file
8
+ else
9
+ raise "Can't deal with #{scenario.class.name}"
10
+ end[/iterations\/(.+?)\//, 1]
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,21 @@
1
+ require 'gherkin'
2
+
3
+ module CukeIterations
4
+ class CukeParser
5
+ class << self
6
+ def parse_features(dir)
7
+ scenarios = []
8
+ Dir.glob(File.join(dir, '**/*.feature')).each do |feature_file|
9
+ formatter = ScenarioExtractingFormatter.new
10
+ parser = Gherkin::Parser::Parser.new (formatter)
11
+ text = File.open(feature_file, 'r') { |f| f.read }
12
+ parser.parse(text, __FILE__, __LINE__-1)
13
+
14
+ scenarios << formatter.discovered_scenarios.each {|s| s[:filename] = File.basename(feature_file)}
15
+ end
16
+
17
+ scenarios.flatten
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,40 @@
1
+ module CukeIterations
2
+ class ScenarioExtractingFormatter
3
+ attr_reader :discovered_scenarios
4
+
5
+ def initialize
6
+ @discovered_scenarios = []
7
+ @feature_tags = []
8
+ end
9
+
10
+ def feature(feature)
11
+ @feature_tags = feature.tags.map { |t| t.name }
12
+ end
13
+
14
+ def scenario(scenario)
15
+ @discovered_scenarios << {
16
+ line: scenario.line,
17
+ tags: @feature_tags + scenario.tags.map { |t| t.name }
18
+ }
19
+ end
20
+
21
+ def scenario_outline(outline)
22
+ @outline_tags = outline.tags.map { |t| t.name }
23
+ end
24
+
25
+ def examples(examples)
26
+ examples.rows.each do |row|
27
+ next if row == examples.rows.first
28
+
29
+ @discovered_scenarios << {
30
+ line: row.line,
31
+ tags: @feature_tags + @outline_tags + examples.tags.map { |t| t.name }
32
+ }
33
+ end
34
+ end
35
+
36
+ def method_missing(sym, *args, &block)
37
+ # We don't care
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,12 @@
1
+ module CukeIterations
2
+ class ScenarioListGenerator
3
+ class << self
4
+ def for_iteration(parsed_features, iteration)
5
+ parsed_features.select do |f|
6
+ next if (f[:tags] & iteration[:exclude_tags]).any?
7
+ (f[:tags] & iteration[:include_tags]).any?
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ module CukeIterations
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,7 @@
1
+ $:.push File.expand_path("../cuke_iterations", __FILE__)
2
+
3
+ require 'version'
4
+ require 'cuke_parser'
5
+ require 'scenario_extracting_formatter'
6
+ require 'scenario_list_generator'
7
+ require 'cucumber_helper'