speq 0.2.1 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ca948589baaf3fd6d4385aec6da046cb0b96595ea1cc78af59787bf76112abe0
4
- data.tar.gz: 5879601e06032198afc0675b44130900b3fdbb895f207cab7c2cfc94b70638fb
3
+ metadata.gz: 2d4e8796522782ab6e954f384d21d6b6d1d5e6728c773c68e587873f4183173e
4
+ data.tar.gz: f0e4a46ce5889abf250761d4f990a43a61792c6bade4e30e88fd7e9c45869699
5
5
  SHA512:
6
- metadata.gz: 8f999677ac2de3f9e9760cfebe7180d1cb91215060a6f424f0c856f981ba9818715105b13dc1353d1b43d1fb9382b6c2189a0f31f64d50b21999b8ecb4bd4e23
7
- data.tar.gz: 1f674c4b2593df191a9d036b0f458cf1aee46f342066667f5df33504bd70e5cfb30858f3fdc208229f0a87741d36fd9b7785e5a05234bb2a0e4f85556c8ffb18
6
+ metadata.gz: 9a08b7857d12e9b57af750fb35db681884a7af03a6224424abf659ad2eb4fde5353c8dbb3b873ab6c9f0ed6ac90ae4732338150748def36ddda786ad1921845f
7
+ data.tar.gz: e4ac157417ac3cda5a872b1b13a39ece575d437b10ea8fbca320d71ab383b255ca66be76085ce07bc3f20054d796dacfe17ad9a855632b10b44e21fe67c26681
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- speq (0.2.1)
4
+ speq (0.3.0)
5
5
  colorize (~> 0.8.1)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -1,10 +1,27 @@
1
1
  # Speq
2
2
 
3
- ## A tiny library to build specs with fewer words
3
+ ## Build specs with fewer words
4
4
 
5
- Speq is TDD library for rapid prototyping in Ruby. It aims to work well anytime testing is desired but writing specs with existing tools may feel excessive.
5
+ Speq is a testing library for rapid prototyping in Ruby.
6
6
 
7
- Speq favors simplicity and minimalism, which may not always be compatible with rigorous testing. The existing TDD tools for Ruby are exceptional, and Speq is not a replacement.
7
+ Speq favors simplicity and minimalism.
8
+
9
+ ```ruby
10
+ is(Array.new).empty?
11
+ # Passed (1/1)
12
+ # [] is empty.
13
+ ```
14
+
15
+ But also values flexibility.
16
+
17
+ ```ruby
18
+ speq(Array(nil), 'an array created by calling Array(nil)').eq?([nil])
19
+
20
+ # Failed (1/1)
21
+ # An array created by calling Array(nil) is NOT equal to [nil]. ( [] != [nil] )
22
+ ```
23
+
24
+ Speq is ideal whenever it would feel excessive to use one of the existing frameworks, but it's not enough to just print & inspect outputs.
8
25
 
9
26
  ## Installation
10
27
 
@@ -22,45 +39,320 @@ Or install it yourself as:
22
39
 
23
40
  gem install speq
24
41
 
25
- ## Design
42
+ ## Design & Syntax
43
+
44
+ Speq is loosely based on the given-when-then or arrange-act-assert testing pattern. Speq's design choices are guided by the competing motivations of having tests be as short and simple as possible while maintaining the flexibility to be as descriptive as needed.
45
+
46
+ In contrast to similar tools, speqs are typically framed as _questions_ about a program's behavior rather than assertions about the desired behavior. The most basic test involves passing a string to the method `speq` followed by `pass?` and a block that evaluates strictly to true or false.
47
+
48
+ ```ruby
49
+ speq("Does Ruby properly calculate Euler's identity?").pass? do
50
+ e = Math::E
51
+ π = Math::PI
52
+ i = (1i)
53
+
54
+ e**(i*π) + 1 == 0
55
+ end
56
+
57
+ # Passed (1/1)
58
+ # Testing 'does Ruby properly calculate Euler's identity?' passes.
59
+ ```
60
+
61
+ The above works, but it's not much shorter than existing solutions. More importantly, it's missing vital information about what action was taken, what the outcome was, and what assertion led to the success.
62
+
63
+ Shown below is the basic two-part pattern for most speqs and the resulting report combining explicit and implicit descriptions:
64
+
65
+ ```ruby
66
+ speq(Math::E**((1i) * Math::PI ) + 1, 'e^iπ + 1').eq?(0)
67
+ # Passed (1/1)
68
+ # e^iπ + 1 is equal to 0. ( (0.0+0.0i) == 0 )
69
+ ```
70
+
71
+ ### Actions
72
+
73
+ Descriptions for simple unit tests are often closely tied to the source code, and as such, Speq tries to eliminate them wherever possible. To automatically generate descriptions, the action or subject of interest is supplied to be evaluated dynamically.
26
74
 
