machinist 1.0.6 → 2.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -2
- data/Gemfile +8 -0
- data/MIT-LICENSE +2 -1
- data/README.markdown +39 -271
- data/Rakefile +22 -14
- data/lib/generators/machinist/install/USAGE +2 -0
- data/lib/generators/machinist/install/install_generator.rb +48 -0
- data/lib/generators/machinist/install/templates/blueprints.rb +9 -0
- data/lib/generators/machinist/install/templates/machinist.rb.erb +10 -0
- data/lib/generators/machinist/model/model_generator.rb +13 -0
- data/lib/machinist.rb +11 -105
- data/lib/machinist/active_record.rb +8 -93
- data/lib/machinist/active_record/blueprint.rb +41 -0
- data/lib/machinist/active_record/lathe.rb +24 -0
- data/lib/machinist/blueprint.rb +89 -0
- data/lib/machinist/exceptions.rb +32 -0
- data/lib/machinist/lathe.rb +69 -0
- data/lib/machinist/machinable.rb +97 -0
- data/lib/machinist/shop.rb +52 -0
- data/lib/machinist/warehouse.rb +36 -0
- data/spec/active_record_spec.rb +100 -169
- data/spec/blueprint_spec.rb +74 -0
- data/spec/exceptions_spec.rb +20 -0
- data/spec/inheritance_spec.rb +104 -0
- data/spec/machinable_spec.rb +101 -0
- data/spec/shop_spec.rb +94 -0
- data/spec/spec_helper.rb +4 -6
- data/spec/support/active_record_environment.rb +65 -0
- data/spec/warehouse_spec.rb +24 -0
- metadata +52 -40
- data/.autotest +0 -7
- data/FAQ.markdown +0 -18
- data/VERSION +0 -1
- data/init.rb +0 -2
- data/lib/machinist/blueprints.rb +0 -25
- data/lib/machinist/data_mapper.rb +0 -83
- data/lib/machinist/object.rb +0 -30
- data/lib/machinist/sequel.rb +0 -62
- data/lib/sham.rb +0 -77
- data/machinist.gemspec +0 -72
- data/spec/data_mapper_spec.rb +0 -134
- data/spec/db/.gitignore +0 -1
- data/spec/db/schema.rb +0 -20
- data/spec/log/.gitignore +0 -1
- data/spec/machinist_spec.rb +0 -190
- data/spec/sequel_spec.rb +0 -146
- data/spec/sham_spec.rb +0 -95
data/.gitignore
CHANGED
data/Gemfile
ADDED
data/MIT-LICENSE
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Copyright (c) 2008 Peter Yandell
|
1
|
+
Copyright (c) 2008-2010 Peter Yandell
|
2
2
|
|
3
3
|
Permission is hereby granted, free of charge, to any person obtaining
|
4
4
|
a copy of this software and associated documentation files (the
|
@@ -18,3 +18,4 @@ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
18
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
19
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
20
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
data/README.markdown
CHANGED
@@ -1,295 +1,58 @@
|
|
1
|
-
Machinist
|
2
|
-
=========
|
1
|
+
# Machinist 2
|
3
2
|
|
4
3
|
*Fixtures aren't fun. Machinist is.*
|
5
|
-
|
6
|
-
Machinist makes it easy to create test data within your tests. It generates data for the fields you don't care about, and constructs any necessary associated objects, leaving you to only specify the fields you *do* care about in your tests. For example:
|
7
|
-
|
8
|
-
describe Comment do
|
9
|
-
before do
|
10
|
-
# This will make a Comment, a Post, and a User (the author of
|
11
|
-
# the Post), and generate values for all their attributes:
|
12
|
-
@comment = Comment.make(:spam => true)
|
13
|
-
end
|
14
|
-
|
15
|
-
it "should not include comments marked as spam in the without_spam named scope" do
|
16
|
-
Comment.without_spam.should_not include(@comment)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
You tell Machinist how to do this with blueprints:
|
21
|
-
|
22
|
-
require 'machinist/active_record'
|
23
|
-
require 'sham'
|
24
|
-
require 'faker'
|
25
|
-
|
26
|
-
Sham.name { Faker::Name.name }
|
27
|
-
Sham.email { Faker::Internet.email }
|
28
|
-
Sham.title { Faker::Lorem.sentence }
|
29
|
-
Sham.body { Faker::Lorem.paragraph }
|
30
|
-
|
31
|
-
User.blueprint do
|
32
|
-
name
|
33
|
-
email
|
34
|
-
end
|
35
|
-
|
36
|
-
Post.blueprint do
|
37
|
-
title
|
38
|
-
author
|
39
|
-
body
|
40
|
-
end
|
41
|
-
|
42
|
-
Comment.blueprint do
|
43
|
-
post
|
44
|
-
author_name { Sham.name }
|
45
|
-
author_email { Sham.email }
|
46
|
-
body
|
47
|
-
end
|
48
|
-
|
49
|
-
|
50
|
-
Download & Install
|
51
|
-
==================
|
52
|
-
|
53
|
-
### Installing as a Rails plugin
|
54
|
-
|
55
|
-
./script/plugin install git://github.com/notahat/machinist.git
|
56
|
-
|
57
|
-
### Installing as a Gem
|
58
|
-
|
59
|
-
sudo gem install machinist --source http://gemcutter.org
|
60
|
-
|
61
|
-
### Setting up your project
|
62
|
-
|
63
|
-
Create a `blueprints.rb` file to hold your blueprints in your test (or spec) directory. It should start with:
|
64
|
-
|
65
|
-
require 'machinist/active_record'
|
66
|
-
require 'sham'
|
67
|
-
|
68
|
-
Substitute `data_mapper` or `sequel` for `active_record` if that's your weapon of choice.
|
69
|
-
|
70
|
-
Require `blueprints.rb` in your `test_helper.rb` (or `spec_helper.rb`):
|
71
|
-
|
72
|
-
require File.expand_path(File.dirname(__FILE__) + "/blueprints")
|
73
|
-
|
74
|
-
Set Sham to reset before each test. In the `class Test::Unit::TestCase` block in your `test_helper.rb`, add:
|
75
|
-
|
76
|
-
setup { Sham.reset }
|
77
|
-
|
78
|
-
or, if you're on RSpec, in the `Spec::Runner.configure` block in your `spec_helper.rb`, add:
|
79
|
-
|
80
|
-
config.before(:all) { Sham.reset(:before_all) }
|
81
|
-
config.before(:each) { Sham.reset(:before_each) }
|
82
|
-
|
83
|
-
|
84
|
-
Documentation
|
85
|
-
=============
|
86
|
-
|
87
|
-
Sham - Generating Attribute Values
|
88
|
-
----------------------------------
|
89
|
-
|
90
|
-
Sham lets you generate random but repeatable unique attributes values.
|
91
|
-
|
92
|
-
For example, you could define a way to generate random names as:
|
93
|
-
|
94
|
-
Sham.name { (1..10).map { ('a'..'z').to_a.rand }.join }
|
95
|
-
|
96
|
-
Then, to generate a name, call:
|
97
|
-
|
98
|
-
Sham.name
|
99
|
-
|
100
|
-
So why not just define a helper method to do this? Sham ensures two things for you:
|
101
|
-
|
102
|
-
1. You get the same sequence of values each time your test is run
|
103
|
-
2. You don't get any duplicate values
|
104
|
-
|
105
|
-
Sham works very well with the excellent [Faker gem](http://faker.rubyforge.org/) by Benjamin Curtis. Using this, a much nicer way to generate names is:
|
106
|
-
|
107
|
-
Sham.name { Faker::Name.name }
|
108
|
-
|
109
|
-
Sham also supports generating numbered sequences if you prefer.
|
110
|
-
|
111
|
-
Sham.name {|index| "Name #{index}" }
|
112
|
-
|
113
|
-
If you want to allow duplicate values for a sham, you can pass the `:unique` option:
|
114
|
-
|
115
|
-
Sham.coin_toss(:unique => false) { rand(2) == 0 ? 'heads' : 'tails' }
|
116
|
-
|
117
|
-
You can create a bunch of sham definitions in one hit like this:
|
118
|
-
|
119
|
-
Sham.define do
|
120
|
-
title { Faker::Lorem.words(5).join(' ') }
|
121
|
-
name { Faker::Name.name }
|
122
|
-
body { Faker::Lorem.paragraphs(3).join("\n\n") }
|
123
|
-
end
|
124
|
-
|
125
|
-
|
126
|
-
Blueprints - Generating Objects
|
127
|
-
-------------------------------
|
128
|
-
|
129
|
-
A blueprint describes how to generate an object. The idea is that you let the blueprint take care of making up values for attributes that you don't care about in your test, leaving you to focus on the just the things that you're testing.
|
130
|
-
|
131
|
-
A simple blueprint might look like this:
|
132
|
-
|
133
|
-
Post.blueprint do
|
134
|
-
title { Sham.title }
|
135
|
-
author { Sham.name }
|
136
|
-
body { Sham.body }
|
137
|
-
end
|
138
4
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
5
|
+
- [Home page](http://github.com/notahat/machinist/tree/machinist2)
|
6
|
+
- [What's new in Machinist 2](http://wiki.github.com/notahat/machinist/machinist-2)
|
7
|
+
- [Installation](http://wiki.github.com/notahat/machinist/installation)
|
8
|
+
- [Documentation](http://wiki.github.com/notahat/machinist/getting-started)
|
9
|
+
- [Google group](http://groups.google.com/group/machinist-users)
|
10
|
+
- [Issue tracker](http://github.com/notahat/machinist/issues)
|
144
11
|
|
145
|
-
You can override values defined in the blueprint by passing a hash to make:
|
146
12
|
|
147
|
-
|
148
|
-
|
149
|
-
If you don't supply a block for an attribute in the blueprint, Machinist will look for a Sham definition with the same name as the attribute, so you can shorten the above blueprint to:
|
13
|
+
# Introduction
|
150
14
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
end
|
156
|
-
|
157
|
-
If you want to generate an object without saving it to the database, replace `make` with `make_unsaved`. (`make_unsaved` also ensures that any associated objects that need to be generated are not saved - although not if you are using Sequel. See the section on associations below.)
|
15
|
+
Machinist makes it easy to create objects within your tests. It generates data
|
16
|
+
for the attributes you don't care about, and constructs any necessary
|
17
|
+
associated objects, leaving you to specify only the attributes you *do* care
|
18
|
+
about in your tests. For example:
|
158
19
|
|
159
|
-
|
20
|
+
describe Comment do
|
21
|
+
it "should not include spam in the without_spam scope" do
|
22
|
+
# This will make a Comment, a Post, and a User (the author of the
|
23
|
+
# Post), generate values for all their attributes, and save them:
|
24
|
+
spam = Comment.make!(:spam => true)
|
160
25
|
|
161
|
-
|
162
|
-
|
163
|
-
author { Sham.name }
|
164
|
-
body { "Post by #{author}" }
|
26
|
+
Comment.without_spam.should_not include(spam)
|
27
|
+
end
|
165
28
|
end
|
166
|
-
|
167
29
|
|
168
|
-
|
30
|
+
You tell Machinist how to do this with blueprints:
|
169
31
|
|
170
|
-
|
32
|
+
require 'machinist/active_record'
|
171
33
|
|
172
34
|
User.blueprint do
|
173
|
-
|
174
|
-
email
|
175
|
-
end
|
176
|
-
|
177
|
-
User.blueprint(:admin) do
|
178
|
-
name { Sham.name + " (admin)" }
|
179
|
-
admin { true }
|
35
|
+
username { "user#{sn}" } # Each user gets a unique serial number.
|
180
36
|
end
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
will use the `:admin` blueprint.
|
187
|
-
|
188
|
-
Named blueprints call the default blueprint to set any attributes not specifically provided, so in this example the `email` attribute will still be generated even for an admin user.
|
189
|
-
|
190
|
-
|
191
|
-
### Belongs\_to Associations
|
192
|
-
|
193
|
-
If you're generating an object that belongs to another object, you can generate the associated object like this:
|
194
|
-
|
195
|
-
Comment.blueprint do
|
196
|
-
post { Post.make }
|
37
|
+
|
38
|
+
Post.blueprint do
|
39
|
+
author
|
40
|
+
title { "Post #{sn}" }
|
41
|
+
body { "Lorem ipsum..." }
|
197
42
|
end
|
198
|
-
|
199
|
-
Calling `Comment.make` will construct a Comment and its associated Post, and save both.
|
200
43
|
|
201
|
-
If you want to override the value for post when constructing the comment, you can do this:
|
202
|
-
|
203
|
-
post = Post.make(:title => "A particular title)
|
204
|
-
comment = Comment.make(:post => post)
|
205
|
-
|
206
|
-
Machinist will not call the blueprint block for the post attribute, so this won't generate two posts.
|
207
|
-
|
208
|
-
Machinist is smart enough to look at the association and work out what sort of object it needs to create, so you can shorten the above blueprint to:
|
209
|
-
|
210
44
|
Comment.blueprint do
|
211
45
|
post
|
46
|
+
email { "commenter-#{sn}@example.com" }
|
47
|
+
body { "Lorem ipsum..." }
|
212
48
|
end
|
213
49
|
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
For has\_many and has\_and\_belongs\_to\_many associations, ActiveRecord insists that the object be saved before any associated objects can be saved. That means you can't generate the associated objects from within the blueprint.
|
218
|
-
|
219
|
-
The simplest solution is to write a test helper:
|
220
|
-
|
221
|
-
def make_post_with_comments(attributes = {})
|
222
|
-
post = Post.make(attributes)
|
223
|
-
3.times { post.comments.make }
|
224
|
-
post
|
225
|
-
end
|
226
|
-
|
227
|
-
Note here that you can call `make` on a has\_many association. (This isn't yet supported for DataMapper.)
|
228
|
-
|
229
|
-
Make can take a block, into which it passes the constructed object, so the above can be written as:
|
230
|
-
|
231
|
-
def make_post_with_comments
|
232
|
-
Post.make(attributes) do |post|
|
233
|
-
3.times { post.comments.make }
|
234
|
-
end
|
235
|
-
end
|
236
|
-
|
237
|
-
|
238
|
-
### Using Blueprints in Rails Controller Tests
|
239
|
-
|
240
|
-
The `plan` method behaves like `make`, except it returns a hash of attributes, and doesn't save the object. This is useful for passing in to controller tests:
|
241
|
-
|
242
|
-
test "should create post" do
|
243
|
-
assert_difference('Post.count') do
|
244
|
-
post :create, :post => Post.plan
|
245
|
-
end
|
246
|
-
assert_redirected_to post_path(assigns(:post))
|
247
|
-
end
|
248
|
-
|
249
|
-
`plan` will save any associated objects. In this example, it will create an Author, and it knows that the controller expects an `author_id` attribute, rather than an `author` attribute, and makes this translation for you.
|
250
|
-
|
251
|
-
You can also call plan on has\_many associations, making it easy to test nested controllers:
|
252
|
-
|
253
|
-
test "should create comment" do
|
254
|
-
post = Post.make
|
255
|
-
assert_difference('Comment.count') do
|
256
|
-
post :create, :post_id => post.id, :comment => post.comments.plan
|
257
|
-
end
|
258
|
-
assert_redirected_to post_comment_path(post, assigns(:comment))
|
259
|
-
end
|
260
|
-
|
261
|
-
(Calling plan on associations is not yet supported in DataMapper.)
|
262
|
-
|
50
|
+
Check out the
|
51
|
+
[documentation](http://wiki.github.com/notahat/machinist/getting-started) for
|
52
|
+
more info.
|
263
53
|
|
264
|
-
### Blueprints on Plain Old Ruby Objects
|
265
54
|
|
266
|
-
|
267
|
-
|
268
|
-
class Post
|
269
|
-
attr_accessor :title
|
270
|
-
attr_accessor :body
|
271
|
-
end
|
272
|
-
|
273
|
-
You can then do the following in your `blueprints.rb`:
|
274
|
-
|
275
|
-
require 'machinist/object'
|
276
|
-
|
277
|
-
Post.blueprint do
|
278
|
-
title "A title!"
|
279
|
-
body "A body!"
|
280
|
-
end
|
281
|
-
|
282
|
-
Community
|
283
|
-
=========
|
284
|
-
|
285
|
-
You can always find the [latest version on GitHub](http://github.com/notahat/machinist).
|
286
|
-
|
287
|
-
If you have questions, check out the [Google Group](http://groups.google.com/group/machinist-users).
|
288
|
-
|
289
|
-
File bug reports and feature requests in the [issue tracker](http://github.com/notahat/machinist/issues).
|
290
|
-
|
291
|
-
Contributors
|
292
|
-
------------
|
55
|
+
## Contributors
|
293
56
|
|
294
57
|
Machinist is maintained by Pete Yandell ([pete@notahat.com](mailto:pete@notahat.com), [@notahat](http://twitter.com/notahat))
|
295
58
|
|
@@ -297,6 +60,7 @@ Other contributors include:
|
|
297
60
|
|
298
61
|
[Marcos Arias](http://github.com/yizzreel),
|
299
62
|
[Jack Dempsey](http://github.com/jackdempsey),
|
63
|
+
[Jeremy Durham](http://github.com/jeremydurham),
|
300
64
|
[Clinton Forbes](http://github.com/clinton),
|
301
65
|
[Perryn Fowler](http://github.com/perryn),
|
302
66
|
[Niels Ganser](http://github.com/Nielsomat),
|
@@ -314,4 +78,8 @@ Other contributors include:
|
|
314
78
|
[Matt Wastrodowski](http://github.com/towski),
|
315
79
|
[Ian White](http://github.com/ianwhite)
|
316
80
|
|
317
|
-
Thanks to Thoughtbot's [Factory
|
81
|
+
Thanks to Thoughtbot's [Factory
|
82
|
+
Girl](http://github.com/thoughtbot/factory_girl/tree/master). Machinist was
|
83
|
+
written because I loved the idea behind Factory Girl, but I thought the
|
84
|
+
philosophy wasn't quite right, and I hated the syntax.
|
85
|
+
|
data/Rakefile
CHANGED
@@ -1,4 +1,10 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
Bundler.setup
|
4
|
+
|
1
5
|
require 'rake'
|
6
|
+
require 'rspec/core/rake_task'
|
7
|
+
require 'rake/rdoctask'
|
2
8
|
|
3
9
|
begin
|
4
10
|
require 'jeweler'
|
@@ -9,29 +15,31 @@ begin
|
|
9
15
|
gem.homepage = "http://github.com/notahat/machinist"
|
10
16
|
gem.authors = ["Pete Yandell"]
|
11
17
|
gem.has_rdoc = false
|
12
|
-
gem.add_development_dependency "rspec", ">= 1.2.8"
|
13
18
|
end
|
14
19
|
Jeweler::GemcutterTasks.new
|
15
20
|
rescue LoadError
|
16
|
-
puts "Jeweler
|
21
|
+
puts "Jeweler not available. Install it with: gem install jeweler"
|
17
22
|
end
|
18
23
|
|
19
24
|
|
20
|
-
|
21
|
-
desc 'Run the specs.'
|
22
|
-
Spec::Rake::SpecTask.new(:spec) do |spec|
|
23
|
-
spec.libs << 'lib' << 'spec'
|
24
|
-
spec.spec_files = FileList['spec/**/*_spec.rb']
|
25
|
-
end
|
25
|
+
RSpec::Core::RakeTask.new
|
26
26
|
|
27
|
-
|
28
|
-
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
29
|
-
spec.libs << 'lib' << 'spec'
|
30
|
-
spec.pattern = 'spec/**/*_spec.rb'
|
27
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
31
28
|
spec.rcov = true
|
29
|
+
spec.rcov_opts = ['--exclude', 'spec', '--exclude', '.rvm']
|
32
30
|
end
|
33
31
|
|
34
|
-
task :spec => :check_dependencies
|
35
|
-
|
36
32
|
desc 'Run the specs.'
|
37
33
|
task :default => :spec
|
34
|
+
|
35
|
+
|
36
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
37
|
+
rdoc.rdoc_dir = 'doc'
|
38
|
+
rdoc.title = 'Machinist'
|
39
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
40
|
+
rdoc.rdoc_files.include('lib')
|
41
|
+
end
|
42
|
+
|
43
|
+
task :notes do
|
44
|
+
system "grep -n -r 'FIXME\\|TODO' lib spec"
|
45
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Machinist
|
2
|
+
module Generators #:nodoc:
|
3
|
+
class InstallGenerator < Rails::Generators::Base #:nodoc:
|
4
|
+
source_root File.expand_path('../templates', __FILE__)
|
5
|
+
|
6
|
+
class_option :test_framework, :type => :string, :aliases => "-t", :desc => "Test framework to use Machinist with"
|
7
|
+
class_option :cucumber, :type => :boolean, :desc => "Set up access to Machinist from Cucumber"
|
8
|
+
|
9
|
+
def blueprints_file
|
10
|
+
if rspec?
|
11
|
+
copy_file "blueprints.rb", "spec/support/blueprints.rb"
|
12
|
+
else
|
13
|
+
copy_file "blueprints.rb", "test/blueprints.rb"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_helper
|
18
|
+
if rspec?
|
19
|
+
inject_into_file("spec/spec_helper.rb", :after => "Rspec.configure do |config|\n") do
|
20
|
+
" # Reset the Machinist cache before each spec.\n" +
|
21
|
+
" config.before(:each) { Machinist.reset_before_test }\n\n"
|
22
|
+
end
|
23
|
+
else
|
24
|
+
inject_into_file("test/test_helper.rb", :after => "require 'rails/test_help'\n") do
|
25
|
+
"require File.expand_path(File.dirname(__FILE__) + '/blueprints')\n"
|
26
|
+
end
|
27
|
+
inject_into_class("test/test_helper.rb", ActiveSupport::TestCase) do
|
28
|
+
" # Reset the Machinist cache before each test.\n" +
|
29
|
+
" setup { Machinist.reset_before_test }\n\n"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def cucumber_support
|
35
|
+
if options[:cucumber]
|
36
|
+
template "machinist.rb.erb", "features/support/machinist.rb"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def rspec?
|
43
|
+
options[:test_framework].to_sym == :rspec
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|