r_spec 0.3.2 → 1.0.0.beta4

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
  SHA256:
3
- metadata.gz: df8a4849032bcc19682a2f22d851d60cf5ee447822f9f72763eeb8ae56d7f142
4
- data.tar.gz: 2ecfff4ecfc927109e347ec312da8d29c1d0c62718e212ed2262be661b1d5122
3
+ metadata.gz: 0d94ad6d7ba01dc04c4bcbe727543524f6a3d83cf69549a34453c1eefc5282e9
4
+ data.tar.gz: 61b3ebe1299e69d7ef1f2c180325e85e1bda05e2347176ae507fc7430cf311ca
5
5
  SHA512:
6
- metadata.gz: eba24c64920a278f78c5baf29ac41027558a5b0b7e4dfb4ee25e04d7a8920a697fd094c8c829024f5b925a6f9c5485f6ddb3da6411a2c14d683f3299e4828b60
7
- data.tar.gz: 928a480b07ff99162267d5937a0c7be23d35ad6731e1572c9a85c4e7ad0e924c3950c75c03e658574d446bc10a55efdd7e9f47b5336417e97801254cd5fc7d5e
6
+ metadata.gz: 5d44ce99f58c17711be4b3f76cdfaf3c9923d031aa9a7d214db50b990825673b9bb485ce095195594fbeb45977763dc5a0be31770f089cd17dcb1e3ef1d6885b
7
+ data.tar.gz: ca64a27f7cf4032b6b073b9fc2eb05aa37654c02a58289c41094e9afa000deccc3886d03d2043642a02e38cf10b6cc0fbd775e7ef9bc1c29cb592b813dcf5d04
data/LICENSE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2015-2019 Cyril Kato
3
+ Copyright (c) 2015-2021 Cyril Kato
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,131 +1,239 @@
1
- # R Spec
1
+ # RSpec clone
2
2
 
