test_tube 1.0.0.beta0 → 2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3e928758abf1d784bdf93e7d9128f3fc69a48bdcf532769a9f5250a0a09338ac
4
- data.tar.gz: ba835fe5031d9af8fc124f6cb012008923d3080a8de953a0a31c348f693102cd
3
+ metadata.gz: 6aa09337dc5d35dd53da1f517ff92bced3b0d0c4f9c25eef1cd9287b63e14bbf
4
+ data.tar.gz: 36026bb1f5f3532e70ff51ebc25a1ddcf8c6098214c438e6a6fb6ad0305b249c
5
5
  SHA512:
6
- metadata.gz: 132fe5f45b2b7317667e868aed913d856caa86fa302a6f954ef3a5ca7c11ac0c8047125df550196999208340f731cb4fc1d296974b465e6bad66afe4c4c2c37f
7
- data.tar.gz: e2d7535593db0618488764c7d1d42ec6902f75912c79f2bd010c7f627722d9ff5c3fb8b8e70ef30a5b6a807a104b65ff0c688dea4c76e8b8f856afb3f002bfab
6
+ metadata.gz: 668493142591c985ece8e150691d7e60d6834b5da976361ff4c4aea34204b0c645aea61352424daf6964366014df424f881fd6b27fb416c581399395b9c7f8af
7
+ data.tar.gz: ea4ce58827e8643b54811082bee5861502d339daa4489ac69355fc06c172ca15f6c1129c6db2bbee171184d06211dba70cca56995302e18d176b3801e61e9f8f
data/README.md CHANGED
@@ -1,11 +1,15 @@
1
1
  # Test Tube
2
2
 
