test_tube 1.1.0 → 2.1.2
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 +4 -4
- data/README.md +43 -53
- data/lib/test_tube.rb +17 -19
- data/lib/test_tube/base.rb +11 -1
- data/lib/test_tube/invoker.rb +14 -14
- data/lib/test_tube/passer.rb +3 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cba8c30ae585d6ac402dffd73a643e1f56b5e3341ae02c7299f1b9a4bb34b38f
|
4
|
+
data.tar.gz: 1b4d9ca54c4eb275982bbee603c16a82639a988920a61f8e9b2029a8b4ee6471
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4332f949beca9c924e535917bca0df8538c385daeebb60bbced8f4b3868f05112f86c140147f01b34d4c6d3d68a5c0deea4879769d7af796d2d1a87af6a6bcf5
|
7
|
+
data.tar.gz: 15dbac8b721a1d11849138783f009462bbdee16b899713db691432d33285bf997da4068efaf61fee874e12adb47dcc66d984d036a8205124b111139f71f05945
|
data/README.md
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
# Test Tube
|
2
2
|
|
3
|
-
[](https://github.com/fixrb/test_tube/releases)
|
4
|
+
[](https://rubydoc.info/github/fixrb/test_tube/main)
|
5
|
+
[](https://github.com/fixrb/test_tube/actions?query=workflow%3Aci+branch%3Amain)
|
6
|
+
[](https://github.com/fixrb/test_tube/actions?query=workflow%3Arubocop+branch%3Amain)
|
7
|
+
[](https://github.com/fixrb/test_tube/raw/main/LICENSE.md)
|
6
8
|
|
7
9
|
> A test tube to conduct software experiments 🧪
|
8
10
|
|
@@ -37,7 +39,7 @@ require "test_tube"
|
|
37
39
|
```
|
38
40
|
|
39
41
|
Assuming we'd like to experiment on the answer to the Ultimate Question of Life,
|
40
|
-
the Universe, and Everything with the following
|
42
|
+
the Universe, and Everything with the following matcher:
|
41
43
|
|
42
44
|
```ruby
|
43
45
|
class BeTheAnswer
|
@@ -47,21 +49,13 @@ class BeTheAnswer
|
|
47
49
|
end
|
48
50
|
```
|
49
51
|
|
50
|
-
|
51
|
-
parameter representing the _actual value_ to be compared.
|
52
|
-
|
53
|
-
Back to our Ruby experiments, one possibility would be to `invoke` a whole block
|
54
|
-
of code:
|
52
|
+
One possibility would be to `invoke` a whole block of code:
|
55
53
|
|
56
54
|
```ruby
|
57
55
|
block_of_code = -> { "101010".to_i(2) }
|
58
56
|
|
59
|
-
experiment = TestTube.invoke(
|
60
|
-
|
61
|
-
isolation: false,
|
62
|
-
matcher: BeTheAnswer.new,
|
63
|
-
negate: false
|
64
|
-
)
|
57
|
+
experiment = TestTube.invoke(isolate: false, matcher: BeTheAnswer.new, negate: false, &block_of_code)
|
58
|
+
# => <TestTube actual=42 error=nil got=true>
|
65
59
|
|
66
60
|
experiment.actual # => 42
|
67
61
|
experiment.error # => nil
|
@@ -73,11 +67,8 @@ An alternative would be to `pass` directly the actual value as a parameter:
|
|
73
67
|
```ruby
|
74
68
|
actual_value = "101010".to_i(2)
|
75
69
|
|
76
|
-
experiment = TestTube.pass(
|
77
|
-
|
78
|
-
matcher: BeTheAnswer.new,
|
79
|
-
negate: false
|
80
|
-
)
|
70
|
+
experiment = TestTube.pass(actual_value, matcher: BeTheAnswer.new, negate: false)
|
71
|
+
# => <TestTube actual=42 error=nil got=true>
|
81
72
|
|
82
73
|
experiment.actual # => 42
|
83
74
|
experiment.error # => nil
|
@@ -85,6 +76,7 @@ experiment.got # => true
|
|
85
76
|
```
|
86
77
|
|
87
78
|
### __Matchi__ matchers
|
79
|
+
|
88
80
|
To facilitate the addition of matchers, a collection is available via the
|
89
81
|
[__Matchi__ project](https://github.com/fixrb/matchi/).
|
90
82
|
|
@@ -102,11 +94,11 @@ An example of successful experience:
|
|
102
94
|
|
103
95
|
```ruby
|
104
96
|
experiment = TestTube.invoke(
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
97
|
+
isolate: false,
|
98
|
+
matcher: Matchi::RaiseException.new(:NoMethodError),
|
99
|
+
negate: false
|
100
|
+
) { "foo".blank? }
|
101
|
+
# => <TestTube actual=#<NoMethodError: undefined method `blank?' for "foo":String> error=nil got=true>
|
110
102
|
|
111
103
|
experiment.actual # => #<NoMethodError: undefined method `blank?' for "foo":String>
|
112
104
|
experiment.error # => nil
|
@@ -117,11 +109,11 @@ Another example of an experiment that fails:
|
|
117
109
|
|
118
110
|
```ruby
|
119
111
|
experiment = TestTube.invoke(
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
)
|
112
|
+
isolate: false,
|
113
|
+
matcher: Matchi::Be.new(0.3),
|
114
|
+
negate: false,
|
115
|
+
&-> { 0.1 + 0.2 }
|
116
|
+
) # => <TestTube actual=0.30000000000000004 error=nil got=false>
|
125
117
|
|
126
118
|
experiment.actual # => 0.30000000000000004
|
127
119
|
experiment.error # => nil
|
@@ -132,11 +124,11 @@ Finally, an experiment which causes an error:
|
|
132
124
|
|
133
125
|
```ruby
|
134
126
|
experiment = TestTube.invoke(
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
127
|
+
isolate: false,
|
128
|
+
matcher: Matchi::Match.new(/^foo$/),
|
129
|
+
negate: false
|
130
|
+
) { BOOM }
|
131
|
+
# => <TestTube actual=nil error=#<NameError: uninitialized constant BOOM> got=nil>
|
140
132
|
|
141
133
|
experiment.actual # => nil
|
142
134
|
experiment.error # => #<NameError: uninitialized constant BOOM>
|
@@ -146,27 +138,25 @@ experiment.got # => nil
|
|
146
138
|
### Code isolation
|
147
139
|
|
148
140
|
When experimenting tests, side-effects may occur. Because they may or may not be
|
149
|
-
desired, an `
|
141
|
+
desired, an `isolate` option is available.
|
150
142
|
|
151
143
|
Let's for instance consider this block of code:
|
152
144
|
|
153
145
|
```ruby
|
154
146
|
greeting = "Hello, world!"
|
155
|
-
block_of_code = -> { greeting.gsub!("world", "Alice") }
|
147
|
+
block_of_code = -> { greeting.gsub!("world", "Alice") } # => #<Proc:0x00007f87f71b9690 (irb):42 (lambda)>
|
156
148
|
```
|
157
149
|
|
158
|
-
By setting the `
|
150
|
+
By setting the `isolate` option to `true`, we can experiment while avoiding
|
159
151
|
side effects:
|
160
152
|
|
161
153
|
```ruby
|
162
154
|
experiment = TestTube.invoke(
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
)
|
168
|
-
|
169
|
-
experiment.inspect # => <TestTube actual="Hello, Alice!" error=nil got=true>
|
155
|
+
isolate: true,
|
156
|
+
matcher: Matchi::Eq.new("Hello, Alice!"),
|
157
|
+
negate: false,
|
158
|
+
&block_of_code
|
159
|
+
) # => <TestTube actual="Hello, Alice!" error=nil got=true>
|
170
160
|
|
171
161
|
greeting # => "Hello, world!"
|
172
162
|
```
|
@@ -175,13 +165,11 @@ Otherwise, we can experiment without any code isolation:
|
|
175
165
|
|
176
166
|
```ruby
|
177
167
|
experiment = TestTube.invoke(
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
)
|
183
|
-
|
184
|
-
experiment.inspect # => <TestTube actual="Hello, Alice!" error=nil got=true>
|
168
|
+
isolate: false,
|
169
|
+
matcher: Matchi::Eq.new("Hello, Alice!"),
|
170
|
+
negate: false,
|
171
|
+
&block_of_code
|
172
|
+
) # => <TestTube actual="Hello, Alice!" error=nil got=true>
|
185
173
|
|
186
174
|
greeting # => "Hello, Alice!"
|
187
175
|
```
|
@@ -189,6 +177,8 @@ greeting # => "Hello, Alice!"
|
|
189
177
|
## Contact
|
190
178
|
|
191
179
|
* Source code: https://github.com/fixrb/test_tube
|
180
|
+
* Chinese blog post: https://ruby-china.org/topics/41390
|
181
|
+
* Japanese blog post: https://qiita.com/cyril/items/36174b619ff1852c80ec
|
192
182
|
|
193
183
|
## Versioning
|
194
184
|
|
@@ -196,7 +186,7 @@ __Test Tube__ follows [Semantic Versioning 2.0](https://semver.org/).
|
|
196
186
|
|
197
187
|
## License
|
198
188
|
|
199
|
-
The [gem](https://rubygems.org/gems/test_tube) is available as open source under the terms of the [MIT License](https://
|
189
|
+
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).
|
200
190
|
|
201
191
|
***
|
202
192
|
|
data/lib/test_tube.rb
CHANGED
@@ -4,11 +4,13 @@ require_relative File.join("test_tube", "invoker")
|
|
4
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
|
9
|
-
# @param
|
10
|
-
# @param
|
11
|
-
# @param
|
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.
|
12
14
|
#
|
13
15
|
# @example
|
14
16
|
# require "test_tube"
|
@@ -19,21 +21,18 @@ module TestTube
|
|
19
21
|
# end
|
20
22
|
# end
|
21
23
|
#
|
22
|
-
# TestTube.invoke(
|
23
|
-
#
|
24
|
-
#
|
25
|
-
# matcher: BeTheAnswer.new,
|
26
|
-
# negate: false
|
27
|
-
# )
|
24
|
+
# TestTube.invoke(isolate: false, matcher: BeTheAnswer.new, negate: false) do
|
25
|
+
# "101010".to_i(2)
|
26
|
+
# end
|
28
27
|
#
|
29
28
|
# @return [Invoker] A software experiment.
|
30
|
-
def self.invoke(
|
31
|
-
Invoker.new(
|
29
|
+
def self.invoke(isolate:, matcher:, negate:, &input)
|
30
|
+
Invoker.new(isolate: isolate, matcher: matcher, negate: negate, &input)
|
32
31
|
end
|
33
32
|
|
34
|
-
# @param input
|
35
|
-
# @param matcher
|
36
|
-
# @param negate
|
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.
|
37
36
|
#
|
38
37
|
# @example
|
39
38
|
# require "test_tube"
|
@@ -44,10 +43,9 @@ module TestTube
|
|
44
43
|
# end
|
45
44
|
# end
|
46
45
|
#
|
47
|
-
# TestTube.pass(
|
48
|
-
#
|
49
|
-
#
|
50
|
-
# negate: false
|
46
|
+
# TestTube.pass("101010".to_i(2),
|
47
|
+
# matcher: BeTheAnswer.new,
|
48
|
+
# negate: false
|
51
49
|
# )
|
52
50
|
#
|
53
51
|
# @return [Passer] A software experiment.
|
data/lib/test_tube/base.rb
CHANGED
@@ -2,25 +2,35 @@
|
|
2
2
|
|
3
3
|
module TestTube
|
4
4
|
# Abstract class representing the state of an experiment.
|
5
|
-
|
5
|
+
#
|
6
|
+
# @api private
|
7
|
+
class Base < ::BasicObject
|
6
8
|
# Expectation's actual value.
|
7
9
|
#
|
8
10
|
# @return [#object_id] The actual value.
|
11
|
+
#
|
12
|
+
# @api public
|
9
13
|
attr_reader :actual
|
10
14
|
|
11
15
|
# Expectation's raised error.
|
12
16
|
#
|
13
17
|
# @return [Exception, nil] The raised error.
|
18
|
+
#
|
19
|
+
# @api public
|
14
20
|
attr_reader :error
|
15
21
|
|
16
22
|
# Expectation's returned boolean value.
|
17
23
|
#
|
18
24
|
# @return [Boolean, nil] The returned boolean value.
|
25
|
+
#
|
26
|
+
# @api public
|
19
27
|
attr_reader :got
|
20
28
|
|
21
29
|
# A string containing a human-readable representation of the experiment.
|
22
30
|
#
|
23
31
|
# @return [String] The human-readable representation of the experiment.
|
32
|
+
#
|
33
|
+
# @api public
|
24
34
|
def inspect
|
25
35
|
"<TestTube actual=#{actual.inspect} error=#{error.inspect} got=#{got.inspect}>"
|
26
36
|
end
|
data/lib/test_tube/invoker.rb
CHANGED
@@ -6,28 +6,28 @@ require_relative "base"
|
|
6
6
|
|
7
7
|
module TestTube
|
8
8
|
# Evaluate an actual value invoking it with #call method.
|
9
|
+
#
|
10
|
+
# @api private
|
9
11
|
class Invoker < Base
|
10
12
|
# Class initializer.
|
11
13
|
#
|
12
14
|
# rubocop:disable Lint/RescueException, Metrics/MethodLength
|
13
15
|
#
|
14
|
-
# @param
|
15
|
-
# @param
|
16
|
-
# @param
|
17
|
-
# @param
|
18
|
-
def initialize(
|
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
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
end
|
23
|
+
value = if isolate
|
24
|
+
send_call.to!(input)
|
25
|
+
else
|
26
|
+
send_call.to(input)
|
27
|
+
end
|
27
28
|
|
28
|
-
|
29
|
-
|
30
|
-
end
|
29
|
+
@actual = value.object
|
30
|
+
@got = negate ^ matcher.matches? { value.call }
|
31
31
|
rescue ::Exception => e
|
32
32
|
@actual = nil
|
33
33
|
@error = e
|
data/lib/test_tube/passer.rb
CHANGED
@@ -4,10 +4,12 @@ require_relative "base"
|
|
4
4
|
|
5
5
|
module TestTube
|
6
6
|
# Evaluate an actual value passed in parameter.
|
7
|
+
#
|
8
|
+
# @api private
|
7
9
|
class Passer < Base
|
8
10
|
# Class initializer.
|
9
11
|
#
|
10
|
-
# @param input [#object_id]
|
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:)
|
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:
|
4
|
+
version: 2.1.2
|
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-
|
11
|
+
date: 2021-08-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: defi
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 2.0.
|
19
|
+
version: 2.0.6
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 2.0.
|
26
|
+
version: 2.0.6
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: brutal
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|