ddt 0.1.0

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.
data/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ Copyright (c) 2012 Dan Swain
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.
23
+
24
+
data/README.md ADDED
@@ -0,0 +1,208 @@
1
+ # DDT
2
+
3
+ Data driven testing for Ruby with RSpec. DDT kills bugs.
4
+
5
+ DDT is a plugin for RSpec that makes data-driven testing easy. It
6
+ allows you to define a [YAML](http://www.yaml.org/) file for a
7
+ specific method that includes test input and expected outputs. See
8
+ below for an example.
9
+
10
+ ## License
11
+
12
+ DDT is released under the
13
+ [MIT license](http://en.wikipedia.org/wiki/MIT_License).
14
+ See the LICENSE file.
15
+
16
+ ## Requirements
17
+
18
+ DDT requires RSpec because, well, it's an RSpec plugin. In the
19
+ future, I may make DDT more general and let the RSpec part be
20
+ optional.
21
+
22
+ DDT should work on any Ruby. It's only been tested on Ruby
23
+ 1.9.3 so far, though.
24
+
25
+ ## Installation
26
+
27
+ If your system is set up to allow it, you can just do
28
+
29
+ gem install ddt
30
+
31
+ Or, if you prefer a more hands-on approach or want to hack at the source:
32
+
33
+ git clone git://github.com/simplifi/ddt.git
34
+ cd ddt
35
+ rake install
36
+
37
+ If you are working on a system where you need to `sudo gem install`
38
+ you can do
39
+
40
+ rake gem
41
+ sudo gem install ddt
42
+
43
+ As always, you can `rake -T` to find out what other rake tasks we have
44
+ provided.
45
+
46
+ ## Basic Usage
47
+
48
+ Right now, `DDT::TruthTesting` that is the main workhorse of DDT.
49
+ `DDT::TruthTesting` adds some methods to RSpec to enable a truth
50
+ testing. `DDT::TruthTest` automates the process of creating an
51
+ object, calling a method on it with some specified input, and checking
52
+ the state of the object afterwords against what is expected, i.e., the
53
+ "truth". `DDT::TruthTest` defines a class called `Truth` for the
54
+ class under test; the `Truth` class encapsulates the input and
55
+ expected output.
56
+
57
+ This example and the others below are taken from
58
+ spec/truth\_test/truth\_test\_spec.rb
59
+
60
+ First, include ddt in your test suite. Probably this just means
61
+ adding `require 'ddt'` to your spec\_helper.rb
62
+
63
+ Suppose we have a classed called `Cat` that has a state called
64
+ `disposition` and a method called `pet`:
65
+
66
+ class Cat
67
+ attr_accessor :disposition
68
+
69
+ def initialize
70
+ @disposition = "mrow."
71
+ end
72
+
73
+ # cats are temperamental. my cat does not
74
+ # like to be petted on the belly, but she
75
+ # won't let you know until afterwords.
76
+ def pet how
77
+ predisposition = @disposition
78
+ if how =~ /belly/
79
+ @disposition = "*hiss*"
80
+ end
81
+ predisposition
82
+ end
83
+ end
84
+
85
+ We can write a truth testing spec file for `Cat#pet` like this:
86
+
87
+ describe Cat do
88
+ truth_test :pet
89
+ end
90
+
91
+ When we run rspec, DDT will look for a file called
92
+ `truths/cat/pet_truths.yaml` containing YAML test cases. Here's an
93
+ example:
94
+
95
+ ---
96
+ input: "on the head"
97
+ output: "mrow."
98
+ disposition: "mrow."
99
+ ---
100
+ input: "on the belly"
101
+ output: "mrow."
102
+ disposition: "*hiss*"
103
+
104
+ For each of these test cases, `DDT::TruthTest` will create a new `Cat`
105
+ instance and call the `pet` method with "input" as the argument. It
106
+ then does an RSpec example that looks something like
107
+
108
+ cat.pet("on the head").should == "mrow."
109
+
110
+ as well as
111
+
112
+ cat.disposition.should == "mrow."
113
+
114
+ ## Mapping test data
115
+
116
+ `DDT::TruthTest` lets us map test data using
117
+ `Object::define_truth`, which takes as arguments an instance of the
118
+ object under test and a hash corresponding to the YAML test case. As
119
+ an example, consider the `Dog` class here:
120
+
121
+ class Dog
122
+ attr_accessor :name, :weight, :age
123
+
124
+ def parse! str
125
+ d = str.split(",")
126
+ @name = d[0]
127
+ @weight = d[1].to_f
128
+ @age = d[2].to_i
129
+ self
130
+ end
131
+ end
132
+
133
+ # example:
134
+ rover = Dog.new.parse! "rover", 31.5, 5
135
+ rover.name # => "rover"
136
+ rover.weight # => 31.5
137
+ rover.age # => 5
138
+
139
+ In our test data, the weight needs to be converted to a float and the
140
+ age needs to be converted to an int, so we'll manually add some
141
+ conversions to our truth class:
142
+
143
+ Dog::define_truth do |dog, data|
144
+ # make age an integer
145
+ dog.age = data["age"].to_i
146
+
147
+ # make weight a float
148
+ dog.weight = data["weight"].to_f
149
+ end
150
+
151
+ We can then define truths for `Dog#parse!` in
152
+ `truths/dog/parse\!_truths.yaml` (note the `\!`):
153
+
154
+ ---
155
+ input: "Fido, 30.0, 3"
156
+ name: "Fido"
157
+ weight: 30.0
158
+ age: 3
159
+
160
+ And test in RSpec:
161
+
162
+ describe Dog do
163
+ truth_test :parse!
164
+ end
165
+
166
+ Note, in the `Cat` example above, `Cat::define_truth` is called
167
+ automatically by `truth_test`.
168
+
169
+ ## Specifying the test instance
170
+
171
+ The `Truth` class also provides a method called `Truth#tester` that
172
+ supplies the instance to test. By default, `Object#new` is called
173
+ with no arguments to supply the instance. This method can be overridden to
174
+ specify other arguments:
175
+
176
+ class Wolf
177
+ attr_accessor :call
178
+ def initialize call
179
+ @call = call
180
+ end
181
+ end
182
+
183
+ # we have to first create the truth class
184
+ Wolf::define_truth
185
+
186
+ # then we can monkey patch the tester method
187
+ class Wolf::Truth
188
+ def tester
189
+ self.class.tester || Wolf.new("AOOOO")
190
+ end
191
+ end
192
+
193
+ It's also possible to use RSpec's stubbing to do this:
194
+
195
+ Wolf::Truth.should_receive(:tester).and_return(Wolf.new "draw blood")
196
+
197
+ ## Contributing
198
+
199
+ The usual github process applies here:
200
+
201
+ 1. Fork it
202
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
203
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
204
+ 4. Push to the branch (`git push origin my-new-feature`)
205
+ 5. Create new Pull Request
206
+
207
+ You can also contribute to the author's ego by letting him know that
208
+ you find String Eater useful ;)
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ require 'rake/clean'
2
+
3
+ project_name = "ddt"
4
+
5
+ desc "Run rspec spec/ (compile if needed)"
6
+ task :test do
7
+ sh "rspec spec/"
8
+ end
9
+
10
+ desc "Create gem"
11
+ task :gem => "#{project_name}.gemspec" do
12
+ sh "gem build #{project_name}.gemspec"
13
+ end
14
+
15
+ desc "Install using 'gem install'"
16
+ task :install => :gem do
17
+ sh "gem install #{project_name}"
18
+ end
data/lib/ddt.rb ADDED
@@ -0,0 +1,17 @@
1
+ # defines DDT::TruthTest
2
+ require 'truth_test/truth_test'
3
+
4
+ module DDT
5
+ autoload :VERSION, 'version/version'
6
+
7
+ # copied from ActiveSupport
8
+ def underscore string
9
+ string.gsub(/::/, '/').
10
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
11
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
12
+ tr("-", "_").
13
+ downcase
14
+ end
15
+
16
+ module_function :underscore
17
+ end
@@ -0,0 +1,151 @@
1
+ require 'rspec/core'
2
+ require 'yaml'
3
+
4
+ class Object
5
+ def self.define_truth &block
6
+ DDT::TruthTest::define_truth_for self, &block
7
+ end
8
+ end
9
+
10
+ # @private
11
+ module DDT
12
+ module TruthTest
13
+
14
+ def define_truth_for the_klass, &block
15
+ klass = Class.new
16
+
17
+ klass.class_eval do
18
+ # defined so we can override it with should_receive
19
+ def self.tester
20
+ nil
21
+ end
22
+
23
+ attr_accessor :input, :output
24
+ end
25
+
26
+ if block_given?
27
+ pblock = block
28
+ else
29
+ pblock = nil
30
+ end
31
+
32
+ # this is a bit of trickery to get at the private method
33
+ # :define_method and also allow access to the_klass
34
+ klass.send(:define_method, :initialize) do |data={}|
35
+ data.each_pair do |attr, value|
36
+ instance_variable_set "@#{attr}", value
37
+ self.class.send(:attr_accessor, attr.to_sym)
38
+ end
39
+
40
+ instance_variable_set "@input", data["input"]
41
+ (instance_variable_set "@output", data["output"]) if data["output"]
42
+
43
+ if pblock
44
+ pblock.call(self, data)
45
+ end
46
+ end
47
+
48
+ klass.send(:define_method, :tester) do
49
+ self.class.send(:tester) || the_klass.new
50
+ end
51
+
52
+ the_klass.const_set "Truth", klass
53
+
54
+ end
55
+
56
+ module_function :define_truth_for
57
+
58
+ class RSpec::Core::ExampleGroup
59
+
60
+ # Declare a truth test for a method of the class being described.
61
+ # If the class did not have define_truth called, it will be
62
+ # called automatically with no arguments.
63
+ #
64
+ # Example
65
+ # # truths/foo/do_stuff_truths.yaml
66
+ # ---
67
+ # input: blah
68
+ # bar: baz blah
69
+ # ---
70
+ # input: bam
71
+ # bar: baz bam
72
+ #
73
+ # # foo.rb
74
+ # class Foo
75
+ # attr_accessor :bar
76
+ #
77
+ # def do_stuff input
78
+ # @bar = "baz #{input}"
79
+ # end
80
+ # end
81
+ #
82
+ # # foo_spec.rb
83
+ # Foo.define_truth
84
+ #
85
+ # describe Foo do
86
+ # truth_test :do_stuff
87
+ # end
88
+ #
89
+ # opts can either be an options hash or a filename in which to find the
90
+ # truth data. If opts is a hash and it includes :data as a key, the
91
+ # value of opts :data is a string to be parsed as YAML. If opts
92
+ # includes the :truth_file, then that file is used for truth (same
93
+ # as using a string instead of a hash).
94
+ #
95
+ # If no input is specified (i.e., if opts is omitted), then truth_test will
96
+ # look for a yaml file called ./truths/class_name/method_truths.yaml
97
+ # (class_name in snake case a la Rails).
98
+ def self.truth_test method, opts={}
99
+ unless described_class.const_defined?("Truth")
100
+ described_class.define_truth
101
+ end
102
+
103
+ if (opts.is_a? Hash) && opts[:data]
104
+ data = opts[:data]
105
+ else
106
+ filename = opts if opts.is_a? String
107
+ filename ||= (opts[:truth_file] || "./truths/#{DDT::underscore(described_class.name)}/#{method}_truths.yaml")
108
+ data = File.open(filename, "rb")
109
+ end
110
+
111
+ run_truth_test method, data
112
+ ensure
113
+ data.close if data.is_a? File
114
+ end
115
+
116
+ # @private
117
+ def self.run_truth_test method, data
118
+ truth_count = -1
119
+ YAML::load_documents(data).each do |datum|
120
+ truth_count += 1
121
+ truth = described_class::Truth.new datum
122
+
123
+ # don't use the value because we might want nil input
124
+ unless datum.has_key?("input")
125
+ puts "Skipping truth datum #{truth_count} with no input..."
126
+ next
127
+ end
128
+
129
+ if truth.output
130
+ it "should respond to '#{method}(#{truth.input})' with '#{truth.output}'" do
131
+ truth.tester.send(method, truth.input).should == truth.output
132
+ end
133
+ end
134
+
135
+ # already tested these
136
+ datum.delete("input")
137
+ datum.delete("output")
138
+
139
+ datum.each_key do |attr|
140
+ it "when the input is '#{truth.input}', calling '#{attr}' should return '#{truth.send(attr)}'" do
141
+ obj = truth.tester
142
+ obj.send(method, truth.input)
143
+ obj.send(attr).should == truth.send(attr)
144
+ end
145
+ end
146
+ end
147
+ end
148
+
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,9 @@
1
+ module DDT
2
+ module VERSION
3
+ MAJOR = 0
4
+ MINOR = 1
5
+ PATCH = 0
6
+ PRE = nil
7
+ STRING = [MAJOR, MINOR, PATCH, PRE].compact.join('.')
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ $LOAD_PATH.unshift('lib/')
2
+
3
+ require 'ddt'
@@ -0,0 +1,121 @@
1
+ require 'spec_helper'
2
+
3
+ Dog_attrs = [:name, :weight, :age]
4
+
5
+ # A class to test with
6
+ class Dog
7
+ attr_accessor *Dog_attrs
8
+
9
+ def parse! str
10
+ d = str.split(",")
11
+ @name = d[0]
12
+ @weight = d[1].to_f
13
+ @age = d[2].to_i
14
+ self
15
+ end
16
+ end
17
+
18
+ Dog::define_truth do |dog, data|
19
+ # make age an integer
20
+ dog.age = data["age"].to_i
21
+
22
+ # make weight a float
23
+ dog.weight = data["weight"].to_f
24
+ end
25
+
26
+ # we'll automatically generate a truth class for this
27
+ class Cat
28
+ attr_accessor :disposition
29
+
30
+ def initialize
31
+ @disposition = "mrow."
32
+ end
33
+
34
+ # cats are temperamental. my cat does not
35
+ # like to be petted on the belly, but she
36
+ # won't let you know until afterwords.
37
+ def pet how
38
+ predisposition = @disposition
39
+ if how =~ /belly/
40
+ @disposition = "*hiss*"
41
+ end
42
+ predisposition
43
+ end
44
+ end
45
+
46
+ # a class that we'll define a specific tester for
47
+ class Wolf
48
+ attr_accessor :call
49
+ def initialize call
50
+ @call = call
51
+ end
52
+ end
53
+
54
+ Wolf::define_truth
55
+
56
+ class Wolf::Truth
57
+ def tester
58
+ self.class.tester || Wolf.new("AOOOO")
59
+ end
60
+ end
61
+
62
+ describe DDT::TruthTest do
63
+ it "should create a Truth class under Dog" do
64
+ Dog::Truth.should respond_to(:new)
65
+ end
66
+
67
+ it "should create an input attr_accessor" do
68
+ Dog::Truth.new.should respond_to(:input)
69
+ end
70
+
71
+ it "should create an output attr_accessor" do
72
+ Dog::Truth.new.should respond_to(:output)
73
+ end
74
+
75
+ it "should create a tester method" do
76
+ Dog::Truth.new.should respond_to(:tester)
77
+ end
78
+
79
+ it "should respond to tester with an instance of Dog" do
80
+ Dog::Truth.new.tester.class.should == Dog
81
+ end
82
+
83
+ it "should allow us to set the tester method" do
84
+ Wolf::Truth.new.tester.call.should == "AOOOO"
85
+ end
86
+
87
+ it "should allow us to override the tester method by intercepting Truth::tester" do
88
+ Wolf::Truth.should_receive(:tester).and_return(Wolf.new "draw blood")
89
+ Wolf::Truth.new.tester.call.should == "draw blood"
90
+ end
91
+ end
92
+
93
+ # this way we know that a truth was automatically defined
94
+ describe Cat do
95
+ truth_test :pet
96
+ end
97
+
98
+ # tests for truth_test
99
+ describe Dog do
100
+ Group = self
101
+
102
+ it "should not raise an error if we try to add a truth test" do
103
+ Group.should_receive(:run_truth_test)
104
+ Group.truth_test :parse!
105
+ end
106
+
107
+ it "should raise an error if the truth file does not exist" do
108
+ lambda do
109
+ Group.truth_test :parse!, "nonexistent.yaml"
110
+ end.should raise_error
111
+ end
112
+
113
+ it "should allow us to specify YAML directly" do
114
+ yaml = "input: foo"
115
+ Group.should_receive(:run_truth_test).with(:parse!, yaml)
116
+ Group.truth_test :parse!, :data => yaml
117
+ end
118
+
119
+ # test that we actually can run the test
120
+ truth_test :parse!
121
+ end
@@ -0,0 +1,8 @@
1
+ ---
2
+ input: "on the head"
3
+ output: "mrow."
4
+ disposition: "mrow."
5
+ ---
6
+ input: "on the belly"
7
+ output: "mrow."
8
+ disposition: "*hiss*"
@@ -0,0 +1,5 @@
1
+ ---
2
+ input: "Fido, 30.0, 3"
3
+ name: "Fido"
4
+ weight: 30.0
5
+ age: 3
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ddt
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Dan Swain
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-08-27 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: DDT automates data driven testing. Test data is read from a YAML file.
15
+ email:
16
+ - dan@simpli.fi
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - lib/ddt.rb
22
+ - lib/truth_test/truth_test.rb
23
+ - lib/version/version.rb
24
+ - spec/spec_helper.rb
25
+ - spec/truth_test/truth_test_spec.rb
26
+ - truths/cat/pet_truths.yaml
27
+ - truths/dog/parse!_truths.yaml
28
+ - LICENSE
29
+ - Rakefile
30
+ - README.md
31
+ homepage: http://github.com/simplifi/ddt
32
+ licenses: []
33
+ post_install_message:
34
+ rdoc_options: []
35
+ require_paths:
36
+ - lib
37
+ required_ruby_version: !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ! '>='
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ required_rubygems_version: !ruby/object:Gem::Requirement
44
+ none: false
45
+ requirements:
46
+ - - ! '>='
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ requirements: []
50
+ rubyforge_project:
51
+ rubygems_version: 1.8.24
52
+ signing_key:
53
+ specification_version: 3
54
+ summary: Data driven testing for Ruby with RSpec. DDT kills bugs.
55
+ test_files:
56
+ - spec/spec_helper.rb
57
+ - spec/truth_test/truth_test_spec.rb
58
+ - truths/cat/pet_truths.yaml
59
+ - truths/dog/parse!_truths.yaml