test_tube 1.0.0.beta0 → 2.1.0

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: 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: