datum 4.1.3 → 4.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/MIT-LICENSE +44 -0
- data/README.md +311 -0
- data/Rakefile +32 -0
- data/lib/datum/container.rb +81 -0
- data/lib/datum/datum.rb +69 -0
- data/lib/datum/helpers.rb +67 -0
- data/lib/datum/version.rb +24 -0
- data/lib/datum.rb +45 -0
- data/lib/plan9/structures.rb +76 -0
- data/lib/support/scenario.rb +42 -0
- data/lib/support/test.rb +80 -0
- metadata +17 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dd71e45fa21da8dee623296b6364276aea53e046
|
4
|
+
data.tar.gz: c62a33f1b5d2d0372eae2dd79f3d1a88a564c9d2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 656998cde9a7af4d4548e1d77e2f25ca4a09745479ef4840d680ae62a9115ff87395bd0ad5e8186fe5f0db995c9c5bad272773b1b17c09da2ffa03468556e07e
|
7
|
+
data.tar.gz: f5d4b814485362e88872ee61b54f851c1033b92d5ffe3edb0571cc3a89d0c0c3d75bfbbe3e09e5e7edcf84208f17b81c97dcb151b7b090babf6798a3f18b8dfb
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
Copyright 2015 Tyemill
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
22
|
+
#### From https://github.com/iconara/immutable_struct/blob/master/LICENSE
|
23
|
+
#### Provided due to use of immutable_struct/blob/master/lib/immutable_struct.rb
|
24
|
+
|
25
|
+
Copyright (c) 2010 Theo Hultberg
|
26
|
+
|
27
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
28
|
+
a copy of this software and associated documentation files (the
|
29
|
+
"Software"), to deal in the Software without restriction, including
|
30
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
31
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
32
|
+
permit persons to whom the Software is furnished to do so, subject to
|
33
|
+
the following conditions:
|
34
|
+
|
35
|
+
The above copyright notice and this permission notice shall be
|
36
|
+
included in all copies or substantial portions of the Software.
|
37
|
+
|
38
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
39
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
40
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
41
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
42
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
43
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
44
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,311 @@
|
|
1
|
+
## Datum: A flexible data-driven test solution for Rails
|
2
|
+
|
3
|
+
### Synopsis
|
4
|
+
Datum includes an easy-to-use mechanism for generating test cases based on data attributes. For more granular control when seeding the test database, Datum introduces Scenarios. By combining these features, Datum can be extremely useful and fun.
|
5
|
+
|
6
|
+
|
7
|
+
### Feature List
|
8
|
+
**1. Tests coupled with datasets**: Datum adds a data-driven extension to the default Rails testing infrastructure. When a data_test is defined, it is coupled with a dataset you define. For each dataset, a test case is generated greatly simplifying adding and removing additional cases.
|
9
|
+
|
10
|
+
**2. On-demand test database seeding**: Datum Scenarios are a per-test or per-test-class database seeding mechanism. Each Scenario can use any set of Models in a single file and can be self-contained or referenced via other Scenarios.
|
11
|
+
|
12
|
+
### Installing
|
13
|
+
Update your Gemfile with:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
gem 'datum'
|
17
|
+
```
|
18
|
+
|
19
|
+
Next run the bundle command:
|
20
|
+
|
21
|
+
```console
|
22
|
+
bundle install
|
23
|
+
```
|
24
|
+
|
25
|
+
To create the default datum directories:
|
26
|
+
|
27
|
+
```console
|
28
|
+
rake datum:install
|
29
|
+
```
|
30
|
+
|
31
|
+
### Simple Examples
|
32
|
+
|
33
|
+
#### data_test: Data Driven Tests for Rails
|
34
|
+
To get started, we'll look at a simple Person Model app/models/person.rb:
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
class Person < ActiveRecord::Base
|
38
|
+
|
39
|
+
validates_presence_of :first_name, :last_name
|
40
|
+
|
41
|
+
# "John Doe" from first_name: "John", last_name: "Doe"
|
42
|
+
def name
|
43
|
+
"#{self.first_name} #{self.last_name}"
|
44
|
+
end
|
45
|
+
|
46
|
+
# "John D." from first_name: "John" last_name: "Doe"
|
47
|
+
def short_name
|
48
|
+
"#{self.first_name} #{self.last_name.capitalize[0]}."
|
49
|
+
end
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
Now let's create a test test/models/person_test.rb:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
require 'test_helper'
|
57
|
+
class PersonTest < ActiveSupport::TestCase
|
58
|
+
test 'should confirm short_name' do
|
59
|
+
person = Person.create first_name: "Marge", last_name: "Simpson"
|
60
|
+
assert_equal "Marge S.", person.short_name
|
61
|
+
end
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
Executing the test:
|
66
|
+
|
67
|
+
```console
|
68
|
+
# Running:
|
69
|
+
|
70
|
+
.
|
71
|
+
|
72
|
+
1 runs, 1 assertions, 0 failures, 0 errors, 0 skips
|
73
|
+
```
|
74
|
+
|
75
|
+
To convert our test to be data-driven - test/models/person_test.rb:
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
require 'test_helper'
|
79
|
+
class PersonTest < ActiveSupport::TestCase
|
80
|
+
#test 'should confirm short_name' do
|
81
|
+
# person = Person.create first_name: "Marge", last_name: "Simpson"
|
82
|
+
# assert_equal "Marge S.", person.short_name
|
83
|
+
#end
|
84
|
+
|
85
|
+
data_test 'should_confirm_shortname' do
|
86
|
+
person = Person.create first_name: @datum.first_name, last_name: @datum.last_name
|
87
|
+
assert_equal @datum.short_name, person.short_name
|
88
|
+
end
|
89
|
+
end
|
90
|
+
```
|
91
|
+
|
92
|
+
In the data_test, the fixed values of the original test have been replaced with the usage of the @datum.[attribute] variables. For each dataset defined, the data_test will be called and @datum will provide access.
|
93
|
+
|
94
|
+
Next, we'll define our Datum and data in test/datum/data/should_confirm_shortname.rb:
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
# Sub-class the Datum struct with attributes we need for our test
|
98
|
+
SimpleShortName = Datum.new(:first_name, :last_name, :short_name)
|
99
|
+
|
100
|
+
# Define instances for our test cases
|
101
|
+
SimpleShortName.new "Marge", "Simpson", "Marge S."
|
102
|
+
SimpleShortName.new "Homer", "Simpson", "Homer S."
|
103
|
+
```
|
104
|
+
|
105
|
+
Executing:
|
106
|
+
|
107
|
+
```console
|
108
|
+
# Running:
|
109
|
+
|
110
|
+
..
|
111
|
+
|
112
|
+
2 runs, 2 assertions, 0 failures, 0 errors, 0 skips
|
113
|
+
```
|
114
|
+
|
115
|
+
Adding more datasets and thus more generated test cases test/datum/data/should_confirm_shortname.rb:
|
116
|
+
|
117
|
+
```ruby
|
118
|
+
SimpleShortName = Datum.new(:first_name, :last_name, :short_name)
|
119
|
+
|
120
|
+
# Define instances for our test cases
|
121
|
+
SimpleShortName.new "Marge", "Simpson", "Marge S."
|
122
|
+
SimpleShortName.new "Homer", "Simpson", "Homer S."
|
123
|
+
SimpleShortName.new "Lisa", "Simpson", "Lisa S."
|
124
|
+
SimpleShortName.new "Bart", "Simpson", "Bart S."
|
125
|
+
SimpleShortName.new "Maggie", "Simpson", "Maggie S."
|
126
|
+
```
|
127
|
+
|
128
|
+
```console
|
129
|
+
# Running:
|
130
|
+
|
131
|
+
.....
|
132
|
+
|
133
|
+
5 runs, 5 assertions, 0 failures, 0 errors, 0 skips
|
134
|
+
```
|
135
|
+
|
136
|
+
#### Scenarios: On-demand Test Database Seeding
|
137
|
+
To get started, we'll look at a simple Person Model app/models/person.rb:
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
class Person < ActiveRecord::Base
|
141
|
+
|
142
|
+
validates_presence_of :first_name, :last_name
|
143
|
+
|
144
|
+
# "John Doe" from first_name: "John", last_name: "Doe"
|
145
|
+
def name
|
146
|
+
"#{self.first_name} #{self.last_name}"
|
147
|
+
end
|
148
|
+
|
149
|
+
# "John D." from first_name: "John" last_name: "Doe"
|
150
|
+
def short_name
|
151
|
+
"#{self.first_name} #{self.last_name.capitalize[0]}."
|
152
|
+
end
|
153
|
+
end
|
154
|
+
```
|
155
|
+
|
156
|
+
Now let's create a test test/models/person_test.rb:
|
157
|
+
|
158
|
+
```ruby
|
159
|
+
require 'test_helper'
|
160
|
+
class PersonTest < ActiveSupport::TestCase
|
161
|
+
test 'should confirm short_name' do
|
162
|
+
person = Person.create first_name: "Marge", last_name: "Simpson"
|
163
|
+
assert_equal "Marge S.", person.short_name
|
164
|
+
end
|
165
|
+
end
|
166
|
+
```
|
167
|
+
|
168
|
+
Instead of using a Fixture or defining our variables in-line, we'll modify our test to use a Scenario. First, let's define the Scenario test/datum/scenarios/simpsons_scenario.rb:
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
@marge = Person.create(first_name: "Marge", last_name: "Simpson")
|
172
|
+
@homer = Person.create(__clone(@marge, {first_name: "Homer"}))
|
173
|
+
```
|
174
|
+
|
175
|
+
Now, let's modify our test, test/models/person_test.rb:
|
176
|
+
|
177
|
+
```ruby
|
178
|
+
require 'test_helper'
|
179
|
+
class PersonTest < ActiveSupport::TestCase
|
180
|
+
test 'should confirm short_name' do
|
181
|
+
process_scenario :simpsons_scenario
|
182
|
+
assert_equal "Marge S.", @marge.short_name
|
183
|
+
assert_equal "Homer S.", @homer.short_name
|
184
|
+
end
|
185
|
+
end
|
186
|
+
```
|
187
|
+
|
188
|
+
#### Data-Driven Tests Combined with On-Demand Test DB Seeding
|
189
|
+
To get started, we'll look at a simple Person Model app/models/person.rb:
|
190
|
+
|
191
|
+
```ruby
|
192
|
+
class Person < ActiveRecord::Base
|
193
|
+
|
194
|
+
validates_presence_of :first_name, :last_name
|
195
|
+
|
196
|
+
# "John Doe" from first_name: "John", last_name: "Doe"
|
197
|
+
def name
|
198
|
+
"#{self.first_name} #{self.last_name}"
|
199
|
+
end
|
200
|
+
|
201
|
+
# "John D." from first_name: "John" last_name: "Doe"
|
202
|
+
def short_name
|
203
|
+
"#{self.first_name} #{self.last_name.capitalize[0]}."
|
204
|
+
end
|
205
|
+
end
|
206
|
+
```
|
207
|
+
|
208
|
+
Now let's define a data_test test/models/person_test.rb:
|
209
|
+
|
210
|
+
```ruby
|
211
|
+
require 'test_helper'
|
212
|
+
class PersonTest < ActiveSupport::TestCase
|
213
|
+
data_test 'should_confirm_shortname' do
|
214
|
+
person = Person.create first_name: @datum.first_name, last_name: @datum.last_name
|
215
|
+
assert_equal @datum.short_name, person.short_name
|
216
|
+
end
|
217
|
+
end
|
218
|
+
```
|
219
|
+
|
220
|
+
Let's add some initial data test/datum/data/should_confirm_shortname.rb:
|
221
|
+
|
222
|
+
```ruby
|
223
|
+
SimpleShortName = Datum.new(:first_name, :last_name, :short_name)
|
224
|
+
|
225
|
+
# Define instances for our test cases
|
226
|
+
m = SimpleShortName.new "Marge", "Simpson", "Marge S."
|
227
|
+
SimpleShortName.new "Homer", m.last_name, "Homer S."
|
228
|
+
SimpleShortName.new "Lisa", m.last_name, "Lisa S."
|
229
|
+
SimpleShortName.new "Bart", m.last_name, "Bart S."
|
230
|
+
SimpleShortName.new "Maggie", m.last_name, "Maggie S."
|
231
|
+
```
|
232
|
+
|
233
|
+
Now let's define a Scenario test/datum/scenarios/simpsons_scenario.rb:
|
234
|
+
|
235
|
+
```ruby
|
236
|
+
@marge = Person.create(first_name: "Marge", last_name: "Simpson")
|
237
|
+
@homer = Person.create(__clone(@marge, {first_name: "Homer"}))
|
238
|
+
@lisa = Person.create(__clone(@marge, {first_name: "Lisa"}))
|
239
|
+
@bart = Person.create(__clone(@marge, {first_name: "Bart"}))
|
240
|
+
@maggie = Person.create(__clone(@marge, {first_name: "Maggie"}))
|
241
|
+
```
|
242
|
+
|
243
|
+
Now let's update our data_test to make use of our Scenario test/models/person_test.rb:
|
244
|
+
|
245
|
+
```ruby
|
246
|
+
require 'test_helper'
|
247
|
+
class PersonTest < ActiveSupport::TestCase
|
248
|
+
data_test 'should_confirm_shortname' do
|
249
|
+
process_scenario :simpsons_scenario
|
250
|
+
person = self.instance_variable_get("@#{@datum.first_name.downcase}")
|
251
|
+
assert_equal @datum.first_name, person.first_name
|
252
|
+
assert_equal @datum.last_name, person.last_name
|
253
|
+
assert_equal @datum.short_name, person.short_name
|
254
|
+
end
|
255
|
+
end
|
256
|
+
```
|
257
|
+
|
258
|
+
Executing the test:
|
259
|
+
|
260
|
+
```console
|
261
|
+
# Running:
|
262
|
+
|
263
|
+
.....
|
264
|
+
|
265
|
+
5 runs, 15 assertions, 0 failures, 0 errors, 0 skips
|
266
|
+
```
|
267
|
+
|
268
|
+
### Real-World Examples (Not Finished)
|
269
|
+
A Model Test to verify addresses from different countries:
|
270
|
+
|
271
|
+
```ruby
|
272
|
+
require 'test_helper'
|
273
|
+
class AddressLabelTest < ActiveSupport::TestCase
|
274
|
+
|
275
|
+
# makes use of 'AddressLabel' Model
|
276
|
+
data_test 'should_verify_address_labels' do
|
277
|
+
# label = AddressLabel.create
|
278
|
+
|
279
|
+
#assert_equal @datum.
|
280
|
+
#assert_equal @datum.
|
281
|
+
#assert_equal @datum.
|
282
|
+
end
|
283
|
+
end
|
284
|
+
```
|
285
|
+
|
286
|
+
A Functional Test to verify CRUD with different permissions:
|
287
|
+
|
288
|
+
```ruby
|
289
|
+
require 'test_helper'
|
290
|
+
class AddressLabelTest < ActiveSupport::TestCase
|
291
|
+
|
292
|
+
# makes use of 'AddressLabel' Model
|
293
|
+
data_test 'should_verify_address_labels' do
|
294
|
+
# label = AddressLabel.create
|
295
|
+
|
296
|
+
#assert_equal @datum.
|
297
|
+
#assert_equal @datum.
|
298
|
+
#assert_equal @datum.
|
299
|
+
end
|
300
|
+
end
|
301
|
+
```
|
302
|
+
|
303
|
+
|
304
|
+
|
305
|
+
### License
|
306
|
+
|
307
|
+
MIT License. Copyright 2012-2015 Tyemill. http://tyemill.com
|
308
|
+
|
309
|
+
You are not granted rights or licenses to the trademarks of Tyemill, including without limitation the Datum name or logo.
|
310
|
+
|
311
|
+
Datum struct uses ImprovedStruct and ImmutableStruct which are derivitive works based on ImmutableStruct by Theo Hultberg. This great class can be found here: [Immutable Struct](https://github.com/iconara/immutable_struct)
|
data/Rakefile
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'Datum'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.rdoc')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
Bundler::GemHelper.install_tasks
|
21
|
+
|
22
|
+
require 'rake/testtask'
|
23
|
+
|
24
|
+
Rake::TestTask.new(:test) do |t|
|
25
|
+
t.libs << 'lib'
|
26
|
+
t.libs << 'test'
|
27
|
+
t.pattern = 'test/**/*_test.rb'
|
28
|
+
t.verbose = false
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
task default: :test
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require "datum/helpers"
|
2
|
+
|
3
|
+
module Datum
|
4
|
+
# A Container holds attributes for a single data test.
|
5
|
+
#
|
6
|
+
# A data_test definition references a specific file in test/datum/data. When
|
7
|
+
# the data file is loaded, each Datum created is associated with a Container
|
8
|
+
# which in-turn is associated with the data_test.
|
9
|
+
#
|
10
|
+
# Container references are stored in Datum::containers
|
11
|
+
class Container
|
12
|
+
|
13
|
+
# @!attribute [r] data_method_name
|
14
|
+
# The name of the data test method
|
15
|
+
# @return [String]
|
16
|
+
attr_reader :data_method_name
|
17
|
+
|
18
|
+
# @!attribute [r] test_instance
|
19
|
+
# The ActiveSupport::TestCase instance of the data test
|
20
|
+
# @return [ActiveSupport::TestCase]
|
21
|
+
attr_reader :test_instance
|
22
|
+
|
23
|
+
# @!attribute [r] count
|
24
|
+
# The total number of test cases generated for the data method
|
25
|
+
# @return [Fixnum]
|
26
|
+
def count; @loaded_data.count + @invoked_data.count; end;
|
27
|
+
|
28
|
+
# @!attribute [r] data
|
29
|
+
# A Hash of data elements, datum structs for the test case
|
30
|
+
# @return [Hash]
|
31
|
+
def data; @loaded_data.merge(@invoked_data); end
|
32
|
+
|
33
|
+
alias_method :length, :count
|
34
|
+
alias_method :size, :count
|
35
|
+
alias_method :test_count, :count
|
36
|
+
|
37
|
+
# @!visibility private
|
38
|
+
# Creates a Hash key formatted for use with a Container.
|
39
|
+
# @param [TestCase] tst_instance The TestCase instance for the data_test
|
40
|
+
# @param [String] data_method_name The name of the data_test method
|
41
|
+
# @return [String]
|
42
|
+
def self.key tst_instance, data_method_name
|
43
|
+
Helpers.build_key(tst_instance, data_method_name)
|
44
|
+
end
|
45
|
+
|
46
|
+
# @!visibility private
|
47
|
+
# @param [String] data_method_name The name of test method to be called
|
48
|
+
# @param [TestCase] tst_instance The instance containing the data_method_name
|
49
|
+
def initialize(data_method_name, tst_instance)
|
50
|
+
@data_method_name = data_method_name; @test_instance = tst_instance
|
51
|
+
@loaded_data = {}; @invoked_data = {}
|
52
|
+
::Datum.send(:add_container, self,
|
53
|
+
Container.key(@test_instance, @data_method_name))
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def add_datum datum
|
59
|
+
test_name = Helpers.build_test_name(data_method_name, test_count + 1)
|
60
|
+
@loaded_data[Datum.key(test_instance, test_name)] = datum
|
61
|
+
add_data_test test_name
|
62
|
+
[count, test_name]
|
63
|
+
end
|
64
|
+
|
65
|
+
def invoke_datum key, tst_case
|
66
|
+
@invoked_data[key] = datum = @loaded_data.delete(key)
|
67
|
+
tst_case.instance_variable_set :@datum, datum
|
68
|
+
tst_case.send datum.container.data_method_name
|
69
|
+
end
|
70
|
+
|
71
|
+
def add_data_test test_name
|
72
|
+
test_instance.send(:define_method, test_name) do
|
73
|
+
datum_key = Datum.key(nm = self.class.to_s, __method__)
|
74
|
+
container_key = Container.key(nm,
|
75
|
+
Helpers.data_method_from_test_name(__method__))
|
76
|
+
::Datum.containers[container_key].send(:invoke_datum, datum_key, self)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
data/lib/datum/datum.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require "datum/container"
|
2
|
+
require "datum/helpers"
|
3
|
+
require "plan9/structures"
|
4
|
+
|
5
|
+
module Datum
|
6
|
+
# Datum is an Immutable Struct to be used for creating test cases.
|
7
|
+
# @!attribute [r] datum_id
|
8
|
+
# The index of the test case and the data from it's Datum
|
9
|
+
# @return [Fixnum]
|
10
|
+
# @!attribute [r] test_method_name
|
11
|
+
# The name of the test case generated
|
12
|
+
# @return [String]
|
13
|
+
# @!attribute [r] container
|
14
|
+
# The Container reference which generated this Datum and test case
|
15
|
+
# @return [Container]
|
16
|
+
class Datum < Plan9::ImmutableStruct
|
17
|
+
# @!visibility private
|
18
|
+
# Creates a Hash key formatted for use with a Datum
|
19
|
+
# @param [TestCase] test_instance The TestCase instance for the test
|
20
|
+
# @param [String] test_name The name of the test
|
21
|
+
# @return [String] Datum compatible Hash key
|
22
|
+
def self.key test_instance, test_name
|
23
|
+
Helpers.build_key(test_instance, test_name)
|
24
|
+
end
|
25
|
+
|
26
|
+
# @!visibility private
|
27
|
+
def self.new(*attrs, &block)
|
28
|
+
attrs.push "datum_id"
|
29
|
+
super(*attrs, &block)
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
|
34
|
+
def self.init_new(struct)
|
35
|
+
super(struct)
|
36
|
+
datumize_constructor!(struct)
|
37
|
+
struct
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
def self.datumize_constructor! struct
|
42
|
+
|
43
|
+
struct.class_eval do
|
44
|
+
alias_method :datum_initialize, :initialize
|
45
|
+
|
46
|
+
# @!attribute [r] test_method_name
|
47
|
+
# @return [String] The name of the test method
|
48
|
+
attr_reader :test_method_name
|
49
|
+
|
50
|
+
# @!attribute [r] container
|
51
|
+
# @return [Container] A reference to the Container of this Datum
|
52
|
+
attr_reader :container
|
53
|
+
|
54
|
+
# @!visibility private
|
55
|
+
def initialize(*atrs)
|
56
|
+
dtm_id = configure_attributes
|
57
|
+
is_hash_case?(*atrs) ? atrs.first[:datum_id] = dtm_id : atrs.push(dtm_id)
|
58
|
+
datum_initialize(*atrs)
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
def configure_attributes
|
63
|
+
@container = ::Datum.send(:current_container)
|
64
|
+
(dtm_id, @test_method_name = @container.send(:add_datum, self))[0]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Datum
|
2
|
+
|
3
|
+
# @!visibility private
|
4
|
+
# Various helper functions
|
5
|
+
class Helpers
|
6
|
+
class << self
|
7
|
+
# @!visibility private
|
8
|
+
# @param [String] test_name Test case name generated from data_test usage
|
9
|
+
# @return [int] The index of the data_test / datum_id
|
10
|
+
def index_from_test_name test_name
|
11
|
+
((test_name.to_s.split('_')[-1]).to_i)
|
12
|
+
end
|
13
|
+
|
14
|
+
# @!visibility private
|
15
|
+
# @param [String] test_name Test case name generated from data_test usage
|
16
|
+
# @return [String] Name of the data_test method which generated the test
|
17
|
+
def data_method_from_test_name test_name
|
18
|
+
test_name.slice(/(?<=_).*(?=_)/)
|
19
|
+
end
|
20
|
+
|
21
|
+
# @!visibility private
|
22
|
+
# Test name for current test case, index, data_test method
|
23
|
+
# @param [String] data_method_name The name of the data_test method
|
24
|
+
# @param [int] counter The index / datum_id of the current test case
|
25
|
+
# @return [String]
|
26
|
+
def build_test_name data_method_name, counter
|
27
|
+
"test_#{data_method_name}_#{counter}"
|
28
|
+
end
|
29
|
+
|
30
|
+
# @!visibility private
|
31
|
+
# @param [TestCase] test_instance The ActiveSupport::TestCase instance
|
32
|
+
# @param [String] method The name of the method
|
33
|
+
# @return [String] A key for usage in Datum-compatible Hash instances
|
34
|
+
def build_key test_instance, method
|
35
|
+
"#{test_instance}_#{method}"
|
36
|
+
end
|
37
|
+
|
38
|
+
# @!visibility private
|
39
|
+
# @param [String] file_name The name of the file to read
|
40
|
+
# @param [Pathname] directory A Pathname representing the file's directory
|
41
|
+
# @param [String] ext Optional extention of the file (default: '.rb')
|
42
|
+
# @return [String] The file's contents
|
43
|
+
def read_file file_name, directory, ext = ".rb"
|
44
|
+
File.read directory.join("#{file_name}#{ext}")
|
45
|
+
end
|
46
|
+
|
47
|
+
# @!visibility private
|
48
|
+
# Reads a ruby file and eval's it's contents in the context of the Binding
|
49
|
+
# @param [String] file_name The name of the file to import
|
50
|
+
# @param [Pathname] directory A Pathname representing the file's directory
|
51
|
+
# @param [Binding] current_binding Context at a particular code location
|
52
|
+
def import_file file_name, directory, current_binding
|
53
|
+
eval(read_file(file_name, directory), current_binding)
|
54
|
+
end
|
55
|
+
|
56
|
+
# @!visibility private
|
57
|
+
# @param [ActiveRecord::Base] resource An ActiveRecord Model instance
|
58
|
+
# @param [Hash] override_hash Hash of attributes / values to override from
|
59
|
+
# @return [Hash] Hash of attributes from provided resource
|
60
|
+
def clone_resource resource, override_hash = nil
|
61
|
+
override_hash.nil? ? resource.dup.attributes.with_indifferent_access :
|
62
|
+
resource.dup.attributes.merge(
|
63
|
+
override_hash.with_indifferent_access).with_indifferent_access
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Datum
|
2
|
+
# @!visibility private
|
3
|
+
VERSION = "4.1.4"
|
4
|
+
end
|
5
|
+
|
6
|
+
|
7
|
+
## 0.8.1 - 0.9.2
|
8
|
+
## Original datum, proof-of-concept
|
9
|
+
##
|
10
|
+
## 4.0.0
|
11
|
+
## Full rewrite, updated with latest concepts and code -- Still in Testing
|
12
|
+
##
|
13
|
+
## 4.0.x
|
14
|
+
## Several small updates (readme, etc) -- Readme still needs work.
|
15
|
+
## 4.0.6
|
16
|
+
## Seems like the build process was dead on arrival? Trying a re-build .7
|
17
|
+
## 4.0.7
|
18
|
+
## Does not seem to have fixed the issue. :(
|
19
|
+
## 4.0.8
|
20
|
+
## Removing railtie reference
|
21
|
+
## 4.0.9
|
22
|
+
## Removing railtie and rake task
|
23
|
+
## 4.1.0
|
24
|
+
## Removing files.
|
data/lib/datum.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
|
2
|
+
require "datum/helpers"
|
3
|
+
require "datum/datum"
|
4
|
+
require "datum/container"
|
5
|
+
require "support/test"
|
6
|
+
require "support/scenario"
|
7
|
+
|
8
|
+
# Datum is a flexible data-driven test solution for Rails.
|
9
|
+
#
|
10
|
+
# Datum's primary features include defining data-driven tests via the
|
11
|
+
# data_test method and Scenarios, a load-on-demand mechanism for seeding the
|
12
|
+
# test database.
|
13
|
+
module Datum
|
14
|
+
@@all_containers, @@scenario_path, @@data_path, @@datum_path = nil
|
15
|
+
|
16
|
+
class << self
|
17
|
+
|
18
|
+
# @!attribute [r] path
|
19
|
+
# Fully qualified path for the root of datum directory
|
20
|
+
# @return [Pathname]
|
21
|
+
def path; @@datum_path ||= Rails.root.join('test', 'datum'); end
|
22
|
+
|
23
|
+
# @!attribute [r] data_path
|
24
|
+
# Fully qualified path for the datum/data directory
|
25
|
+
# @return [Pathname]
|
26
|
+
def data_path; @@data_path ||= ::Datum.path.join('data'); end
|
27
|
+
|
28
|
+
# @!attribute [r] scenario_path
|
29
|
+
# Fully qualified path for the datum/scenarios directory
|
30
|
+
# @return [Pathname]
|
31
|
+
def scenario_path; @@scenario_path ||= ::Datum.path.join('scenarios'); end
|
32
|
+
|
33
|
+
# @!attribute [r] containers
|
34
|
+
# Hash of all loaded Containers
|
35
|
+
# @return [Hash]
|
36
|
+
def containers; @@all_containers ||= {}; end
|
37
|
+
|
38
|
+
private
|
39
|
+
def add_container container, key
|
40
|
+
::Datum.containers[key] = container
|
41
|
+
::Datum.instance_variable_set(:"@current_container", container)
|
42
|
+
end
|
43
|
+
def current_container; @current_container; end;
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# @!visibility private
|
2
|
+
module Plan9 # Module for simple code reuse between various projects.
|
3
|
+
# @!visibility private
|
4
|
+
class ImprovedStruct # A few improvements to a Ruby Struct
|
5
|
+
#
|
6
|
+
# Re-organized slightly, this code is reused from 'ImmutableStruct'
|
7
|
+
# by Theo Hultberg. See https://github.com/iconara/immutable_struct
|
8
|
+
# Copyright notice mentioned in the LICENSE file.
|
9
|
+
#
|
10
|
+
# @!visibility private
|
11
|
+
def self.new(*attrs, &block)
|
12
|
+
init_new(Struct.new(*attrs, &block))
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
def self.init_new(struct)
|
17
|
+
optionalize_constructor!(struct)
|
18
|
+
extend_dup!(struct)
|
19
|
+
struct
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def self.optionalize_constructor!(struct)
|
24
|
+
struct.class_eval do
|
25
|
+
alias_method :struct_initialize, :initialize
|
26
|
+
|
27
|
+
def initialize(*attrs)
|
28
|
+
if is_hash_case?(*attrs)
|
29
|
+
struct_initialize(*members.map { |m| attrs.first[m.to_sym] })
|
30
|
+
else
|
31
|
+
struct_initialize(*attrs)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
protected
|
36
|
+
def is_hash_case?(*a)
|
37
|
+
# @return (bool) true if attrs are Hash, false otherwise
|
38
|
+
members.size > 1 && a && a.size == 1 && a.first.instance_of?(Hash)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.extend_dup!(struct)
|
44
|
+
struct.class_eval do
|
45
|
+
def dup(overrides={})
|
46
|
+
self.class.new(to_h.merge(overrides))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# @!visibility private
|
53
|
+
class ImmutableStruct < ImprovedStruct # A read-only Struct
|
54
|
+
# Re-organized slightly, this code is reused from 'ImmutableStruct'
|
55
|
+
# by Theo Hultberg. See https://github.com/iconara/immutable_struct
|
56
|
+
# Copyright notice mentioned in the LICENSE file.
|
57
|
+
|
58
|
+
protected
|
59
|
+
|
60
|
+
def self.init_new(struct)
|
61
|
+
make_immutable!(struct)
|
62
|
+
super(struct)
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def self.make_immutable!(struct)
|
68
|
+
# removes the member= method, to prevent write
|
69
|
+
struct.send(:undef_method, "[]=".to_sym)
|
70
|
+
struct.members.each do |member|
|
71
|
+
struct.send(:undef_method, "#{member}=".to_sym)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
|
2
|
+
# From a scenario file, imports an existing scenario's code into the current
|
3
|
+
# Binding context of the current scenario.
|
4
|
+
#
|
5
|
+
# @param [Symbol, String] scenario_name The name of a scenario file
|
6
|
+
# @return [void]
|
7
|
+
#
|
8
|
+
# @example Using __import
|
9
|
+
# # test/datum/scenarios/springfield_police_scenario.rb
|
10
|
+
# @clancy = Person.create(first_name: "Clancy", last_name: "Wiggum")
|
11
|
+
# @eddie = Person.create(first_name: "Eddie", last_name: "Police-Officer")
|
12
|
+
#
|
13
|
+
# # test/datum/scenarios/extended_simpsons_scenario.rb
|
14
|
+
# @homer = Person.create(first_name: "Homer", last_name: "Simpson")
|
15
|
+
# @marge = Person.create(__clone(@homer, {first_name: "Marge"}))
|
16
|
+
# @bart = Person.create(__clone(@homer, {first_name: "Marge"}))
|
17
|
+
#
|
18
|
+
# __import :springfield_police_scenario # will give us clancy, eddie
|
19
|
+
#
|
20
|
+
# # Using @eddie from imported :springfield_police_scenario to
|
21
|
+
# # define @lou's last_name
|
22
|
+
# @lou = Person.create first_name: "Lou", last_name: @eddie.last_name
|
23
|
+
def __import scenario_name
|
24
|
+
::Datum::Helpers.import_file scenario_name, ::Datum.scenario_path, binding
|
25
|
+
end
|
26
|
+
|
27
|
+
# From a scenario file, clones the attributes of an existing instance and
|
28
|
+
# overrides as specified.
|
29
|
+
#
|
30
|
+
# @param [ActiveRecord::Base] resource An ActiveRecord Model instance
|
31
|
+
# @param [Hash] override_hash Hash of attributes / values to override from
|
32
|
+
#
|
33
|
+
# @return [Hash] Hash of attributes from provided resource
|
34
|
+
#
|
35
|
+
# @example Using __clone
|
36
|
+
# # test/datum/scenarios/simpsons_scenario.rb
|
37
|
+
# # any code included in this file gets loaded when calling process_scenario
|
38
|
+
# @homer = Person.create(first_name: "Homer", last_name: "Simpson")
|
39
|
+
# @marge = Person.create(__clone(@homer, {first_name: "Marge"}))
|
40
|
+
def __clone resource, override_hash = nil
|
41
|
+
::Datum::Helpers.clone_resource resource, override_hash
|
42
|
+
end
|
data/lib/support/test.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
require "datum/helpers"
|
2
|
+
require "datum/container"
|
3
|
+
require "datum/datum"
|
4
|
+
|
5
|
+
# Adds the process_scenario method to ActiveSupport::TestCase and includes
|
6
|
+
# the Datum module
|
7
|
+
# @note Supports most extending test types (functional, integration, etc)
|
8
|
+
# @example Making a Scenario
|
9
|
+
# # test/datum/scenarios/simpsons_scenario.rb
|
10
|
+
# # any code included in this file gets loaded when calling process_scenario
|
11
|
+
# @homer = Person.create(first_name: "Homer", last_name: "Simpson")
|
12
|
+
# @marge = Person.create(__clone(@homer, {first_name: "Marge"}))
|
13
|
+
# @!method process_scenario(scenario_name)
|
14
|
+
# Imports a scenario file into the execution context of the current test
|
15
|
+
# @param [Symbol, String] scenario_name The name of a scenario file
|
16
|
+
# @return [void]
|
17
|
+
# @example Using process_scenario
|
18
|
+
# test "should verify basic scenario" do
|
19
|
+
# process_scenario :simpsons_scenario
|
20
|
+
# assert_not_nil @homer, "process_scenario did not define @homer"
|
21
|
+
# assert_not_nil @marge, "process_scenario did not define @marge"
|
22
|
+
# end
|
23
|
+
class ActiveSupport::TestCase
|
24
|
+
include Datum
|
25
|
+
|
26
|
+
def process_scenario scenario_name
|
27
|
+
__import(scenario_name)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Defines a test to work in conjuction with Datum struct extensions found in
|
32
|
+
# a file with the same name in the test/datum/data directory
|
33
|
+
#
|
34
|
+
# @param [String] name Name of the file in the datum/data directory
|
35
|
+
# @param [Block] block A block of Ruby code
|
36
|
+
#
|
37
|
+
# @return [void]
|
38
|
+
#
|
39
|
+
# @example Defining a data_test
|
40
|
+
# # test/datum/data/simple_person_data.rb
|
41
|
+
#
|
42
|
+
# # first, define a sub-class of Datum to use in your test
|
43
|
+
# PersonData = Datum.new(:first_name, :last_name, :name, :short_name)
|
44
|
+
#
|
45
|
+
# # next, use your sub-class to create datasets which will be accessible to
|
46
|
+
# # your data_test as @datum
|
47
|
+
# #
|
48
|
+
# # your data can be generated, etc... here, we're keeping it simple
|
49
|
+
# homer = PersonData.new("Homer", "Simpson", "Homer Simpson", "Homer S.")
|
50
|
+
# marge = PersonData.new("Marge", homer.last_name,
|
51
|
+
# "Marge #{homer.last_name}", "Marge S.")
|
52
|
+
#
|
53
|
+
# # test/datum/scenarios/simpsons_scenario.rb
|
54
|
+
#
|
55
|
+
# @homer = Person.create(first_name: "Homer", last_name: "Simpson")
|
56
|
+
# @marge = Person.create(__clone(@homer, {first_name: "Marge"}))
|
57
|
+
#
|
58
|
+
# # test/models/person_test.rb
|
59
|
+
# require 'test_helper'
|
60
|
+
# class PersonTest < ActiveSupport::TestCase
|
61
|
+
#
|
62
|
+
# # this data method will be called once for each Datum defined in
|
63
|
+
# # test/datum/data/simple_person_data.rb
|
64
|
+
# #
|
65
|
+
# # each time this method is called @datum will reference the current
|
66
|
+
# # dataset
|
67
|
+
# data_test "simple_person_data" do
|
68
|
+
# process_scenario :simpsons_scenario
|
69
|
+
# person = self.instance_variable_get("@#{@datum.first_name.downcase}")
|
70
|
+
# assert_equal @datum.first_name, person.first_name
|
71
|
+
# assert_equal @datum.last_name, person.last_name
|
72
|
+
# assert_equal @datum.name, person.name
|
73
|
+
# assert_equal @datum.short_name, person.short_name
|
74
|
+
# end
|
75
|
+
# end
|
76
|
+
def data_test name, &block
|
77
|
+
::Datum::Container.new(name, self)
|
78
|
+
self.send(:define_method, name, &block)
|
79
|
+
self.class_eval(::Datum::Helpers.read_file(name, ::Datum.data_path))
|
80
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: datum
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.1.
|
4
|
+
version: 4.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tyemill
|
@@ -14,14 +14,14 @@ dependencies:
|
|
14
14
|
name: rails
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: 4.1.7
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 4.1.7
|
27
27
|
- !ruby/object:Gem::Dependency
|
@@ -31,7 +31,7 @@ dependencies:
|
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '0'
|
34
|
-
type: :
|
34
|
+
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
@@ -44,10 +44,20 @@ email:
|
|
44
44
|
executables: []
|
45
45
|
extensions: []
|
46
46
|
extra_rdoc_files: []
|
47
|
-
files:
|
47
|
+
files:
|
48
|
+
- MIT-LICENSE
|
49
|
+
- README.md
|
50
|
+
- Rakefile
|
51
|
+
- lib/datum.rb
|
52
|
+
- lib/datum/container.rb
|
53
|
+
- lib/datum/datum.rb
|
54
|
+
- lib/datum/helpers.rb
|
55
|
+
- lib/datum/version.rb
|
56
|
+
- lib/plan9/structures.rb
|
57
|
+
- lib/support/scenario.rb
|
58
|
+
- lib/support/test.rb
|
48
59
|
homepage: https://github.com/tyemill/datum
|
49
|
-
licenses:
|
50
|
-
- MIT
|
60
|
+
licenses: []
|
51
61
|
metadata: {}
|
52
62
|
post_install_message:
|
53
63
|
rdoc_options: []
|