27
- Speq's design choices are influenced by the competing motivations of having tests be as short and simple as possible while maintaining the flexibility to be as descriptive as needed.
75
+ More specifically, we begin by setting up the program state and then explicitly supply the action being tested using the methods below. By breaking down the expression's method/message, arguments, and receiver, Speq can use the additional information to help generate an often sufficiently detailed description of the test. Actions are evaluated when they reach a [matcher](#matchers).
28
76
 
29
- Speq is loosely based on the given-when-then or arrange-act-assert testing pattern. Whereas one would typically need to make each step explicit, Speq tests focus on making it easy to simply assert the expected behavior with an implicit arrangement and action.
77
+ - message: `does(*:symbol` or `is(*:symbol)`, default: `:itself`
78
+ - arguments: `with(...)` or `of(...)`, default: `*[]`
79
+ - receiver: `on(object, description(optional))`, default: `Object`
30
80
 
31
- ## Syntax
81
+ ```ruby
82
+ is(:prime?).of(2).true?
83
+
84
+ on((1..4).to_a.reverse).does(:sort).eq?([1, 2, 3, 4])
85
+
86
+ # .with() accepts arguments with the exact same format as any other method
87
+ does(:map).with { |idx| idx * idx }.on(0..3).eq?([0, 1, 4, 9])
88
+
89
+ # Passed (3/3)
90
+ # prime? of 2 is true.
91
+ # sort on [4, 3, 2, 1] equals [1, 2, 3, 4].
92
+ # map with a block on 1..4 equals [0, 1, 4, 9].
93
+ ```
32
94
 