3
- [![Build Status](https://api.travis-ci.org/fixrb/test_tube.svg?branch=main)](https://travis-ci.org/fixrb/test_tube)
4
- [![Gem Version](https://badge.fury.io/rb/test_tube.svg)](https://rubygems.org/gems/test_tube)
5
- [![Documentation](https://img.shields.io/:yard-docs-38c800.svg)](https://rubydoc.info/gems/test_tube)
3
+ [![Version](https://img.shields.io/github/v/tag/fixrb/test_tube?label=Version&logo=github)](https://github.com/fixrb/test_tube/releases)
4
+ [![Yard documentation](https://img.shields.io/badge/Yard-documentation-blue.svg?logo=github)](https://rubydoc.info/github/fixrb/test_tube/main)
5
+ [![CI](https://github.com/fixrb/test_tube/workflows/CI/badge.svg?branch=main)](https://github.com/fixrb/test_tube/actions?query=workflow%3Aci+branch%3Amain)
6
+ [![RuboCop](https://github.com/fixrb/test_tube/workflows/RuboCop/badge.svg?branch=main)](https://github.com/fixrb/test_tube/actions?query=workflow%3Arubocop+branch%3Amain)
7
+ [![License](https://img.shields.io/github/license/fixrb/test_tube?label=License&logo=github)](https://github.com/fixrb/test_tube/raw/main/LICENSE.md)
6
8
 
7
9
  > A test tube to conduct software experiments 🧪
8
10
 
11
+ ![A researcher experimenting with Ruby code](https://github.com/fixrb/test_tube/raw/main/img/social-media-preview.png)
12
+
9
13
  ## Installation
10
14
 
11
15
  Add this line to your application's Gemfile:
@@ -28,8 +32,14 @@ gem install test_tube
28
32
 
29
33
  ## Usage
30
34
 
31
- Let's experiment the answer to the Ultimate Question of Life, the Universe, and
32
- Everything:
35
+ To make __TestTube__ available:
36
+
37
+ ```ruby
38
+ require "test_tube"
39
+ ```
40
+
41
+ Assuming we'd like to experiment on the answer to the Ultimate Question of Life,
42
+ the Universe, and Everything with the following _matcher_:
33
43
 
34
44
  ```ruby
35
45
  class BeTheAnswer
@@ -37,23 +47,142 @@ class BeTheAnswer
37
47
  42.equal?(yield)
38
48
  end
39
49
  end
50
+ ```
51
+
52
+ A _matcher_ is an object that responds to the `matches?` method with a block
53
+ parameter representing the _actual value_ to be compared.
54
+
55
+ Back to our Ruby experiments, one possibility would be to `invoke` a whole block
56
+ of code:
57
+
58
+ ```ruby
59
+ block_of_code = -> { "101010".to_i(2) }
60
+
61
+ experiment = TestTube.invoke(isolate: false, matcher: BeTheAnswer.new, negate: false, &block_of_code)
62
+ # => <TestTube actual=42 error=nil got=true>
63
+
64
+ experiment.actual # => 42
65
+ experiment.error # => nil
66
+ experiment.got # => true
67
+ ```
68
+
69
+ An alternative would be to `pass` directly the actual value as a parameter:
70
+
71
+ ```ruby
72
+ actual_value = "101010".to_i(2)
73
+
74
+ experiment = TestTube.pass(actual_value, matcher: BeTheAnswer.new, negate: false)
75
+ # => <TestTube actual=42 error=nil got=true>
76
+
77
+ experiment.actual # => 42
78
+ experiment.error # => nil
79
+ experiment.got # => true
80
+ ```
81
+
82
+ ### __Matchi__ matchers
83
+
84
+ To facilitate the addition of matchers, a collection is available via the
85
+ [__Matchi__ project](https://github.com/fixrb/matchi/).
86
+
87
+ Let's use a built-in __Matchi__ matcher:
88
+
89
+ ```sh
90
+ gem install matchi
91
+ ```
92
+
93
+ ```ruby
94
+ require "matchi"
95
+ ```
96
+
97
+ An example of successful experience:
98
+
99
+ ```ruby
100
+ experiment = TestTube.invoke(
101
+ isolate: false,
102
+ matcher: Matchi::Matcher::RaiseException.new(NoMethodError),
103
+ negate: false
104
+ ) { "foo".blank? }
105
+ # => <TestTube actual=#<NoMethodError: undefined method `blank?' for "foo":String> error=nil got=true>
106
+
107
+ experiment.actual # => #<NoMethodError: undefined method `blank?' for "foo":String>
108
+ experiment.error # => nil
109
+ experiment.got # => true
110
+ ```
111
+
112
+ Another example of an experiment that fails:
113
+
114
+ ```ruby
115
+ experiment = TestTube.invoke(
116
+ isolate: false,
117
+ matcher: Matchi::Matcher::Equal.new(0.3),
118
+ negate: false,
119
+ &-> { 0.1 + 0.2 }
120
+ ) # => <TestTube actual=0.30000000000000004 error=nil got=false>
121
+
122
+ experiment.actual # => 0.30000000000000004
123
+ experiment.error # => nil
124
+ experiment.got # => false
125
+ ```
126
+
127
+ Finally, an experiment which causes an error:
128
+
129
+ ```ruby
130
+ experiment = TestTube.invoke(
131
+ isolate: false,
132
+ matcher: Matchi::Matcher::Match.new(/^foo$/),
133
+ negate: false
134
+ ) { BOOM }
135
+ # => <TestTube actual=nil error=#<NameError: uninitialized constant BOOM> got=nil>
136
+
137
+ experiment.actual # => nil
138
+ experiment.error # => #<NameError: uninitialized constant BOOM>
139
+ experiment.got # => nil
140
+ ```
141
+
142
+ ### Code isolation
143
+
144
+ When experimenting tests, side-effects may occur. Because they may or may not be
145
+ desired, an `isolate` option is available.
146
+
147
+ Let's for instance consider this block of code:
148
+
149
+ ```ruby
150
+ greeting = "Hello, world!"
151
+ block_of_code = -> { greeting.gsub!("world", "Alice") } # => #<Proc:0x00007f87f71b9690 (irb):42 (lambda)>
152
+ ```
40
153
 
41
- tt = TestTube.content(
42
- -> { "101010".to_i(2) },
43
- isolation: false,
44
- matcher: BeTheAnswer.new,
45
- negate: false
46
- )
47
- # => #<TestTube::Content:0x00007fb3b328b248 @actual=42, @got=true, @error=nil>
48
-
49
- tt.actual # => 42
50
- tt.error # => nil
51
- tt.got # => true
154
+ By setting the `isolate` option to `true`, we can experiment while avoiding
155
+ side effects:
156
+
157
+ ```ruby
158
+ experiment = TestTube.invoke(
159
+ isolate: true,
160
+ matcher: Matchi::Matcher::Eql.new("Hello, Alice!"),
161
+ negate: false,
162
+ &block_of_code
163
+ ) # => <TestTube actual="Hello, Alice!" error=nil got=true>
164
+
165
+ greeting # => "Hello, world!"
166
+ ```
167
+
168
+ Otherwise, we can experiment without any code isolation:
169
+
170
+ ```ruby
171
+ experiment = TestTube.invoke(
172
+ isolate: false,
173
+ matcher: Matchi::Matcher::Eql.new("Hello, Alice!"),
174
+ negate: false,
175
+ &block_of_code
176
+ ) # => <TestTube actual="Hello, Alice!" error=nil got=true>
177
+
178
+ greeting # => "Hello, Alice!"
52
179
  ```
53
180
 
54
181
  ## Contact
55
182
 
56
183
  * Source code: https://github.com/fixrb/test_tube
184
+ * Chinese blog post: https://ruby-china.org/topics/41390
185
+ * Japanese blog post: https://qiita.com/cyril/items/36174b619ff1852c80ec
57
186
 
58
187
  ## Versioning
59
188
 
@@ -61,7 +190,7 @@ __Test Tube__ follows [Semantic Versioning 2.0](https://semver.org/).
61
190
 
62
191
  ## License
63
192
 
64
- The [gem](https://rubygems.org/gems/test_tube) is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
193
+ The [gem](https://rubygems.org/gems/test_tube) is available as open source under the terms of the [MIT License](https://github.com/fixrb/test_tube/raw/main/LICENSE.md).
65
194
 
66
195
  ***
67
196
 
data/lib/test_tube.rb CHANGED
@@ -1,26 +1,55 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative File.join("test_tube", "content")
4
- require_relative File.join("test_tube", "liquid")
3
+ require_relative File.join("test_tube", "invoker")
4
+ require_relative File.join("test_tube", "passer")
5
5
 
6
6
  # Namespace for the TestTube library.
7
+ #
8
+ # @api public
7
9
  module TestTube
8
- # @param input [#call] The callable object to test.
9
- # @param isolation [Boolean] Compute in isolation or not.
10
- # @param matcher [#matches?] A matcher.
11
- # @param negate [Boolean] Invert the matcher or not.
12
- #
13
- # @return [Content] A software experiment.
14
- def self.content(input, isolation:, matcher:, negate:)
15
- Content.new(input, isolation: isolation, matcher: matcher, negate: negate)
10
+ # @param isolate [Boolean] Compute in a subprocess.
11
+ # @param matcher [#matches?] A matcher.
12
+ # @param negate [Boolean] Invert the matcher or not.
13
+ # @param input [Proc] The callable object to test.
14
+ #
15
+ # @example
16
+ # require "test_tube"
17
+ #
18
+ # class BeTheAnswer
19
+ # def matches?
20
+ # 42.equal?(yield)
21
+ # end
22
+ # end
23
+ #
24
+ # TestTube.invoke(isolate: false, matcher: BeTheAnswer.new, negate: false) do
25
+ # "101010".to_i(2)
26
+ # end
27
+ #
28
+ # @return [Invoker] A software experiment.
29
+ def self.invoke(isolate:, matcher:, negate:, &input)
30
+ Invoker.new(isolate: isolate, matcher: matcher, negate: negate, &input)
16
31
  end
17
32
 
18
- # @param input [#call] The callable object to test.
19
- # @param matcher [#matches?] A matcher.
20
- # @param negate [Boolean] Invert the matcher or not.
33
+ # @param input [#object_id] The actual value to test.
34
+ # @param matcher [#matches?] A matcher.
35
+ # @param negate [Boolean] Invert the matcher or not.
36
+ #
37
+ # @example
38
+ # require "test_tube"
39
+ #
40
+ # class BeTheAnswer
41
+ # def matches?
42
+ # 42.equal?(yield)
43
+ # end
44
+ # end
45
+ #
46
+ # TestTube.pass("101010".to_i(2),
47
+ # matcher: BeTheAnswer.new,
48
+ # negate: false
49
+ # )
21
50
  #
22
- # @return [Content] A software experiment.
23
- def self.liquid(input, matcher:, negate:)
24
- Liquid.new(input, matcher: matcher, negate: negate)
51
+ # @return [Passer] A software experiment.
52
+ def self.pass(input, matcher:, negate:)
53
+ Passer.new(input, matcher: matcher, negate: negate)
25
54
  end
26
55
  end
@@ -1,15 +1,40 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TestTube
4
- # The liquid class is great for values.
4
+ # Abstract class representing the state of an experiment.
5
+ #
6
+ # @api private
5
7
  class Base
8
+ # Expectation's actual value.
9
+ #
6
10
  # @return [#object_id] The actual value.
11
+ #
12
+ # @api public
7
13
  attr_reader :actual
8
14
 
9
- # @return [Exception, nil] The raised exception.
15
+ # Expectation's raised error.
16
+ #
17
+ # @return [Exception, nil] The raised error.
18
+ #
19
+ # @api public
10
20
  attr_reader :error
11
21
 
12
- # @return [Boolean, nil] The test result.
22
+ # Expectation's returned boolean value.
23
+ #
24
+ # @return [Boolean, nil] The returned boolean value.
25
+ #
26
+ # @api public
13
27
  attr_reader :got
28
+
29
+ # A string containing a human-readable representation of the experiment.
30
+ #
31
+ # @return [String] The human-readable representation of the experiment.
32
+ #
33
+ # @api public
34
+ def inspect
35
+ "<TestTube actual=#{actual.inspect} error=#{error.inspect} got=#{got.inspect}>"
36
+ end
37
+
38
+ alias to_s inspect
14
39
  end
15
40
  end
@@ -5,21 +5,23 @@ require "defi"
5
5
  require_relative "base"
6
6
 
7
7
  module TestTube
8
- # The content class is great for blocks.
9
- class Content < Base
10
- # Software experiments.
8
+ # Evaluate an actual value invoking it with #call method.
9
+ #
10
+ # @api private
11
+ class Invoker < Base
12
+ # Class initializer.
11
13
  #
12
14
  # rubocop:disable Lint/RescueException, Metrics/MethodLength
13
15
  #
14
- # @param input [#call] The callable object to test.
15
- # @param isolation [Boolean] Compute in isolation or not.
16
- # @param matcher [#matches?] A matcher.
17
- # @param negate [Boolean] Invert the matcher or not.
18
- def initialize(input, isolation:, matcher:, negate:)
16
+ # @param isolate [Boolean] Compute in a subprocess.
17
+ # @param matcher [#matches?] A matcher.
18
+ # @param negate [Boolean] Invert the matcher or not.
19
+ # @param input [Proc] The callable object to test.
20
+ def initialize(isolate:, matcher:, negate:, &input)
19
21
  super()
20
22
 
21
23
  @got = negate ^ matcher.matches? do
22
- value = if isolation
24
+ value = if isolate
23
25
  send_call.to!(input)
24
26
  else
25
27
  send_call.to(input)
@@ -3,18 +3,20 @@
3
3
  require_relative "base"
4
4
 
5
5
  module TestTube
6
- # The liquid class is great for values.
7
- class Liquid < Base
8
- # Software experiments.
6
+ # Evaluate an actual value passed in parameter.
7
+ #
8
+ # @api private
9
+ class Passer < Base
10
+ # Class initializer.
9
11
  #
10
- # @param input [#object_id] An actual value to test.
12
+ # @param input [#object_id] The actual value to test.
11
13
  # @param matcher [#matches?] A matcher.
12
14
  # @param negate [Boolean] Invert the matcher or not.
13
15
  def initialize(input, matcher:, negate:)
14
16
  super()
15
17
 
16
18
  @actual = input
17
- @got = negate ^ matcher.matches? { @actual }
19
+ @got = negate ^ matcher.matches? { input }
18
20
  end
19
21
  end
20
22
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: test_tube
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.beta0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cyril Kato
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-06-18 00:00:00.000000000 Z
11
+ date: 2021-07-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: defi
@@ -160,8 +160,8 @@ files:
160
160
  - README.md
161
161
  - lib/test_tube.rb
162
162
  - lib/test_tube/base.rb
163
- - lib/test_tube/content.rb
164
- - lib/test_tube/liquid.rb
163
+ - lib/test_tube/invoker.rb
164
+ - lib/test_tube/passer.rb
165
165
  homepage: https://github.com/fixrb/test_tube
166
166
  licenses:
167
167
  - MIT
@@ -177,9 +177,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
177
177
  version: 2.7.0
178
178
  required_rubygems_version: !ruby/object:Gem::Requirement
179
179
  requirements:
180
- - - ">"
180
+ - - ">="
181
181
  - !ruby/object:Gem::Version
182
- version: 1.3.1
182
+ version: '0'
183
183
  requirements: []
184
184
  rubygems_version: 3.1.6
185
185
  signing_key: