rspec-varys 0.1.3 → 0.1.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8add44ff8c8ddece4c482995cb65d1798a102b60
4
- data.tar.gz: 9ca6bc274c7969f3f4f22d53e937f1ee85404ffd
3
+ metadata.gz: 4ae6ca67859c48c958453b1533bed0aee4b87c2d
4
+ data.tar.gz: 90161136f92496d5d0b8da34258660c4b913330e
5
5
  SHA512:
6
- metadata.gz: 4a0a48e1e970fdb09f7be35c3116d5b421b9a3ddfe3e4069e4aec1aa21e2aaece36b2ca234273122286f8db2435a90b46df162bc436dfd29b24ee5fc46b897a8
7
- data.tar.gz: 95d2d16154f099c218adfe0c6f5c75792411c0b5adfdb9ef4e11179b1b8472b8c42e2a4c95f7bdfc740317bceb06eb2928959cbd580c95f4afc9f1ffdb32ef31
6
+ metadata.gz: 2bbe1d40a3668a000f8df492394ee97c2f307f6d537d439d615b6e197417080c70b32d05a7722ecbfc5a112909ab4d35b00d38609aeefb54c57bc44f414cff39
7
+ data.tar.gz: 5b474c92524ddec12b5047682731e228b81b80e0793d95a1c15b81b8f22cbd8388568db918ed150cb65a957c79f8424cc8f7a183275d5e30f7ccf96b0b6de466
@@ -0,0 +1,12 @@
1
+ language: ruby
2
+ bundler_args:
3
+ rvm:
4
+ - 2.1.2
5
+ deploy:
6
+ provider: rubygems
7
+ api_key:
8
+ secure: WLQzsGZ6aPzqsyS1Mu4YCZjer4ZXRFQo5j1XPkTdR/bYmip2GBQAoVIUihlIXrcTSDKRk4VVZ62GkVSOkTA41vAVvD69ARGNIgwSDzLfoVp7RWEIi7peUBdYhzOoQM8eFnOKa3UeZbEqjolsTjtQfMQISNCeL9TCmdRcyLa/avk=
9
+ gem: rspec-varys
10
+ on:
11
+ tags: true
12
+ repo: ritchiey/rspec-varys
data/README.md CHANGED
@@ -1,10 +1,58 @@
1
1
  [![Gem Version](https://badge.fury.io/rb/rspec-varys.svg)](http://badge.fury.io/rb/rspec-varys)
2
2
 
3
+ [![Code Climate](https://codeclimate.com/github/ritchiey/rspec-varys/badges/gpa.svg)](https://codeclimate.com/github/ritchiey/rspec-varys)
4
+
3
5
  # Rspec::Varys
4
6
 
5
- Generate RSpec specs from intelligence gathered from doubles and spies.
7
+ Generate RSpec specs from intelligence gathered from doubles and spies. This is an experiment to see if a top-down TDD work-flow can be improved by partially automating the creation of lower level specs.
8
+
9
+ When you define a test-double in your spec:
10
+
11
+ ```ruby
12
+ describe ExampleClass, "#max_margin_for" do
13
+ it "returns the maximum margin for the supplied text" do
14
+ allow(subject).to receive(:margin_for).and_return(2, 4)
15
+ expect(subject.max_margin_for(" unindent line\n indented line\n")).to eq(4)
16
+ end
17
+ end
18
+ ```
19
+
20
+ And your code calls it:
21
+
22
+ ```ruby
23
+ class ExampleClass
24
+ def max_margin_for(text)
25
+ text.split("\n").map {|line| margin_for(line)}.max
26
+ end
27
+ end
28
+ ```
29
+
30
+
31
+ Varys will generate the corresponding specs so you can verify the validity of your test-doubles:
32
+
33
+ ```ruby
34
+ # Generated by Varys:
35
+
36
+ describe ExampleClass, "#margin_for" do
37
+ it "returns something" do
38
+ confirm(subject).can receive(:margin_for).with(" unindent line").and_return(2)
39
+ skip "remove this line once implemented"
40
+ expect(subject.margin_for(" unindent line")).to eq(2)
41
+ end
42
+ end
43
+
44
+ describe ExampleClass, "#margin_for" do
45
+ it "returns something" do
46
+ confirm(subject).can receive(:margin_for).with(" indented line").and_return(4)
47
+ skip "remove this line once implemented"
48
+ expect(subject.margin_for(" indented line")).to eq(4)
49
+ end
50
+ end
51
+ ```
6
52
 
7
- This is an experiment to see if a top-down TDD work-flow can be improved by partially automating the creation of lower level specs.
53
+ ## Limitations
54
+
55
+ Varys is very early release software and it has many limitations. If you find one, please check the Github issues and add it if it's not already listed.
8
56
 
9
57
  ## Installation
10
58
 
@@ -20,9 +68,35 @@ Or install it yourself as:
20
68
 
21
69
  $ gem install rspec-varys
22
70
 
71
+ ## Configuration
72
+
73
+ Add these lines to your `spec/spec_helper.rb`:
74
+
75
+ ```ruby
76
+ require "rspec/varys"
77
+ require "rspec/varys/rspec_generator"
78
+
79
+ RSpec.configure do |config|
80
+ config.include RSpec::Varys::DSL
81
+
82
+ config.before(:suite) do
83
+ RSpec::Varys.reset
84
+ end
85
+
86
+ config.after(:suite) do
87
+ # Required: create varys.yml
88
+ RSpec::Varys.print_report
89
+
90
+ # Optional: create generated_specs.yml from varys.yml
91
+ RSpec::Varys::RSpecGenerator.run
92
+ end
93
+ end
94
+ ```
95
+
23
96
  ## Usage
24
97
 
25
- See the [Cucumber features](https://relishapp.com/spechero/rspec-varys/docs) for examples of intended usage.
98
+ See the [Cucumber features](https://relishapp.com/spechero/rspec-varys/docs) for examples of intended usage or watch [this screencast](https://vimeo.com/119725799) for a simple tutorial.
99
+
26
100
 
27
101
  ## Contributing
28
102
 
@@ -24,6 +24,132 @@ Feature: Generating an RSpec Spec from an RSpec Expectation
24
24
  """
25
25
 
26
26
 
27
+ Scenario: For a test double
28
+ Given a file named "top_level_spec.rb" with:
29
+ """ruby
30
+ require_relative 'spec_helper'
31
+ require_relative 'person'
32
+
33
+ describe "First day at work" do
34
+
35
+ it "starts with an introduction" do
36
+ name = double('Name', full_name: "Dick Jones")
37
+ boss = Person.new(name)
38
+ expect(boss.welcome).to eq "Welcome to OCP, I'm Dick Jones"
39
+ end
40
+
41
+ end
42
+ """
43
+ And a file named "person.rb" with:
44
+ """ruby
45
+ class Person
46
+
47
+ def initialize(name)
48
+ @name = name
49
+ end
50
+
51
+ def welcome
52
+ "Welcome to OCP, I'm #{@name.full_name}"
53
+ end
54
+
55
+ end
56
+ """
57
+
58
+ When I run `rspec top_level_spec.rb`
59
+ Then it should pass with:
60
+ """ruby
61
+ Specs have been generated based on mocks you aren't currently testing.
62
+ """
63
+ And the file "varys.yaml" should contain:
64
+ """yaml
65
+ ---
66
+ :untested_stubs:
67
+ - :class_name: Name
68
+ :type: instance
69
+ :method: full_name
70
+ :returns: Dick Jones
71
+ """
72
+
73
+ And the file "generated_specs.rb" should contain:
74
+ """ruby
75
+ describe Name, "#full_name" do
76
+
77
+ it "returns something" do
78
+ confirm(subject).can receive(:full_name).and_return("Dick Jones")
79
+ skip "remove this line once implemented"
80
+ expect(subject.full_name).to eq("Dick Jones")
81
+ end
82
+
83
+ end
84
+
85
+
86
+ """
87
+ Scenario: For a class method
88
+ Given a file named "top_level_spec.rb" with:
89
+ """ruby
90
+ require_relative 'spec_helper'
91
+ require_relative 'person'
92
+
93
+ describe "First day at work" do
94
+
95
+ it "starts with an introduction" do
96
+ boss = Person.new('Dick', 'Jones')
97
+ allow(Person).to receive(:full_name).with("Dick", "Jones").and_return("Dick Jones")
98
+ expect(boss.welcome).to eq "Welcome to OCP, I'm Dick Jones"
99
+ end
100
+
101
+ end
102
+ """
103
+ And a file named "person.rb" with:
104
+ """ruby
105
+ class Person
106
+
107
+ def initialize(firstname, lastname)
108
+ @firstname = firstname
109
+ @lastname = lastname
110
+ end
111
+
112
+ def welcome
113
+ "Welcome to OCP, I'm #{Person.full_name(@firstname, @lastname)}"
114
+ end
115
+
116
+ end
117
+ """
118
+
119
+ When I run `rspec top_level_spec.rb`
120
+ Then it should pass with:
121
+ """ruby
122
+ Specs have been generated based on mocks you aren't currently testing.
123
+ """
124
+ And the file "varys.yaml" should contain:
125
+ """yaml
126
+ ---
127
+ :untested_stubs:
128
+ - :class_name: Person
129
+ :type: class
130
+ :method: full_name
131
+ :returns: Dick Jones
132
+ :arguments:
133
+ - Dick
134
+ - Jones
135
+ """
136
+
137
+ And the file "generated_specs.rb" should contain:
138
+ """ruby
139
+ describe Person, ".full_name" do
140
+
141
+ it "returns something" do
142
+ confirm(described_class).can receive(:full_name).with("Dick", "Jones").and_return("Dick Jones")
143
+ skip "remove this line once implemented"
144
+ expect(described_class.full_name("Dick", "Jones")).to eq("Dick Jones")
145
+ end
146
+
147
+ end
148
+
149
+
150
+ """
151
+
152
+
27
153
  Scenario: For a single unmatched expectation
28
154
  Given a file named "top_level_spec.rb" with:
29
155
  """ruby
@@ -64,6 +190,7 @@ Feature: Generating an RSpec Spec from an RSpec Expectation
64
190
  ---
65
191
  :untested_stubs:
66
192
  - :class_name: Person
193
+ :type: instance
67
194
  :method: full_name
68
195
  :returns: Dick Jones
69
196
  """
@@ -125,9 +252,11 @@ Feature: Generating an RSpec Spec from an RSpec Expectation
125
252
  ---
126
253
  :untested_stubs:
127
254
  - :class_name: Person
255
+ :type: instance
128
256
  :method: title
129
257
  :returns: Vice President
130
258
  - :class_name: Person
259
+ :type: instance
131
260
  :method: full_name
132
261
  :returns: Dick Jones
133
262
  """
@@ -216,6 +345,7 @@ Feature: Generating an RSpec Spec from an RSpec Expectation
216
345
  ---
217
346
  :untested_stubs:
218
347
  - :class_name: Person
348
+ :type: instance
219
349
  :method: title
220
350
  :returns: Vice President
221
351
  """
@@ -272,6 +402,7 @@ Feature: Generating an RSpec Spec from an RSpec Expectation
272
402
  ---
273
403
  :untested_stubs:
274
404
  - :class_name: Person
405
+ :type: instance
275
406
  :method: join_names
276
407
  :returns: Dick Jones
277
408
  :arguments:
@@ -293,3 +424,7 @@ Feature: Generating an RSpec Spec from an RSpec Expectation
293
424
 
294
425
 
295
426
  """
427
+
428
+
429
+
430
+
@@ -83,10 +83,6 @@ module RSpec::Varys
83
83
  @recorded_messages
84
84
  end
85
85
 
86
- def self.generated_specs
87
- @generated_specs ||= generate_specs
88
- end
89
-
90
86
  def self.reset
91
87
  @recorded_messages = []
92
88
  @generated_specs = nil
@@ -95,57 +91,26 @@ module RSpec::Varys
95
91
 
96
92
  def self.record(object, message, args, block, return_value)
97
93
  @recorded_messages << {
98
- class_name: object.class.name,
94
+ class_name: class_name_for(object),
95
+ type: type_for(object),
99
96
  message: message,
100
97
  args: args,
101
98
  return_value: return_value
102
99
  }
103
100
  end
104
101
 
105
- def self.generate_specs
106
- {}.tap do |generated_specs|
107
- unconfirmed_messages.each do |s|
108
- generated_specs[s[:class_name]] ||= []
109
- generated_specs[s[:class_name]] << generate_spec(s)
110
- end
111
- end
112
- end
113
-
114
-
115
- def self.generate_spec(s)
116
- <<-GENERATED
117
- describe "##{s[:message]}" do
118
-
119
- it "returns the correct value" do
120
- pending
121
- confirm(subject).can receive(:#{s[:message]})#{with_parameters(s)}.and_return(#{serialize s[:return_value]})
122
- expect(subject.#{s[:message]}#{parameters(s)}).to eq(#{serialize s[:return_value]})
123
- end
124
-
125
- end
126
-
127
- GENERATED
102
+ def self.type_for(object)
103
+ object.kind_of?(Class) ? 'class' : 'instance'
128
104
  end
129
105
 
130
- def self.with_parameters(spec)
131
- if spec[:args].length > 0
132
- %Q[.with(#{params spec[:args]})]
106
+ def self.class_name_for(object)
107
+ if object.kind_of? RSpec::Mocks::Double
108
+ 'Name'
133
109
  else
134
- ""
110
+ object.kind_of?(Class) ? object.name : object.class.name
135
111
  end
136
112
  end
137
113
 
138
- def self.params(args)
139
- args.map{|a| serialize(a)}.join(", ")
140
- end
141
-
142
- def self.parameters(spec)
143
- if spec[:args].length > 0
144
- %Q[(#{params spec[:args]})]
145
- else
146
- ""
147
- end
148
- end
149
114
 
150
115
  def self.print_report
151
116
  open_yaml_file do |yaml_file|
@@ -159,6 +124,7 @@ module RSpec::Varys
159
124
  untested_stubs: unconfirmed_messages.map do |call|
160
125
  {
161
126
  class_name: call[:class_name],
127
+ type: call[:type],
162
128
  method: call[:message].to_s,
163
129
  returns: call[:return_value]
164
130
  }.merge(arguments_if_any(call))
@@ -181,25 +147,5 @@ module RSpec::Varys
181
147
  recorded_messages - confirmed_messages
182
148
  end
183
149
 
184
- def self.underscore(camel_cased_word)
185
- camel_cased_word.downcase
186
- end
187
-
188
- # Attempt to recreate the source-code to represent this argument in the setup
189
- # for our generated spec.
190
- def self.serialize(arg)
191
- if %w(Array Hash Float Fixnum String NilClass TrueClass FalseClass).include? arg.class.name
192
- arg.pretty_inspect.chop
193
- else
194
- guess_constructor arg
195
- end
196
- end
197
-
198
- # Don't recognise the type so we don't know how to recreate it
199
- # in source code. So we'll take a guess at what might work and
200
- # let the user fix it up if necessary.
201
- def self.guess_constructor(arg)
202
- "#{arg.class.name}.new(#{serialize(arg.to_s)})"
203
- end
204
150
  end
205
151
 
@@ -13,12 +13,12 @@ class RSpec::Varys::RSpecGenerator
13
13
  def self.process_specs(specs, file)
14
14
  specs[:untested_stubs].each do |spec|
15
15
  file.puts <<-EOF
16
- describe #{spec[:class_name]}, "##{spec[:method]}" do
16
+ describe #{spec[:class_name]}, "#{class_method?(spec) ? '.' : '#'}#{spec[:method]}" do
17
17
 
18
18
  it "returns something" do
19
- confirm(subject).can receive(:#{spec[:method]})#{with_args_if_any(spec)}.and_return(#{serialize spec[:returns]})
19
+ confirm(#{sut(spec)}).can receive(:#{spec[:method]})#{with_args_if_any(spec)}.and_return(#{serialize spec[:returns]})
20
20
  skip "remove this line once implemented"
21
- expect(subject.#{spec[:method]}#{args_if_any(spec)}).to eq(#{serialize spec[:returns]})
21
+ expect(#{sut(spec)}.#{spec[:method]}#{args_if_any(spec)}).to eq(#{serialize spec[:returns]})
22
22
  end
23
23
 
24
24
  end
@@ -29,13 +29,12 @@ end
29
29
  end
30
30
 
31
31
  def self.with_args_if_any(call)
32
- args = call[:arguments]
33
- (args && args.length > 0) ? ".with(#{args.map{|a| serialize a}.join ', '})" : ""
32
+ args_if_any(call, ".with")
34
33
  end
35
34
 
36
- def self.args_if_any(call)
35
+ def self.args_if_any(call, prefix="")
37
36
  args = call[:arguments]
38
- (args && args.length > 0) ? "(#{args.map{|a| serialize a}.join ', '})" : ""
37
+ (args && args.length > 0) ? "#{prefix}(#{args.map{|a| serialize a}.join ', '})" : ""
39
38
  end
40
39
 
41
40
  # Attempt to recreate the source-code to represent this argument in the setup
@@ -55,4 +54,12 @@ end
55
54
  "#{arg.class.name}.new(#{serialize(arg.to_s)})"
56
55
  end
57
56
 
57
+ def self.class_method?(call)
58
+ call[:type] == 'class'
59
+ end
60
+
61
+ def self.sut(call)
62
+ class_method?(call) ? "described_class" : "subject"
63
+ end
64
+
58
65
  end
@@ -1,5 +1,5 @@
1
1
  module Rspec
2
2
  module Varys
3
- VERSION = "0.1.3"
3
+ VERSION = "0.1.4"
4
4
  end
5
5
  end
@@ -1,6 +1,7 @@
1
1
  require 'rspec'
2
2
  require 'rspec/varys'
3
3
 
4
+
4
5
  class Person
5
6
 
6
7
  def initialize(first_name, last_name)
@@ -72,6 +73,7 @@ describe RSpec::Varys do
72
73
 
73
74
  expect(described_class.recorded_messages).to match_array([{
74
75
  class_name: 'Object',
76
+ type: 'instance',
75
77
  message: :a_message,
76
78
  args: [:a_parameter],
77
79
  return_value: 42
@@ -80,27 +82,13 @@ describe RSpec::Varys do
80
82
 
81
83
 
82
84
  context "given the test-suite calls a mocked method" do
83
- context "with no paramters" do
84
-
85
- let(:expected_spec) do
86
- <<GENERATED
87
- describe "#full_name" do
88
-
89
- it "returns the correct value" do
90
- pending
91
- confirm(subject).can receive(:full_name).and_return("Dick Jones")
92
- expect(subject.full_name).to eq("Dick Jones")
93
- end
94
-
95
- end
96
-
97
- GENERATED
98
- end
85
+ context "with no parameters" do
99
86
 
100
87
  let(:recognised_specs) {
101
88
  [
102
89
  {
103
90
  class_name: 'Person',
91
+ type: 'instance',
104
92
  message: :full_name,
105
93
  args: [],
106
94
  return_value: "Dick Jones"
@@ -116,38 +104,19 @@ GENERATED
116
104
  expect(dick.welcome).to eq "Welcome to OCP, I'm Dick Jones"
117
105
  end
118
106
 
119
- it "can generate required specs" do
120
- # did it correctly record the method called
107
+ it "record the methods called" do
121
108
  expect(described_class.recorded_messages).to match_array(recognised_specs)
122
-
123
- # did it generate an in-memory version of the specs?
124
- expect(described_class.generated_specs).to eq('Person' => [ expected_spec ])
125
-
126
109
  end
127
110
 
128
111
  end
129
112
 
130
113
  context "with parameters" do
131
114
 
132
- let(:expected_spec) do
133
- <<GENERATED
134
- describe "#join_names" do
135
-
136
- it "returns the correct value" do
137
- pending
138
- confirm(subject).can receive(:join_names).with("Dick", "Jones").and_return("Dick Jones")
139
- expect(subject.join_names("Dick", "Jones")).to eq("Dick Jones")
140
- end
141
-
142
- end
143
-
144
- GENERATED
145
- end
146
-
147
115
  let(:recognised_specs) {
148
116
  [
149
117
  {
150
118
  class_name: 'Person',
119
+ type: 'instance',
151
120
  message: :join_names,
152
121
  args: ["Dick", "Jones"],
153
122
  return_value: "Dick Jones"
@@ -163,13 +132,8 @@ GENERATED
163
132
  expect(dick.welcome).to eq "Welcome to OCP, I'm Dick Jones"
164
133
  end
165
134
 
166
- it "can generate required specs" do
167
- # did it correctly record the method called
135
+ it "records that the method was called" do
168
136
  expect(described_class.recorded_messages).to match_array(recognised_specs)
169
-
170
- # did it generate an in-memory version of the specs?
171
- expect(described_class.generated_specs).to eq('Person' => [ expected_spec ])
172
-
173
137
  end
174
138
 
175
139
  end
@@ -1,3 +1,4 @@
1
1
 
2
2
  Dir.glob(::File.expand_path('../support/*.rb', __FILE__)).each { |f| require_relative f }
3
3
  Dir.glob(::File.expand_path('../../lib/*.rb', __FILE__)).each { |f| require_relative f }
4
+
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspec-varys
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ritchie Young
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-02-10 00:00:00.000000000 Z
11
+ date: 2015-02-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -115,6 +115,7 @@ extra_rdoc_files: []
115
115
  files:
116
116
  - ".gitignore"
117
117
  - ".ruby-version"
118
+ - ".travis.yml"
118
119
  - Gemfile
119
120
  - Gemfile.lock
120
121
  - LICENSE.txt