33
- Speq's syntax is still in flux and likely to change dramatically until an effective setup is established.
95
+ Chaining methods can be accomplished by providing multiple symbols. If an intermediate value needs to be tested or a method needs to be given arguments, this can be accomplished by using `.then(:next_method).with`. An example of this is shown below. An alternative, unconventional syntax for accomplishing the same can be found in the section on [syntactic sugar](#Sugar)
34
96
 
35
97
  ```ruby
36
- test_unit :sort, on: [3, 2, 1], eq: [1, 2, 3]
98
+ on(1..4)
99
+ .does(:to_a, :shuffle, :sort)
100
+ .eq?([1, 2, 3, 4])
101
+ .then(:sort).with { | a, b | b <=> a }
102
+ .eq?([4, 3, 2, 1])
103
+
104
+ # Passed (1/1)
105
+ # to_a on 1..4 then reverse (on [1, 2, 3, 4]) then sort (on [4, 3, 2, 1]) equals [1, 2, 3, 4].
106
+ ```
107
+
108
+ ### Matchers
109
+
110
+ All speq methods that end with a question mark are matchers.
111
+
112
+ The matcher `pass?` is the most general and simply expects to be given a code block that evaluates to true or false. The output from the action is passed along as the first piped variable.
113
+
114
+ ```ruby
115
+ does(:strip)
116
+ .on(' speq ')
117
+ .pass? { |str| str == 'speq' && !str.include?(' ') }
118
+
119
+ does(:rand)
120
+ .pass? { |val| val.is_a?(Float) }
121
+
122
+ does(:prime?)
123
+ .with(-1)
124
+ .raise?('Prime is not defined for negative numbers')
125
+
126
+ on(User)
127
+ .does(:new)
128
+ .with(id: 1, name: 'user name')
129
+ .have?(:id)
130
+ ```
131
+
132
+ #### Prefixes and Compound Matchers
133
+
134
+ Multiple matchers can follow an action. By default, all matchers must pass for the test to be considered successful. To run multiple tests on the same action, see the section on [test subjects](#subjects)
135
+
136
+ The logical inverse of any matcher can be achieved by prefixing the matcher with `not_`.
137
+
138
+ ```ruby
139
+ # Equivalent to the previous speq
140
+ does(:strip).on(' speq ').not_include?(' ').eq?('speq')
141
+ ```
142
+
143
+ For simple combinations, it's sufficient to prefix the second matcher with one of the following: `or_, nand_, nor_, xor_, xnor_`
144
+
145
+ ```ruby
146
+ arr = Array.new(10, rand.round)
147
+ speq(arr, '10 element array filled with zeros or ones').has?(length: 100).all?(0).or_all?(1)
148
+
149
+ # arr.length == 100 && (arr.all?(0) || arr.all?(1))
150
+ ```
151
+
152
+ Although it's best to avoid complex combinations of matchers, logical operators can be combined unambiguously if absolutely needed by chaining operators with prefix notation.
153
+
154
+ ```ruby
155
+ default = rand.round(1) <=> 0.5
156
+ length = default.zero? ? 0 : 100
157
+
158
+ arr = Array.new(length, default)
159
+ speq(arr, 'Either empty, all 1, or all -1').or?.empty?.xor?.include?(-1).include?(1)
160
+ ```
161
+
162
+ #### Generated & built-in matchers
163
+
164
+ If the object being tested responds to the method ending with `?`, then an appropriate matcher is automatically generated. As a result, existing methods such as `instance_of?`, `nil?`, or `empty?` can be used despite the fact that they are not in the list below.
165
+
166
+ ```ruby
167
+ pass?(*description, &block)
168
+ has?(*:message, **(message: val)) # alias: have?
169
+
170
+ eq?(obj) # result == obj
171
+ case?(obj) # obj === result
172
+
173
+ true?
174
+ false?
175
+ truthy?
176
+ falsy?
177
+
178
+ raise?('The following error message')
179
+ raise?(ErrorClass)
180
+
181
+ # Compound matchers
182
+ either?(*matcher) # matcher1 || matcher2 || ...
183
+ neither?(*matcher) # !matcher1 || !matcher2 || ...
184
+
185
+ ```
186
+
187
+ #### Matcher name precedence
188
+
189
+ Parsing the name of a matcher gives highest precedence to the object being tested. For example, consider the following test:
190
+
191
+ ```ruby
192
+ class Display
193
+ attr_reader :type
194
+
195
+ def initialize(type = :case)
196
+ @type = type.to_sym
197
+ end
198
+
199
+ def case?
200
+ @type == :case
201
+ end
202
+ end
203
+
204
+ window_display = Display.new('window')
205
+
206
+ speq(window_display).not_case? # Will use Display#case
207
+ on(window_display).is(:type).case?(Symbol) # Will use Speq#case?
208
+ ```
209
+
210
+ To resolve `not_case?`, speq will do the following:
211
+
212
+ 1. Checks for the prefix `speq_`, reserved in case of name conflicts.
213
+ 2. Generates a matcher if the object responds to `not_case?`.
214
+ 3. Generates a matcher if the object responds to `case?` and applies prefixes.
215
+ 4. Uses the built-in `case?` and applies prefixes.
216
+
217
+ ### Additional Functionality
218
+
219
+ #### Object Descriptions
220
+
221
+ It may be advantageous to describe an object in advance such that, from there on, the report will automatically include the object description anytime that object is encountered.
222
+
223
+ ```ruby
224
+ MyClass = Class.new
225
+
226
+ call(MyClass, 'my class, an instance of the class Class')
227
+ speq(MyClass).instance_of?(Class)
228
+
229
+ # call tracks both object identity and object equality
230
+ my_arr = []
231
+ call(my_arr, 'a specific empty array')
232
+ call([], 'an empty array')
233
+
234
+ speq(my_arr).eq?([])
235
+
236
+ # Passed (2/2)
237
+ # 'my class, an instance of the class Class' is an instance of the class Class.
238
+ # 'my specific empty array' equals 'an empty array'
239
+ ```
240
+
241
+ #### Fakes
242
+
243
+ Fakes are Speq's simple implementation of a test double. Most closely resembling a stub, fakes provide canned or computed responses, allowing for additional test isolation for objects that rely on objects not within the scope of testing.
244
+
245
+ ```ruby
246
+ fake_bank = fake(
247
+ balance: 5000,
248
+ print_balance: '$50.00',
249
+ withdraw: proc {|amount| [50, amount].min }
250
+ )
251
+ ```
252
+
253
+ #### Subjects
254
+
255
+ Speq has the distinct advantage of making it particularly easy to run many similar tests on the same subject.
256
+ Opening a block on an action that has not been clo
257
+
258
+ ```ruby
259
+ not_prime = [0, 1, 4, 6, 8, 9]
260
+ prime = [2, 3, 5, 7]
261
+
262
+ #
263
+ does(:prime?) do
264
+ with[not_prime].false?
265
+ with[prime].true?
266
+ end
267
+
268
+ does(:my_sort) do
269
+ small_array = [3, 2, 1]
270
+ large_array = Array.new(10**6) {rand}
271
+
272
+ on(small_array).eq?(small_array.sort)
273
+ on(large_array).eq?(large_array.sort)
274
+ end
275
+ ```
276
+
277
+ #### Sugar
278
+
279
+ ```ruby
280
+ # Action-Question Chains
281
+ on('3 2 1')
282
+ << :split
283
+ << has?(length: 3)
284
+ << :map << with(&:to_i)
285
+ << :reduce << with(&:+)
286
+ << eq?(6)
287
+
288
+ # Can start with the usual syntax and switch when convenient
289
+ on(1..4).does(:to_a, :shuffle, :sort).eq?([1, 2, 3, 4])
290
+ << :sort << with { | a, b | b <=> a } << eq?([4, 3, 2, 1])
291
+
292
+ # Broadcasting
293
+ def add(a, b)
294
+ a + b
295
+ end
296
+
297
+ a = [1, 2, 3]
298
+ b = [4, 5, 6]
299
+
300
+ does(:add).with[a, b].eq?([5, 7, 9])
37
301
  ```
38
302
 
39
303
  ## Usage
40
304
 
41
- ### Wherever you want
305
+ ### CLI: Speq Files
306
+
307
+ Speq offers a simple CLI that lets you run tests written in dedicated spec files. This is the preferred way to run speq consistent with the examples shown above.
308
+
309
+ Executing `speq` (or `bundle exec speq` if using bundle) will recursively search the working directory and run all files that end with `_speq.rb`.
310
+
311
+ To run individual files, specify a list of speq file prefixes. For example, to run tests that are within the files `example_speq.rb` and `sample_speq.rb`, simply execute:
312
+
313
+ speq example sample
314
+
315
+ Speq files only `require 'speq'` by default, so any external code being tested will still need to be required.
316
+
317
+ ### Anywhere: Speq.test
318
+
319
+ Speq can be used anywhere as long as it is required and all tests are written within a block passed to Speq.test.
42
320
 
43
321
  ```ruby
44
322
  require 'speq'
45
323
 
46
- Speq.test do
47
- # Tests here can access local variables.
48
- # Test results are printed to $stdout.
49
- # The method returns true if all tests pass, false otherwise.
324
+ Speq.test(self) do | context |
325
+ # You can pass the surrounding context if needed.
326
+ # Calling 'report' prints the tests seen so far to $stdout.
327
+
328
+ # By default, Speq.test Returns the instance of Speq::Test created here.
329
+ # Using the return keyword, you can choose to return the following instead:
330
+ # 'return score': returns the proportion of tests passed as a Rational number
331
+ # 'return pass?': returns true or false for whether all the tests passed
50
332
  end
51
333
  ```
52
334
 
53
- ### With dedicated spec files
335
+ Speq can also be included to be used in a more direct manner. Here is an example of using Speq within another class for added functionality.
54
336
 
55
- Speq also offers a simple CLI that lets you run tests written in dedicated spec files.
337
+ ```ruby
338
+ require 'speq'
56
339
 
57
- Executing `bundle exec speq` will recursively search the working directory and run all files that end with `_speq.rb`.
340
+ class Exam
341
+ include Speq
58
342
 
59
- To run individual files, specify a list of speq file prefixes. For example, to run tests that are within the files `example_speq.rb` and `sample_speq.rb`, simply execute:
343
+ def initialize(questions)
344
+ @questions = questions
345
+ end
60
346
 
61
- bundle exec speq example sample
347
+ def grade
348
+ @questions.each do | question |
349
+ speq(question.response, question.to_s).eq?(question.answer)
350
+ end
62
351
 
63
- Speq files are not expected to require `speq`, but they should require other files that may be needed to run the specs.
352
+ score
353
+ end
354
+ end
355
+ ```
64
356
 
65
357
  ## Contributing
66
358
 
@@ -4,5 +4,6 @@ require 'bundler/setup'
4
4
  require 'pry'
5
5
 
6
6
  require 'speq'
7
+ include Speq
7
8
 
8
9
  Pry.start
data/exe/speq CHANGED
@@ -1,4 +1,3 @@
1
1
  #!/usr/bin/env ruby
2
-
3
- require 'speq'
4
- Speq::CLI.new(ARGV).run
2
+ require 'speq/cli'
3
+ Speq::CLI.new(ARGV)
@@ -4,26 +4,41 @@ require 'speq/matcher'
4
4
  require 'speq/report'
5
5
  require 'speq/unit'
6
6
  require 'speq/fake'
7
+ require 'speq/action'
7
8
  require 'speq/cli'
8
9
 
9
10
  module Speq
11
+ @tests = [Test.new]
12
+ @descriptions = { Object => nil }
13
+
14
+ def self.test(&block)
15
+ self << Test.new
16
+ module_exec(&block)
17
+ end
18
+
19
+ def self.<<(test)
20
+ @tests << test
21
+ end
22
+
23
+ def self.descriptions
24
+ @descriptions
25
+ end
26
+
10
27
  module_function
11
28
 
12
- def method_missing(method, *args)
13
- if Matcher.respond_to?(method)
14
- Matcher.send(method, *args)
29
+ def method_missing(method_name, *args, &block)
30
+ if Action.instance_methods.include?(method_name)
31
+ Action.new(@tests.last).send(method_name, *args, &block)
15
32
  else
16
33
  super
17
34
  end
18
35
  end
19
36
 
20
- def unit_test(message, **kwargs)
21
- matcher_method = kwargs.keys.find { |key| Matcher.respond_to?(key) }
22
- matcher = send(matcher_method, kwargs[matcher_method])
23
-
24
- unit = Unit.new(message, kwargs[:on], kwargs[:with], matcher)
25
- Report.new([unit]).print_report
37
+ def report
38
+ Report.new(@tests).print_report
39
+ end
26
40
 
27
- unit.passed?
41
+ def fake(**mapping)
42
+ Fake.new(mapping)
28
43
  end
29
44
  end
@@ -0,0 +1,97 @@
1
+ require 'speq'
2
+
3
+ module Speq
4
+ class Action
5
+ attr_accessor :test_group, :message_queue, :receiver, :arguments_queue
6
+
7
+ def initialize(
8
+ test_group,
9
+ receiver = Object,
10
+ messages = [:itself],
11
+ arguments = [{ args: [], block: nil }]
12
+ )
13
+ @test_group = test_group
14
+
15
+ @message_queue = messages
16
+ @arguments_queue = arguments
17
+ @receiver = receiver
18
+ end
19
+
20
+ def method_missing(method, *args, &block)
21
+ if method.to_s.end_with?('?')
22
+ matcher = Matcher.send(method, *args, &block)
23
+ @test_group << Unit.new(clone, matcher)
24
+ else
25
+ super
26
+ end
27
+ end
28
+
29
+ def clone
30
+ self.class.new(
31
+ test_group,
32
+ receiver,
33
+ message_queue.clone,
34
+ arguments_queue.clone
35
+ )
36
+ end
37
+
38
+ def result
39
+ until @message_queue.empty?
40
+ args = arguments_queue.shift
41
+ message = message_queue.shift
42
+
43
+ @receiver = receiver.send(message, *args[:args], &args[:block])
44
+ end
45
+
46
+ @receiver
47
+ end
48
+
49
+ def on(receiver, description = nil)
50
+ @receiver = receiver
51
+ Speq.descriptions[receiver] = description || receiver
52
+ self
53
+ end
54
+
55
+ def does(*messages)
56
+ messages.each do |message|
57
+ message_queue.push(message)
58
+ arguments_queue.push(args: [], block: nil)
59
+ end
60
+
61
+ self
62
+ end
63
+
64
+ def with(*args, &block)
65
+ arguments_queue.last[:args] = args
66
+ arguments_queue.last[:block] = block
67
+
68
+ self
69
+ end
70
+
71
+ def format_arguments
72
+ arguments = arguments_queue.last
73
+ argument_description = ''
74
+
75
+ unless arguments[:args].empty?
76
+ argument_description << "with '#{arguments[:args].join(', ')}'"
77
+ end
78
+
79
+ argument_description << ' and a block' if arguments[:block]
80
+
81
+ argument_description
82
+ end
83
+
84
+ def format_receiver
85
+ Speq.descriptions[receiver] ? "on '#{Speq.descriptions[receiver]}'" : ''
86
+ end
87
+
88
+ def to_s
89
+ [message_queue.last, format_arguments, format_receiver]
90
+ .reject(&:empty?)
91
+ .join(' ')
92
+ end
93
+
94
+ alias is does
95
+ alias of with
96
+ end
97
+ end
@@ -1,14 +1,13 @@
1
+ require 'speq'
1
2
  require 'find'
2
3
  require 'colorize'
3
4
 
4
5
  # Provides a CLI for running Speq
5
6
  module Speq
6
7
  class CLI
7
- attr_accessor :tests
8
-
9
8
  def initialize(cli_args)
10
9
  @files = find_files(cli_args)
11
- @tests = []
10
+ run
12
11
  end
13
12
 
14
13
  def find_files(file_prefixes)
@@ -19,8 +18,7 @@ module Speq
19
18
 
20
19
  def run
21
20
  @files.each { |file| Speq.module_eval(File.read(file), file) }
22
- @tests.each(&:report)
23
- @tests.all?(&:passed?)
21
+ Speq.report
24
22
  end
25
23
  end
26
24
  end
@@ -1,4 +1,4 @@
1
- # The Fake class inludes a variety of doubles, mocks, stubs, etc.
1
+ # The Fake class is a lightweight, general-purpose test double
2
2
  module Speq
3
3
  class Fake
4
4
  def initialize(mapping)
@@ -14,5 +14,9 @@ module Speq
14
14
  -> { return_value }
15
15
  end
16
16
  end
17
+
18
+ def to_s
19
+ description = send(:class) == self.class ? 'a fake' : "a fake#{send(:class)}"
20
+ end
17
21
  end
18
22
  end
@@ -2,31 +2,74 @@
2
2
  # respond to match, returning true for an expected return value of a unit test
3
3
  module Speq
4
4
  class Matcher
5
- def initialize(expected)
6
- @expected = expected
5
+ def initialize(expectation, phrase)
6
+ @expectation = expectation
7
+ @phrase = phrase
7
8
  end
8
9
 
9
- def match(actual)
10
- @expected[actual]
10
+ def match?(actual)
11
+ @actual = actual
12
+ @expectation[actual]
11
13
  end
12
14
 
13
- def self.truthy
15
+ def to_s
16
+ @phrase[@actual]
17
+ end
18
+
19
+ def self.method_missing(method_name, *args, &block)
20
+ if method_name.to_s.end_with?('?')
21
+ dynamic_matcher(method_name, *args, &block)
22
+
23
+ else
24
+ super
25
+ end
26
+ end
27
+
28
+ def self.dynamic_matcher(method_name, *args, &block)
29
+ Matcher.new(
30
+ ->(object) { object.send(method_name, *args, &block) },
31
+ proc { "passes: #{method_name}" }
32
+ )
33
+ end
34
+
35
+ def self.pass?(&prc)
36
+ Matcher.new(prc)
37
+ end
38
+
39
+ def self.true?
40
+ Matcher.eq?(true)
41
+ end
42
+
43
+ def self.truthy?
14
44
  Matcher.new(->(actual_value) { actual_value ? true : false })
15
45
  end
16
46
 
17
- def self.falsey
47
+ def self.falsy?
18
48
  Matcher.new(->(actual_value) { actual_value ? false : true })
19
49
  end
20
50
 
21
- def self.eq(expected_value)
22
- Matcher.new(->(actual_value) { expected_value.eql?(actual_value) })
51
+ def self.eq?(expected_value)
52
+ Matcher.new(
53
+ ->(actual_value) { expected_value.eql?(actual_value) },
54
+ ->(actual_value) { "equals #{actual_value}" }
55
+ )
23
56
  end
24
57
 
25
- def self.is(expected_object)
26
- Matcher.new(->(actual_object) { expected_object.equal?(actual_object) })
58
+ def self.have?(*symbols, **key_value_pairs)
59
+ new(
60
+ lambda do |object|
61
+ symbols.each { |symbol| return false unless object.respond_to?(symbol) }
62
+ key_value_pairs.each { |key, value| return false unless object.send(key) == value }
63
+ end,
64
+ proc do
65
+ properties = symbols.empty? ? '' : symbols.join(' ') + ', '
66
+ values = key_value_pairs.map { |key, value| "#{key}: #{value}" }.join(' ')
67
+ "has #{properties}#{values}"
68
+ end
69
+ )
27
70
  end
28
71
 
29
- def self.raises(expected_except)
72
+ def self.raise?(expected_except)
30
73
  case expected_except
31
74
  when Class
32
75
  raise_class(expected_except)
@@ -1,31 +1,24 @@
1
1
  # The Report class produces and prints a report per test group
2
2
  module Speq
3
3
  class Report
4
- def initialize(units)
5
- @units = units
4
+ def initialize(tests)
5
+ @units = tests.flat_map(&:units)
6
+ @passed = []
7
+ @failed = []
6
8
  end
7
9
 
8
10
  def print_report
9
- report_string = ''
10
-
11
11
  @units.each do |unit|
12
12
  if unit.passed?
13
- outcome = 'PASSED: '
14
- color = :green
13
+ @passed << unit.to_s.colorize(:green)
15
14
  else
16
- outcome = 'FAILED: '
17
- color = :red
15
+ @failed << unit.to_s.colorize(:red)
18
16
  end
19
-
20
- method = "calling '#{unit.message}' "
21
- arguments = unit.arguments ? "with arguments: '#{unit.arguments}'" : ''
22
- receiver = unit.receiver == Object ? '' : "on: #{unit.receiver} "
23
-
24
- report_string <<
25
- [outcome, method, arguments, receiver, "\n"].join.colorize(color)
26
17
  end
27
18
 
28
- puts report_string
19
+ puts "Passed (#{@passed.length}/#{@units.length})" unless @passed.empty?
20
+ puts @passed
21
+ puts "Failed (#{@failed.length}/#{@units.length})" unless @failed.empty?
29
22
  end
30
23
  end
31
24
  end
@@ -1,6 +1,10 @@
1
- # The Test class is a simple implementation of unit test block
1
+ require 'speq/action'
2
+
3
+ # A
2
4
  module Speq
3
5
  class Test
6
+ attr_reader :units
7
+
4
8
  def initialize
5
9
  @units = []
6
10
  end
@@ -8,5 +12,10 @@ module Speq
8
12
  def passed?
9
13
  @units.all?(&:passed?)
10
14
  end
15
+
16
+ def <<(unit)
17
+ @units << unit
18
+ self
19
+ end
11
20
  end
12
21
  end
@@ -1,20 +1,24 @@
1
+ require 'speq/action'
2
+
1
3
  # The Unit class is responsible for running a test and storing the result
2
4
  module Speq
3
5
  class Unit
4
- attr_reader :message, :receiver, :arguments
6
+ attr_reader :result, :action, :matcher
5
7
 
6
- def initialize(message, receiver = Object, arguments = [], matcher)
7
- @result = matcher.match(receiver.send(message, *arguments))
8
+ def initialize(action, matcher)
9
+ @action = action
10
+ @matcher = matcher
11
+ @result = matcher.match?(action.clone.result)
8
12
  rescue StandardError => exception
9
- @result = matcher.match(exception)
10
- ensure
11
- @message = message
12
- @receiver = receiver
13
- @arguments = arguments
13
+ @result = matcher.match?(exception)
14
14
  end
15
15
 
16
16
  def passed?
17
17
  @result
18
18
  end
19
+
20
+ def to_s
21
+ "#{action} #{matcher}"
22
+ end
19
23
  end
20
24
  end
@@ -1,3 +1,3 @@
1
1
  module Speq
2
- VERSION = '0.2.1'.freeze
2
+ VERSION = '0.3.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: speq
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - zaniar moradian
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-10-31 00:00:00.000000000 Z
11
+ date: 2018-11-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -84,6 +84,7 @@ files:
84
84
  - bin/setup
85
85
  - exe/speq
86
86
  - lib/speq.rb
87
+ - lib/speq/action.rb
87
88
  - lib/speq/cli.rb
88
89
  - lib/speq/fake.rb
89
90
  - lib/speq/matcher.rb