rspec-varys 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
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