saki 0.0.4 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +43 -24
- data/VERSION +1 -1
- data/lib/generators/saki/spec_generator.rb +31 -0
- data/lib/generators/saki/templates/acceptance_spec.rb +21 -0
- data/lib/saki.rb +40 -16
- data/saki.gemspec +4 -2
- metadata +6 -4
data/README.markdown
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
# Saki - For times when you can't swallow Cucumber
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
Are you tired of having DRY code, but tests that seem to babble on "for the length of a bible"? Me too. How about RSpec code that is hard to follow, when Ruby itself is simple to follow? I hate it too.
|
3
|
+
Are you tired of having DRY code, but tests that seem to babble on "for the length of a bible"? Me too. How about test code that is hard to follow, when Ruby itself is clear as day? I hate it too.
|
6
4
|
|
7
5
|
Enter Saki stage left.
|
8
6
|
|
7
|
+
Saki lets you do acceptance testing on top of RSpec. It is considerably more terse than Cucumber, but does not sacrifice readability. Saki also does not use Given/When/Then syntax because the thought is that there is little return other than familiarity for Cucumber users.
|
8
|
+
|
9
9
|
## How terse is it?
|
10
10
|
|
11
11
|
Well, here's a sample that sets up contexts that create a user and then visit an edit path for that user.
|
12
12
|
|
13
13
|
with_existing :user do
|
14
|
-
on_visiting
|
14
|
+
on_visiting edit_user_path do
|
15
15
|
it { should let_me_edit(@user) }
|
16
16
|
end
|
17
17
|
end
|
@@ -21,25 +21,31 @@ This code basically injects some "before blocks", so it would look like this in
|
|
21
21
|
context "a user exists" do
|
22
22
|
before { @user = Factory :user }
|
23
23
|
context "I visit the page for editing that user" do
|
24
|
-
before { visit
|
24
|
+
before { visit "/users/#{@user.id}/user" }
|
25
25
|
it { should let_me_edit(@user) }
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
-
I believe the Saki example has the following benefits:
|
29
|
+
I believe the Saki example has the following benefits over vanilla RSpec:
|
30
30
|
|
31
31
|
* It saves two lines of code.
|
32
|
-
* It standardizes the code. Whereas a context string might accidentally get out of sync with the code (unmaintained comments, anyone), with Saki this would probably cause a test to fail.
|
32
|
+
* It standardizes the code. Whereas a context string might accidentally get out of sync with the code (unmaintained comments, anyone?), with Saki this would probably cause a test to fail.
|
33
33
|
* It is much more expressive. You read the test and you immediately know what it does.
|
34
|
-
* Any exceptions to the rule (complicated setups, etc.) now stick out like sore thumbs, as they should.
|
34
|
+
* Any exceptions to the rule (complicated setups, etc.) would now stick out like sore thumbs, as they should.
|
35
35
|
|
36
|
-
The only assumption is that you are using factories instead of fixtures. You also get more out of it in a conventional RESTful application.
|
36
|
+
The only assumption is that you are using factories instead of fixtures. You also get more out of it in a conventional RESTful application, where the paths don't have too many gotchas.
|
37
37
|
|
38
|
-
##
|
38
|
+
## Methods provided for setting up contexts
|
39
39
|
|
40
|
-
`with_existing` takes a factory name as a symbol and assigns its created object to
|
40
|
+
`with_existing` takes a factory name as a symbol and assigns its created object to an instance variable with the same name. It also takes options so you can have blocks start with:
|
41
41
|
|
42
|
-
|
42
|
+
with_existing :user, :state => "happy" do...
|
43
|
+
|
44
|
+
`on_visiting` preferably uses some dynamic functions for establishing a path: `new_X_path`, `Xs_path`, `edit_X_path`, `X_path` and `new_X_path`. In these cases, substitute X for the resource name (e.g. `new_user_path`). In cases where the resource is nested, it has a :parent => parent_resource option. This lets you set up blocks like:
|
45
|
+
|
46
|
+
on_visiting auctions_path(:parent => :user) do ...
|
47
|
+
|
48
|
+
`on_visiting` also takes a path as a string, or a lambda that executes within a before block to set up the path. It also takes a symbol which is the name of a method name. This is useful when the code is dependent on an instance variable for path creation.
|
43
49
|
|
44
50
|
path_for_user = lambda { user_path(@user) }
|
45
51
|
|
@@ -53,13 +59,9 @@ or you can do
|
|
53
59
|
|
54
60
|
on_visiting :my_user_path do ...
|
55
61
|
|
56
|
-
`on_visiting` has several helper functions for establishing a path: `create_path_for`, `index_path_for`, `edit_path_for`, `show_path_for` and `new_path_for`. These paths all take resource names for establishing a path. In cases where the resource is nested, it has a :parent => parent_resource option. This lets you set up blocks like:
|
57
|
-
|
58
|
-
on_visiting index_path_for(:auction)
|
59
|
-
|
60
62
|
`on_following_link_to` works the same as on_visiting, but it first validates that the link exists, and then follows it.
|
61
63
|
|
62
|
-
`where` is a function taking as a parameter lambda to execute in the before block.
|
64
|
+
`where` is a function taking as a parameter a lambda to execute in the before block.
|
63
65
|
|
64
66
|
def self.creating_a_user
|
65
67
|
lambda {
|
@@ -69,7 +71,7 @@ or you can do
|
|
69
71
|
}
|
70
72
|
end
|
71
73
|
|
72
|
-
on_following_link_to
|
74
|
+
on_following_link_to new_user_path do
|
73
75
|
where creating_a_user do
|
74
76
|
specify { page.should have_content(@user.email) }
|
75
77
|
end
|
@@ -77,7 +79,7 @@ or you can do
|
|
77
79
|
|
78
80
|
Obviously the return for this is where you have functions acting as "reusable steps" in the style of Cucumber. In addition your "before blocks" are more expressive.
|
79
81
|
|
80
|
-
Finally, to simplify setting up integration tests, anything you wrap in an `integrate` block (like `describe`) sets the test type to acceptance.
|
82
|
+
Finally, to simplify setting up integration tests, anything you wrap in an `integrate` block (like `describe`) sets the test type to acceptance. This is the default `describe` function of the generators, but feel free to use the regular describe block as long as you set its :type option to :acceptance.
|
81
83
|
|
82
84
|
## Installation
|
83
85
|
|
@@ -89,11 +91,28 @@ Then to fill out the directories run:
|
|
89
91
|
|
90
92
|
rails generate saki:install
|
91
93
|
|
92
|
-
|
93
|
-
|
94
|
-
|
94
|
+
You can generate new acceptance tests with `rails generate saki:spec SPEC_NAME`. This automatically generates tests like
|
95
|
+
|
96
|
+
require File.dirname(__FILE__) + '/acceptance_helper'
|
95
97
|
|
96
|
-
|
98
|
+
integrate "author resource" do
|
99
|
+
|
100
|
+
on_visiting new_author_path do
|
101
|
+
specify { fail "not implemented" }
|
102
|
+
end
|
103
|
+
|
104
|
+
with_existing :author do
|
105
|
+
on_visiting edit_author_path do
|
106
|
+
specify { fail "not implemented" }
|
107
|
+
end
|
108
|
+
on_visiting author_path do
|
109
|
+
specify { fail "not implemented" }
|
110
|
+
end
|
111
|
+
on_visiting authors_path do
|
112
|
+
specify { fail "not implemented" }
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
97
116
|
|
98
117
|
## References
|
99
118
|
|
@@ -101,7 +120,7 @@ The motivation behind my migration from Cucumber and to Saki, are described in b
|
|
101
120
|
|
102
121
|
## Thanks
|
103
122
|
|
104
|
-
The generators are stolen directly from Steak with some
|
123
|
+
The generators are stolen directly from Steak with some adjustments.
|
105
124
|
|
106
125
|
## Note on Patches/Pull Requests
|
107
126
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0
|
1
|
+
0.1.0
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
|
3
|
+
module Saki
|
4
|
+
class SpecGenerator < Rails::Generators::NamedBase
|
5
|
+
|
6
|
+
def resource_name
|
7
|
+
file_name.tableize.singularize
|
8
|
+
end
|
9
|
+
|
10
|
+
source_root File.join(File.dirname(__FILE__), 'templates')
|
11
|
+
|
12
|
+
desc <<-DESC
|
13
|
+
Description:
|
14
|
+
Create an acceptance spec for the feature NAME in the
|
15
|
+
'spec/acceptance' folder.
|
16
|
+
|
17
|
+
Example:
|
18
|
+
`rails generate saki:spec author`
|
19
|
+
|
20
|
+
Creates an acceptance spec for the "author" feature:
|
21
|
+
spec/acceptance/author_spec.rb
|
22
|
+
DESC
|
23
|
+
|
24
|
+
def manifest
|
25
|
+
empty_directory File.join('spec/acceptance', class_path)
|
26
|
+
file_name.gsub!(/_spec$/,"")
|
27
|
+
template 'acceptance_spec.rb', File.join('spec/acceptance', class_path, "#{file_name}_spec.rb")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/acceptance_helper'
|
2
|
+
|
3
|
+
integrate "<%= resource_name %> resource" do
|
4
|
+
|
5
|
+
on_visiting new_<%= resource_name %>_path do
|
6
|
+
specify { fail "not implemented" }
|
7
|
+
end
|
8
|
+
|
9
|
+
with_existing :<%= resource_name %> do
|
10
|
+
on_visiting edit_<%= resource_name %>_path do
|
11
|
+
specify { fail "not implemented" }
|
12
|
+
end
|
13
|
+
on_visiting <%= resource_name %>_path do
|
14
|
+
specify { fail "not implemented" }
|
15
|
+
end
|
16
|
+
on_visiting <%= resource_name.pluralize %>_path do
|
17
|
+
specify { fail "not implemented" }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
data/lib/saki.rb
CHANGED
@@ -4,8 +4,8 @@ module Saki
|
|
4
4
|
module AcceptanceHelpers
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
|
-
def default_factory(name)
|
8
|
-
Factory name
|
7
|
+
def default_factory(name, opts = {})
|
8
|
+
Factory name, opts
|
9
9
|
end
|
10
10
|
|
11
11
|
def get_path(path)
|
@@ -18,7 +18,7 @@ module Saki
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
-
def add_opts(link, opts)
|
21
|
+
def add_opts(link, opts = {})
|
22
22
|
if opts[:parent]
|
23
23
|
link = "/#{opts[:parent].class.to_s.tableize}/#{opts[:parent].id}" + link
|
24
24
|
end
|
@@ -64,17 +64,14 @@ module Saki
|
|
64
64
|
else
|
65
65
|
current_path.should match(page_name)
|
66
66
|
end
|
67
|
-
end
|
68
|
-
|
69
|
-
def index_path_for(model)
|
70
|
-
"/#{model}"
|
71
67
|
end
|
72
68
|
|
73
|
-
|
74
69
|
module ClassMethods
|
75
|
-
def with_existing resource, &block
|
70
|
+
def with_existing resource, opts={}, &block
|
76
71
|
context "with exisiting #{resource}" do
|
77
|
-
before
|
72
|
+
before do
|
73
|
+
instance_variable_set "@#{resource}", default_factory(resource, opts)
|
74
|
+
end
|
78
75
|
module_eval &block
|
79
76
|
end
|
80
77
|
end
|
@@ -99,6 +96,7 @@ module Saki
|
|
99
96
|
end
|
100
97
|
end
|
101
98
|
|
99
|
+
|
102
100
|
def add_opts(link, opts, context)
|
103
101
|
if opts[:parent]
|
104
102
|
"/#{opts[:parent].to_s.pluralize}/#{(context.instance_variable_get('@' + opts[:parent].to_s)).id}" + link
|
@@ -112,7 +110,7 @@ module Saki
|
|
112
110
|
add_opts "/#{resource.to_s.pluralize}/#{(context.instance_variable_get('@' + resource.to_s)).id}/edit", opts, context
|
113
111
|
end
|
114
112
|
end
|
115
|
-
|
113
|
+
|
116
114
|
def show_path_for(resource, opts = {})
|
117
115
|
lambda do |context|
|
118
116
|
add_opts "/#{resource.to_s.pluralize}/#{(context.instance_variable_get('@' + resource.to_s)).id}", opts, context
|
@@ -142,15 +140,43 @@ module Saki
|
|
142
140
|
end
|
143
141
|
|
144
142
|
class RSpec::Core::ExampleGroup
|
145
|
-
|
143
|
+
|
144
|
+
def self.method_missing(methId, *args)
|
145
|
+
parse_opts = lambda {|link , opts, context|
|
146
|
+
opts ||= {}
|
147
|
+
if opts[:parent]
|
148
|
+
"/#{opts[:parent].to_s.pluralize}/#{(context.instance_variable_get('@' + opts[:parent].to_s)).id}" + link
|
149
|
+
else
|
150
|
+
link
|
151
|
+
end
|
152
|
+
}
|
153
|
+
|
146
154
|
str = methId.id2name
|
147
|
-
if str.match /(.*)_path/
|
148
|
-
|
155
|
+
if str.match /new_(.*)_path/
|
156
|
+
lambda { |context|
|
157
|
+
parse_opts.call "/#{$1.pluralize}/new", args.first, context
|
158
|
+
}
|
159
|
+
elsif str.match /edit_(.*)_path/
|
160
|
+
lambda { |context|
|
161
|
+
model = context.instance_variable_get "@#{$1}"
|
162
|
+
parse_opts.call "/#{model.class.to_s.tableize}/#{model.id}/edit", args.first, context
|
163
|
+
}
|
164
|
+
elsif str.match /(.*)_path/
|
165
|
+
pluralized = $1.pluralize
|
166
|
+
if pluralized == $1
|
167
|
+
lambda { |context| parse_opts.call "/#{$1}", args.first, context }
|
168
|
+
else
|
169
|
+
lambda { |context|
|
170
|
+
model = context.instance_variable_get "@#{$1}"
|
171
|
+
parse_opts.call "/#{model.class.to_s.tableize}/#{model.id}", args.first, context
|
172
|
+
}
|
173
|
+
end
|
149
174
|
else
|
150
175
|
super(methId, [])
|
151
176
|
end
|
152
177
|
|
153
178
|
end
|
179
|
+
|
154
180
|
end
|
155
181
|
|
156
182
|
module RSpec::Core::ObjectExtensions
|
@@ -161,6 +187,4 @@ module RSpec::Core::ObjectExtensions
|
|
161
187
|
end
|
162
188
|
end
|
163
189
|
|
164
|
-
|
165
|
-
|
166
190
|
RSpec.configuration.include Saki::AcceptanceHelpers, :type => :acceptance
|
data/saki.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{saki}
|
8
|
-
s.version = "0.0
|
8
|
+
s.version = "0.1.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Nate Kidwell"]
|
12
|
-
s.date = %q{2010-08-
|
12
|
+
s.date = %q{2010-08-31}
|
13
13
|
s.description = %q{Cucumber scenarios are long and confusing sometimes. Release yourself from the tyranny of client-centered specing!}
|
14
14
|
s.email = %q{nate@ludicast.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -24,7 +24,9 @@ Gem::Specification.new do |s|
|
|
24
24
|
"Rakefile",
|
25
25
|
"VERSION",
|
26
26
|
"lib/generators/saki/install_generator.rb",
|
27
|
+
"lib/generators/saki/spec_generator.rb",
|
27
28
|
"lib/generators/saki/templates/acceptance_helper.rb",
|
29
|
+
"lib/generators/saki/templates/acceptance_spec.rb",
|
28
30
|
"lib/generators/saki/templates/helpers.rb",
|
29
31
|
"lib/saki.rb",
|
30
32
|
"saki.gemspec",
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: saki
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 27
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
+
- 1
|
8
9
|
- 0
|
9
|
-
|
10
|
-
version: 0.0.4
|
10
|
+
version: 0.1.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Nate Kidwell
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-08-
|
18
|
+
date: 2010-08-31 00:00:00 -04:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -49,7 +49,9 @@ files:
|
|
49
49
|
- Rakefile
|
50
50
|
- VERSION
|
51
51
|
- lib/generators/saki/install_generator.rb
|
52
|
+
- lib/generators/saki/spec_generator.rb
|
52
53
|
- lib/generators/saki/templates/acceptance_helper.rb
|
54
|
+
- lib/generators/saki/templates/acceptance_spec.rb
|
53
55
|
- lib/generators/saki/templates/helpers.rb
|
54
56
|
- lib/saki.rb
|
55
57
|
- saki.gemspec
|