hypercuke 0.4.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2df7dbaea6ea4f5665a5f0e698e677f27fa3d9fd
4
+ data.tar.gz: 1334aa38a72f29866cff7c844808b59454809a00
5
+ SHA512:
6
+ metadata.gz: 8e8ac55bd1d0e6773d148aa7e949904a1c90b171ce6b01e26e48dc40969386b46e317a69fc8ea2f5fe0922304576884ce99dd9ef334fcc289b2edddf2e8c0111
7
+ data.tar.gz: ee444352aaef8664bd45206b3485c60402cbb31754033446d8f9526805b1e1d9597952383c7267ef4b5a4feb450ee9fed452542ee76c4ed2308b32f7f1afa7f2
data/.gitignore ADDED
@@ -0,0 +1,23 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
23
+ private_tasks.rake
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color --order random
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ hypercuke
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-1.9.3-p484
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in hypercuke.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Sam Livingston-Gray
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,142 @@
1
+ # Hypercuke
2
+
3
+ Hypercuke helps you use Cucumber to do BDD at multiple layers of your
4
+ application, and gently nudges you into writing your scenarios in
5
+ high-level terms that your users can understand.
6
+
7
+ ## Why?
8
+
9
+ Because TATFT.
10
+
11
+ ### Okay, But Why Cucumber?
12
+
13
+ Cucumber is a great way to write acceptance tests that are also, by
14
+ definition, integration tests. Each scenario defines a "walking
15
+ skeleton" (I've also heard "tracer bullet") -- one complete path through
16
+ the system from top to bottom, describing one feature that an end user
17
+ actually cares about.
18
+
19
+ ### So Why Doesn't Everyone Do This Already?
20
+
21
+ The traditional Rails approach of using Cucumber to test an app from a
22
+ browser has some pitfalls:
23
+
24
+ * In the short term, sometimes there's a LOT of new functionality to
25
+ define, and you spend days or weeks just getting one scenario to pass.
26
+ This can suck the morale right out of you.
27
+
28
+ More importantly, Cucumber has some long-term pitfalls:
29
+
30
+ * Having all your tests fire up a web browser and exercise the web
31
+ application gets really really slow (especially when every! single!
32
+ test! starts with "Given I am logged in as a normal user").
33
+
34
+ * More subtly, the implicit assumption that "Cucumber ==> web browser"
35
+ makes it too easy for knowledge of the web UI to creep into tests --
36
+ and, as the Cucumber community has already learned (see <a
37
+ href="http://aslakhellesoy.com/post/11055981222/the-training-wheels-came-off">The
38
+ Training Wheels Came Off</a>), this can lead to slow, brittle tests.
39
+
40
+ * Cucumber tutorials tend to show you step definitions that combine a
41
+ regular expression with an associated block of code. Then they say
42
+ "TADA!" and wander off, leaving users with the impression that *that's
43
+ where all their code is supposed to go*. Because everything is in a
44
+ flat namespace, this tends to turn into... well, PHP. I've seen
45
+ projects with thousands of lines of horrible procedural code in deeply
46
+ interdependent step definitions that resist refactoring. In one
47
+ particularly memorable instance, I actually helped write a 50-line
48
+ step definitions with a parameter named "destroy_the_earth".
49
+
50
+ ### Why Hypercuke?
51
+
52
+ Hypercuke's core idea is a clever way of using Cucumber tags. By
53
+ swapping out different adapters for your step definitions, you can write
54
+ a scenario once, tag it appropriately, and then execute that scenario to
55
+ test any or all of:
56
+
57
+ * a fast core layer of plain old Ruby objects,
58
+ * ActiveRecord models,
59
+ * some <a
60
+ href="http://brewhouse.io/blog/2014/04/30/gourmet-service-objects.html">Gourmet
61
+ Service Objects</a>,
62
+ * an API if you have one,
63
+ * the UI,
64
+ * or any other layer that's meaningful to you.
65
+
66
+ Hypercuke directly addresses each of the pain points described above:
67
+
68
+ * By starting off at a low layer, you can use your Cucumber scenario as
69
+ a short-span integration test that's just wrapped around a few simple
70
+ objects. Once you're satisfied with how that works, you can move up
71
+ to a higher level of abstraction. If a scenario is a "walking
72
+ skeleton", Hypercuke lets you start by building the skeleton just up
73
+ to the knees, then up to the spine, and so on.
74
+
75
+ * Just because your tests are in Cucumber doesn't mean they have to be
76
+ slow. I originally developed Cucumber because I wanted to describe
77
+ all of my features using Gherkin, but only test one or two scenarios
78
+ through a web browser. Scenarios to describe boundary cases,
79
+ exceptions, or variations can run against a lower layer of the
80
+ application, which can have as much or as little overhead as makes
81
+ sense for each scenario.
82
+
83
+ * Because my scenarios might run at varying levels of abstraction, I
84
+ write them in interface-agnostic language. (For example, I'll write
85
+ "I view the list of widgets" instead of "I go to /widgets".) And if I
86
+ forget, the cognitive dissonance when I write the step definitions
87
+ very quickly reminds me to use more generic language. This helps me
88
+ write tests at a high level of abstraction, and it also helps keep me
89
+ focused on *why* I'm writing this feature, so I don't get lost
90
+ building a gold-plated automated yak-shaving factory.
91
+
92
+ * Finally, Hypercuke provides *just enough* structure for you to write
93
+ reusable step definitions. Inside the regular-expression-plus-block
94
+ that Cucumber gives you, you write the bare minimum amount of code you
95
+ need to translate from Gherkin into a Ruby message, and then you send
96
+ that message to a step adapter that does the work. Step adapters are
97
+ Ruby objects, which means you can use all of your Ruby fu to keep your
98
+ code organized.
99
+
100
+ ## How?
101
+
102
+ TODO: continue here :D
103
+
104
+ ## About the Name
105
+
106
+ I started out with the concept of "layers", so this gem was originally
107
+ going to be called "cucumber-parfait". But as I worked through it, I
108
+ kept visualizing things using two-dimensional matrices, which kept
109
+ moving around in my brain as I thought about them... and that reminded
110
+ me of visualizations of a hypercube. Ergo, Hypercuke.
111
+
112
+ ## Installation
113
+
114
+ Add this line to your application's Gemfile:
115
+
116
+ gem 'hypercuke'
117
+
118
+ And then execute:
119
+
120
+ $ bundle
121
+
122
+ Or install it yourself as:
123
+
124
+ $ gem install hypercuke
125
+
126
+ ## Usage
127
+
128
+ Obviously I have some more writing to do, but you'll need to add this
129
+ line somewhere in your application's Cucumber environment (this is
130
+ usually somewhere in /features/support/):
131
+
132
+ require 'hypercuke/cucumber_integration'
133
+
134
+ TODO: Write more detailed usage instructions
135
+
136
+ ## Contributing
137
+
138
+ 1. Fork it ( https://github.com/[my-github-username]/hypercuke/fork )
139
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
140
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
141
+ 4. Push to the branch (`git push origin my-new-feature`)
142
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ desc 'Default: run specs.'
5
+ task :default => :spec
6
+
7
+ desc 'Run specs'
8
+ RSpec::Core::RakeTask.new do |t|
9
+ # Put spec opts in a file named .rspec in root
10
+ end
11
+
data/bin/hcu ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Make sure we can get to hypercuke on the load path
4
+ unless $:.any? {|path| path =~ /hypercuke\/lib/ }
5
+ lib_path = File.expand_path( File.join( File.dirname(__FILE__), *%w[.. lib] ) )
6
+ $:.unshift(lib_path)
7
+ end
8
+
9
+ # Load the gem
10
+ begin
11
+ require 'hypercuke'
12
+ rescue LoadError
13
+ require 'rubygems' # worth a shot before just dying
14
+ require 'hypercuke'
15
+ end
16
+
17
+ # Hand the Hypercuke command off to the CLI, which will parse and
18
+ # Kernel#exec the appropriate Cucumber command
19
+ Hypercuke::CLI.exec ARGV, output_to: STDOUT
data/hypercuke.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'hypercuke/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "hypercuke"
8
+ spec.version = Hypercuke::VERSION
9
+ spec.authors = ["Sam Livingston-Gray"]
10
+ spec.email = ["sam.livingstongray@livingsocial.com"]
11
+ spec.summary = %q{Run Cucumber scenarios at multiple layers of your application.}
12
+ spec.description = %q{Hypercuke helps you use Cucumber to do BDD at multiple layers of your application, and gently nudges you into writing your scenarios in high-level terms that your users can understand.}
13
+ spec.homepage = "https://github.com/livingsocial/hypercuke"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency "cucumber", "~> 1.3"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.6"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "rspec", "~> 3.0.0"
26
+
27
+ spec.add_development_dependency 'geminabox' # TODO: remove this when publishing
28
+ end
data/lib/hypercuke.rb ADDED
@@ -0,0 +1,43 @@
1
+ require 'hypercuke/version'
2
+ require 'hypercuke/cli'
3
+ require 'hypercuke/context'
4
+ require 'hypercuke/config'
5
+ require 'hypercuke/exceptions'
6
+ require 'hypercuke/mini_inflector'
7
+ require 'hypercuke/name_list'
8
+ require 'hypercuke/step_driver'
9
+ require 'hypercuke/step_adapter'
10
+ require 'hypercuke/step_adapters'
11
+ require 'hypercuke/adapter_definition'
12
+
13
+ module Hypercuke
14
+ LAYER_NAME_ENV_VAR = 'HYPERCUKE_LAYER'
15
+
16
+ def self.reset!
17
+ @config = nil
18
+ @current_layer = nil
19
+ StepAdapters.clear
20
+ end
21
+
22
+ def self.current_layer=(layer_name)
23
+ @current_layer = layer_name ? layer_name.to_sym : nil
24
+ end
25
+ def self.current_layer
26
+ layer_name = (@current_layer || ENV[LAYER_NAME_ENV_VAR])
27
+ layer_name && layer_name.to_sym
28
+ end
29
+
30
+
31
+ def self.config
32
+ @config ||= Config.new
33
+ end
34
+
35
+ class << self
36
+ extend Forwardable
37
+ def_delegators :config, *[
38
+ :layers, :layer_names,
39
+ :topics, :topic_names,
40
+ ]
41
+ end
42
+
43
+ end
@@ -0,0 +1,29 @@
1
+ module Hypercuke
2
+ # Entry point for the adapter definition API
3
+ def self.topic(topic_name, &block)
4
+ AdapterDefinition.topic topic_name, &block
5
+ end
6
+
7
+ module AdapterDefinition
8
+ def self.topic(topic_name, &block)
9
+ Hypercuke.topics.define topic_name
10
+ tb = TopicBuilder.new(topic_name)
11
+ tb.instance_eval &block if block_given?
12
+ end
13
+
14
+ class TopicBuilder
15
+ attr_reader :topic_name
16
+ def initialize(topic_name)
17
+ @topic_name = topic_name.to_sym
18
+ end
19
+
20
+ # I know the name *says* "layer", but what it *means* is that we
21
+ # should define a step adapter for that layer.
22
+ def layer(layer_name, &block)
23
+ Hypercuke.layers.define layer_name
24
+ klass = Hypercuke::StepAdapters.define( topic_name, layer_name )
25
+ klass.module_eval &block if block_given?
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,53 @@
1
+ require 'hypercuke/cli/parser'
2
+ require 'hypercuke/cli/builder'
3
+
4
+ module Hypercuke
5
+ class CLI
6
+ def self.exec(argv, opts = {})
7
+ cli = new(argv, opts[:output_to])
8
+ cli.run!
9
+ end
10
+
11
+ # NB: .bundler_present? is not covered by tests, because I can't
12
+ # think of a reasonable way to test it. PRs welcome. :)
13
+ def self.bundler_present?
14
+ !! (`which bundle` =~ /\wbundle\w/) # parens are significant
15
+ end
16
+
17
+ def initialize(argv, output = nil, environment = ENV, kernel = Kernel)
18
+ @argv = argv
19
+ @output = output
20
+ @environment = environment
21
+ @kernel = kernel
22
+ end
23
+
24
+ def run!
25
+ output && output.puts(cucumber_command_for_display)
26
+ new_env = environment.to_hash.merge({ Hypercuke::LAYER_NAME_ENV_VAR => layer_name })
27
+ kernel.exec new_env, cucumber_command
28
+ end
29
+
30
+ def layer_name
31
+ parser.layer_name
32
+ end
33
+
34
+ def cucumber_command
35
+ builder.cucumber_command_line(self.class.bundler_present?)
36
+ end
37
+
38
+ def cucumber_command_for_display
39
+ "HYPERCUKE_LAYER=#{layer_name} #{cucumber_command}"
40
+ end
41
+
42
+ private
43
+ attr_reader :argv, :output, :environment, :kernel
44
+
45
+ def parser
46
+ @parser ||= Hypercuke::CLI::Parser.new(argv)
47
+ end
48
+
49
+ def builder
50
+ @builder ||= Hypercuke::CLI::Builder.new(parser.options)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,68 @@
1
+ module Hypercuke
2
+ class CLI
3
+
4
+ # I take information extracted the Parser and use it to build a
5
+ # 'cucumber' command line
6
+ class Builder
7
+ def initialize(options)
8
+ @options = options
9
+ @cuke_args = []
10
+ build_cuke_args
11
+ end
12
+
13
+ def cucumber_command_line(prepend_bundler = false)
14
+ cmd = prepend_bundler ? 'bundle exec ' : ''
15
+ cmd << cuke_args.join(' ')
16
+ cmd
17
+ end
18
+
19
+ private
20
+ attr_reader :options, :cuke_args
21
+
22
+ def build_cuke_args
23
+ add_base_command
24
+ add_layer_tag_for_mode
25
+ add_profile_unless_already_present
26
+ pass_through_all_other_args
27
+ end
28
+
29
+ def add_base_command
30
+ cuke_args << 'cucumber'
31
+ end
32
+
33
+ def add_layer_tag_for_mode
34
+ cuke_args << "--tags #{layer_tag_for_mode}"
35
+ end
36
+
37
+ def layer_tag_for_mode
38
+ layer = options[:layer_name]
39
+ mode = options[:mode] || 'ok'
40
+ '@%s_%s' % [ layer, mode ]
41
+ end
42
+
43
+ def add_profile_unless_already_present
44
+ if profile_specified?
45
+ add_profile options[:profile]
46
+ else
47
+ if options[:mode] == 'wip'
48
+ add_profile 'wip'
49
+ end
50
+ end
51
+ end
52
+
53
+ def profile_specified?
54
+ options[:profile].to_s !~ /^\s*$/
55
+ end
56
+
57
+ def add_profile(profile_name)
58
+ cuke_args << '--profile'
59
+ cuke_args << profile_name
60
+ end
61
+
62
+ def pass_through_all_other_args
63
+ cuke_args.concat( options[:other_args] )
64
+ end
65
+ end
66
+
67
+ end
68
+ end