3
- [![Build Status](https://travis-ci.org/cyril/r_spec.svg?branch=master)][travis]
4
- [![Code Climate](https://codeclimate.com/github/cyril/r_spec/badges/gpa.svg)][codeclimate]
5
- [![Dependency Status](https://gemnasium.com/cyril/r_spec.svg)][gemnasium]
6
- [![Gem Version](https://badge.fury.io/rb/r_spec.svg)][gem]
7
- [![Inline docs](http://inch-ci.org/github/cyril/r_spec.svg?branch=master)][inchpages]
8
- [![Documentation](http://img.shields.io/:yard-docs-38c800.svg)][rubydoc]
3
+ A minimalist __[RSpec](https://github.com/rspec/rspec) clone__ with all the essentials.
9
4
 
10
- > A small [Rspec](https://github.com/rspec/rspec) clone based on [Fix specing framework](https://github.com/fixrb/fix).
5
+ ![What did you RSpec?](https://github.com/cyril/r_spec.rb/raw/main/img/what-did-you-rspec.svg)
11
6
 
12
- ***
7
+ ## Status
13
8
 
14
- :warning: Important:
9
+ [![Gem Version](https://badge.fury.io/rb/r_spec.svg)](https://badge.fury.io/rb/r_spec)
10
+ [![Build Status](https://travis-ci.org/cyril/r_spec.rb.svg?branch=main)](https://travis-ci.org/cyril/r_spec.rb)
11
+ [![Inline Docs](https://inch-ci.org/github/cyril/r_spec.rb.svg)](https://inch-ci.org/github/cyril/r_spec.rb)
12
+ [![Documentation](https://img.shields.io/:yard-docs-38c800.svg)](https://rubydoc.info/gems/r_spec/frames)
15
13
 
16
- To avoid confusion in the community, please note that the gem of this project is **not [rspec](https://rubygems.org/gems/rspec)**, it is **[r_spec](https://rubygems.org/gems/r_spec)** (meaning _Ruby Spec_).
14
+ ## Goal
17
15
 
18
- This project is totally independent of [rspec.info](http://rspec.info/).
16
+ This clone attempts to provide most of RSpec's DSL to express expected outcomes of a code example without magic power.
19
17
 
20
- Also, while both gems define an `RSpec` module, **r_spec** (which follows [the gem naming convention](http://guides.rubygems.org/name-your-gem/#use-underscores-for-multiple-words)) is still quite different than **rspec** due to its [Ruby Fix](http://fixrb.org/) dependency.
18
+ ## Some differences
21
19
 
22
- ***
20
+ * Less features and an implementation with much less code complexity.
21
+ * Spec files can also be executed directly with the `ruby` executable.
22
+ * There is no option to activate monkey-patching.
23
+ * It does not rely on hacks such as `at_exit` hook to trigger the tests.
24
+ * Built-in matchers do not trust _actual_ and do not send it messages.
25
+ * The `subject` must be explicitly defined, otherwise it is not implemented.
26
+ * Expectations cannot be added inside a `before` block.
27
+ * [Arbitrary helper methods](https://relishapp.com/rspec/rspec-core/v/3-10/docs/helper-methods/arbitrary-helper-methods) are not exposed to examples.
28
+ * The `let` method defines a helper method rather than a memoized helper method.
29
+ * The one-liner `is_expected` syntax also works with block expectations.
23
30
 
24
- ## Contact
31
+ ## Important ⚠️
25
32
 
26
- * Home page: https://github.com/cyril/r_spec
27
- * Bugs/issues: https://github.com/cyril/r_spec/issues
33
+ To avoid confusion in the community, please note that:
28
34
 
29
- ## Rubies
35
+ - the gem of this project is **not [`rspec`](https://rubygems.org/gems/rspec)**,
36
+ it is **[`r_spec`](https://rubygems.org/gems/r_spec)**;
37
+ - this project is totally independent of [rspec.info](https://rspec.info/).
30
38
 
31
- * [MRI](https://www.ruby-lang.org/)
32
- * [Rubinius](http://rubini.us/)
33
- * [JRuby](http://jruby.org/)
39
+ ### Note
34
40
 
35
- ## Terminal sessions
41
+ Following [RubyGems naming conventions](https://guides.rubygems.org/name-your-gem/#use-underscores-for-multiple-words), the module name for this project is `RSpec`.
36
42
 
37
- A comparison between the behavior of a small fix-based script (which became the `r_spec` gem) and `rspec`.
43
+ ## Installation
38
44
 
39
- As a result, with `r_spec` the build is passing, while with rspec it is failing. Despite two separate contexts, `rspec` was not able to evaluate some code in isolation to prevent side effects.
45
+ Add this line to your application's Gemfile:
40
46
 
41
- [![What did you RSpec?](https://asciinema.org/a/29070.png)](https://asciinema.org/a/29070)
47
+ ```ruby
48
+ gem "r_spec", ">= 1.0.0.beta4"
49
+ ```
42
50
 
43
- ***
51
+ And then execute:
44
52
 
45
- Unbelievable but true, sometimes "💩" is equal to 42.
53
+ ```sh
54
+ bundle
55
+ ```
46
56
 
47
- Although fun, this _feature_ can be fixed pretty easily...
57
+ Or install it yourself as:
48
58
 
49
- [![Fix RSpec!](https://asciinema.org/a/29172.png)](https://asciinema.org/a/29172)
59
+ ```sh
60
+ gem install r_spec --pre
61
+ ```
50
62
 
51
- ## Installation
63
+ ## Overview
52
64
 
53
- Add this line to your application's Gemfile:
65
+ __RSpec clone__ provides a structure for writing executable examples of how your code should behave.
66
+
67
+ Inspired by [RSpec](https://rspec.info/), it includes a domain specific language (DSL) that allows you to write examples in a way similar to plain english.
68
+
69
+ A basic spec looks something like this:
70
+
71
+ [![Super DRY example](https://asciinema.org/a/418672.svg)](https://asciinema.org/a/418672?autoplay=1)
72
+
73
+ ## Usage
74
+
75
+ ### Anatomy of a spec file
76
+
77
+ To use the `RSpec` module and its DSL, you need to add `require "r_spec"` to your spec files.
78
+ Many projects use a custom spec helper which organizes these includes.
79
+
80
+ Concrete test cases are defined in `it` blocks.
81
+ An optional descriptive string states it's purpose and a block contains the main logic performing the test.
82
+
83
+ An `it` block contains an example that should invoke the code to be tested and define what is expected of it.
84
+ Each example can contain multiple expectations, but it should test only one specific behaviour.
85
+
86
+ To express an expectation, wrap an object or block in `expect`, call `to` or `not_to` and pass it a matcher object.
87
+
88
+ If the expectation is met, code execution continues.
89
+ Otherwise the example has _failed_ and other code will not be executed.
90
+
91
+ Test cases that have been defined or outlined but are not yet expected to work can be defined using `pending` instead of `expect`. They will not be run but show up in the spec report as pending.
92
+
93
+ In test files, specs are structured by example groups which are defined by `describe` and `context` sections.
94
+ Typically a top level `describe` defines the outer unit (such as a class) to be tested by the spec.
95
+ Further `describe` sections can be nested within the outer unit to specify smaller units under test (such as individual methods).
96
+
97
+ For unit tests, it is recommended to follow the conventions for method names:
98
+
99
+ * outer `describe` is the name of the class, inner `describe` targets methods;
100
+ * instance methods are prefixed with `#`, class methods with `.`.
101
+
102
+ To establish certain contexts - think _empty array_ versus _array with elements_ - the `context` method may be used to communicate this to the reader.
103
+ It has a different name, but behaves exactly like `describe`.
104
+
105
+ `describe` and `context` take an optional description as argument and a block containing the individual specs or nested groupings.
106
+
107
+ ### Expectations
108
+
109
+ Expectations define if the value being tested (_actual_) matches a certain value or specific criteria.
110
+
111
+ #### Equivalence
54
112
 
55
113
  ```ruby
56
- gem 'r_spec'
114
+ expect(actual).to eql(expected) # passes if expected.eql?(actual)
115
+ expect(actual).to eq(expected) # passes if expected.eql?(actual)
57
116
  ```
58
117
 
59
- And then execute:
118
+ #### Identity
60
119
 
61
- $ bundle
120
+ ```ruby
121
+ expect(actual).to equal(expected) # passes if expected.equal?(actual)
122
+ expect(actual).to be(expected) # passes if expected.equal?(actual)
123
+ ```
62
124
 
63
- Or install it yourself as:
125
+ #### Regular expressions
64
126
 
65
- $ gem install r_spec
127
+ ```ruby
128
+ expect(actual).to match(expected) # passes if expected.match?(actual)
129
+ ```
66
130
 
67
- ## Usage
131
+ #### Expecting errors
68
132
 
69
- Given this `greeting_spec.rb` spec:
133
+ ```ruby
134
+ expect { actual }.to raise_exception(expected) # passes if expected exception is raised
135
+ ```
136
+
137
+ #### Truth
70
138
 
71
139
  ```ruby
72
- require 'r_spec'
140
+ expect(actual).to be_true # passes if true.equal?(actual)
141
+ ```
142
+
143
+ #### Untruth
73
144
 
74
- greeting = 'Hello, world!'
145
+ ```ruby
146
+ expect(actual).to be_false # passes if false.equal?(actual)
147
+ ```
75
148
 
76
- RSpec.describe 'Test' do
77
- context 'Alice' do
78
- before { greeting.gsub!('world', 'Alice') }
79
- it { expect(greeting).to eql 'Hello, Alice!' }
80
- end
149
+ #### Nil
81
150
 
82
- context 'Bob' do
83
- before { greeting.gsub!('world', 'Bob') }
84
- it { expect(greeting).to eql 'Hello, Bob!' }
85
- end
86
- end
151
+ ```ruby
152
+ expect(actual).to be_nil # passes if nil.equal?(actual)
87
153
  ```
88
154
 
89
- It can be tested in the console with the command:
155
+ #### Type/class
90
156
 
91
- $ ruby greeting_spec.rb
92
- ..
157
+ ```ruby
158
+ expect(actual).to be_instance_of(expected) # passes if expected.equal?(actual.class)
159
+ expect(actual).to be_an_instance_of(expected) # passes if expected.equal?(actual.class)
160
+ ```
161
+
162
+ ### Running specs
163
+
164
+ By convention, specs live in the `spec/` directory of a project. Spec files should end with `_spec.rb` to be recognizable as such.
93
165
 
94
- Ran 2 tests in 0.010994 seconds
95
- 100% compliant - 0 infos, 0 failures, 0 errors
166
+ Depending of the project settings, you may run the specs of a project by running `rake spec` (see [`rake` integration example](#rake-integration-example) below).
167
+ A single file can also be executed directly with the Ruby interpreter.
96
168
 
97
- ## Security
169
+ #### Examples
98
170
 
99
- As a basic form of security __R Spec__ provides a set of SHA512 checksums for
100
- every Gem release. These checksums can be found in the `checksum/` directory.
101
- Although these checksums do not prevent malicious users from tampering with a
102
- built Gem they can be used for basic integrity verification purposes.
171
+ Run all specs in files matching `spec/**/*_spec.rb`:
172
+
173
+ ```sh
174
+ bundle exec rake spec
175
+ ```
103
176
 
104
- The checksum of a file can be checked using the `sha512sum` command. For
105
- example:
177
+ Run a single file:
106
178
 
107
- $ sha512sum pkg/r_spec-0.1.0.gem
108
- e9e35e1953104e2d428b0f217e418db3c1baecd9e011b2545f9fcba4ff7e3bba674c6b928b3d8db842a139cd7cc9806d77ebdc7f710ece4f2aecb343703e2451 pkg/r_spec-0.1.0.gem
179
+ ```sh
180
+ ruby spec/my/test/file_spec.rb
181
+ ```
182
+
183
+ I know that sounds weird, but the [`rspec` command line](https://relishapp.com/rspec/rspec-core/docs/command-line) is also working pretty well:
184
+
185
+ ```sh
186
+ rspec spec/my/test/file_spec.rb
187
+ rspec spec/my/test/file_spec.rb:42
188
+ rspec spec/my/test/
189
+ rspec
190
+ ```
191
+
192
+ ### Spec helper
193
+
194
+ Many projects use a custom spec helper file, usually named `spec/spec_helper.rb`.
195
+
196
+ This file is used to require `r_spec` and other includes, like the code from the project needed for every spec file.
197
+
198
+ ### `rake` integration example
199
+
200
+ The following `Rakefile` settings should be enough:
201
+
202
+ ```ruby
203
+ require "bundler/gem_tasks"
204
+ require "rake/testtask"
205
+
206
+ Rake::TestTask.new do |t|
207
+ t.pattern = "spec/**/*_spec.rb"
208
+ t.verbose = true
209
+ t.warning = true
210
+ end
211
+
212
+ task spec: :test
213
+ task default: :test
214
+ ```
215
+
216
+ ## Test suite
217
+
218
+ __RSpec clone__'s specifications are self-described here: [spec/](https://github.com/cyril/r_spec.rb/blob/main/spec/)
219
+
220
+ ## Contact
221
+
222
+ * Home page: https://r-spec.dev
223
+ * Source code: https://github.com/cyril/r_spec.rb
224
+ * Twitter: [https://twitter.com/cyri\_](https://twitter.com/cyri\_)
109
225
 
110
226
  ## Versioning
111
227
 
112
- __R Spec__ follows [Semantic Versioning 2.0](http://semver.org/).
228
+ __RSpec clone__ follows [Semantic Versioning 2.0](https://semver.org/).
113
229
 
114
- ## Contributing
230
+ ## Special thanks
115
231
 
116
- 1. [Fork it](https://github.com/cyril/r_spec/fork)
117
- 2. Create your feature branch (`git checkout -b my-new-feature`)
118
- 3. Commit your changes (`git commit -am 'Add some feature'`)
119
- 4. Push to the branch (`git push origin my-new-feature`)
120
- 5. Create a new Pull Request
232
+ I would like to thank the whole [RSpec team](https://rspec.info/about/) for all their work.
233
+ It's a great framework and it's a pleasure to work with every day.
121
234
 
122
- ## License
235
+ Without RSpec, this clone would not have been possible. ❤️
123
236
 
124
- See `LICENSE.md` file.
237
+ ## License
125
238
 
126
- [gem]: https://rubygems.org/gems/r_spec
127
- [travis]: https://travis-ci.org/cyril/r_spec
128
- [codeclimate]: https://codeclimate.com/github/cyril/r_spec
129
- [gemnasium]: https://gemnasium.com/cyril/r_spec
130
- [inchpages]: http://inch-ci.org/github/cyril/r_spec
131
- [rubydoc]: http://rubydoc.info/gems/r_spec/frames
239
+ The [gem](https://rubygems.org/gems/r_spec) is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/lib/r_spec.rb CHANGED
@@ -1,33 +1,65 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'matchi/rspec'
4
- require 'fix/expect'
5
- require 'fix/its'
6
-
7
- # Namespace for the R Spec framework.
3
+ # Top level namespace for the RSpec clone.
8
4
  #
9
- # @api public
5
+ # @example The true from the false
6
+ # require "r_spec"
7
+ #
8
+ # RSpec.describe do
9
+ # it { expect(false).not_to be true }
10
+ # end
11
+ #
12
+ # @example The basic behavior of arrays
13
+ # require "r_spec"
14
+ #
15
+ # RSpec.describe Array do
16
+ # describe "#size" do
17
+ # it "correctly reports the number of elements in the Array" do
18
+ # expect([1, 2, 3].size).to eq 3
19
+ # end
20
+ # end
21
+ #
22
+ # describe "#empty?" do
23
+ # it "is empty when no elements are in the array" do
24
+ # expect([].empty?).to be_true
25
+ # end
26
+ #
27
+ # it "is not empty if there are elements in the array" do
28
+ # expect([1].empty?).to be_false
29
+ # end
30
+ # end
31
+ # end
32
+ #
33
+ # @example An inherited definition of let
34
+ # require "r_spec"
10
35
  #
36
+ # RSpec.describe Integer do
37
+ # let(:answer) { 42 }
38
+ #
39
+ # it "returns the value" do
40
+ # expect(answer).to be(42)
41
+ # end
42
+ #
43
+ # context "when the number is incremented" do
44
+ # let(:answer) { super().next }
45
+ #
46
+ # it "returns the next value" do
47
+ # expect(answer).to be(43)
48
+ # end
49
+ # end
50
+ # end
51
+ #
52
+ # @api public
11
53
  module RSpec
12
54
  # Specs are built with this method.
13
55
  #
14
- # @example The answer must be equal to 42.
15
- # describe('the answer') do
16
- # it { expect(42).to be 42 }
17
- # end
18
- #
19
- # @param front_object [#object_id] The front object.
20
- # @param options [Hash] Some options.
21
- # @param specs [Proc] The set of specs.
56
+ # @param const [Module, String] A module to include in block context.
57
+ # @param block [Proc] The block to define the specs.
22
58
  #
23
- # @raise [SystemExit] The result of the test.
24
- def self.describe(front_object, options = {}, &specs)
25
- t = ::Fix::Test.new(front_object, options, &specs)
26
-
27
- print t.report.to_s if options.fetch(:verbose, true)
28
- exit t.pass?
59
+ # @api public
60
+ def self.describe(const = nil, &block)
61
+ Dsl.describe(const, &block)
29
62
  end
30
63
  end
31
64
 
32
- require_relative File.join 'fix', 'it'
33
- require_relative File.join 'fix', 'on'
65
+ require_relative File.join("r_spec", "dsl")
data/lib/r_spec/dsl.rb ADDED
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+
5
+ module RSpec
6
+ # Abstract class for handling the domain-specific language.
7
+ class Dsl
8
+ # Instructs the spec runner to execute the given block before each spec in
9
+ # the spec suite.
10
+ #
11
+ # @param block [Proc] The content to execute at the class initialization.
12
+ def self.before(&block)
13
+ define_method(:initialize) do |*args, **kwargs|
14
+ super()
15
+ instance_exec(*args, **kwargs, &block)
16
+ end
17
+ end
18
+
19
+ # Sets a user-defined property.
20
+ #
21
+ # @param name [String, Symbol] The name of the property.
22
+ # @param block [Proc] The content of the method to define.
23
+ #
24
+ # @return [Symbol] A protected method that define the block content.
25
+ def self.let(name, *args, **kwargs, &block)
26
+ protected define_method(name.to_sym, *args, **kwargs, &block)
27
+ end
28
+
29
+ # Sets a user-defined property named `subject`.
30
+ #
31
+ # @param block [Proc] The subject to set.
32
+ # @return [Symbol] A `subject` method that define the block content.
33
+ def self.subject(&block)
34
+ let(__method__, &block)
35
+ end
36
+
37
+ # Create a group of specs.
38
+ #
39
+ # @param const [Module, #object_id] A module to include in block context.
40
+ # @param block [Proc] The block to define the specs.
41
+ def self.describe(const = nil, &block)
42
+ desc = Sandbox.const_set(random_test_const_name, ::Class.new(self))
43
+
44
+ if const.is_a?(::Module)
45
+ desc.define_method(:described_class) { const }
46
+ desc.send(:protected, :described_class)
47
+ end
48
+
49
+ desc.instance_eval(&block)
50
+ desc
51
+ end
52
+
53
+ # Add `context` to the DSL.
54
+ singleton_class.send(:alias_method, :context, :describe)
55
+
56
+ # Define a single spec. A spec should contain one or more expectations that
57
+ # test the state of the code.
58
+ #
59
+ # @param block [Proc] An expectation to evaluate.
60
+ #
61
+ # @raise (see ExpectationTarget::Base#result)
62
+ # @return (see ExpectationTarget::Base#result)
63
+ def self.it(_name = nil, &block)
64
+ raise ::ArgumentError, "Missing block" unless block
65
+
66
+ puts "\e[37m#{block.source_location.join(':')}\e[0m"
67
+
68
+ i = it_example.new
69
+ i.instance_eval(&block)
70
+ end
71
+
72
+ # Define a single spec. A spec should contain one or more expectations that
73
+ # test the state of the code.
74
+ #
75
+ # @example The value after 41
76
+ # require "r_spec"
77
+ #
78
+ # RSpec.describe Integer do
79
+ # subject { 41 }
80
+ #
81
+ # its(:next) { is_expected.to be 42 }
82
+ # end
83
+ #
84
+ # @example Without defining a subject
85
+ # require "r_spec"
86
+ #
87
+ # RSpec.describe Integer do
88
+ # its(:next) { is_expected.to raise_exception NameError }
89
+ # end
90
+ #
91
+ # @param attribute [String, Symbol] The property to call to subject.
92
+ #
93
+ # @raise (see ExpectationTarget::Base#result)
94
+ # @return (see ExpectationTarget::Base#result)
95
+ def self.its(attribute, *args, **kwargs, &block)
96
+ raise ::ArgumentError, "Missing block" unless block
97
+
98
+ puts "\e[37m#{block.source_location.join(':')}\e[0m"
99
+
100
+ i = its_example.new
101
+
102
+ i.define_singleton_method(:actual) do
103
+ subject.public_send(attribute, *args, **kwargs)
104
+ end
105
+
106
+ i.instance_eval(&block)
107
+ end
108
+
109
+ # @private
110
+ #
111
+ # @return [Class<Dsl>] The class of the example to be tested.
112
+ def self.it_example
113
+ ::Class.new(self) { include ExpectationHelper::It }
114
+ end
115
+
116
+ # @private
117
+ #
118
+ # @return [Class<Dsl>] The class of the example to be tested.
119
+ def self.its_example
120
+ ::Class.new(self) { include ExpectationHelper::Its }
121
+ end
122
+
123
+ # @private
124
+ #
125
+ # @return [String] A random constant name for a test class.
126
+ def self.random_test_const_name
127
+ "Test#{::SecureRandom.hex(4).to_i(16)}"
128
+ end
129
+
130
+ private_class_method :it_example, :its_example, :random_test_const_name
131
+ end
132
+ end
133
+
134
+ require_relative "expectation_helper"
135
+ require_relative "sandbox"