test_tube 1.0.0 → 1.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: ed96729c5b8c3a0ccef89cc6739da3e46e54c70eaf3e0975ca7af8d74db23d26
4
- data.tar.gz: b534c7cb0d0e5de7ce02c328b1be76960214ec4e6a4d45b031fafb1d9ffac136
3
+ metadata.gz: f1c0e61708e5bd5a64d8c57b429c1dcdb74c35003fb2717aeb09777c10b58ed4
4
+ data.tar.gz: 99e9ee9069e4b5a3124fb42566eaf9bed7cce5328335ba8ef0abc96b285f9715
5
5
  SHA512:
6
- metadata.gz: 48ac22dc6ada96e416df896a7c6e4bf7242e2391ce3788c28b2a2aeaa2ee93a18ed5f690be51dc0a434c97a8185c112a796cb7f0985dabe90eb39b33f8b4fee9
7
- data.tar.gz: 5a508032716cd4836009b9ac4b2b1fcdfa07e7b579fbfae771563bdebb81cf6e68efa2a7edbe897f8162015c8e45c0ba46346785026867f227cae14da4c398ca
6
+ metadata.gz: 3e30d9ccb6fc2186e61d6f3278da6db17568fe8144fe3008af9a1523a579eeb345246e4f5bc6675fe9d4ba3090890ef400c535f69f0fd3ba0f909f8511d17545
7
+ data.tar.gz: c58676b8830b438bfde6fc83a12cf8c110e0049da7b7b63385742628e304bce385bfcbed2303d21abb26bc7b76c647394caf0d6a41679bd5e3939e762b0aa83a
data/README.md CHANGED
@@ -6,6 +6,8 @@
6
6
 
7
7
  > A test tube to conduct software experiments 🧪
8
8
 
9
+ ![A researcher experimenting with Ruby code](https://github.com/fixrb/test_tube/raw/main/img/social-media-preview.png)
10
+
9
11
  ## Installation
10
12
 
11
13
  Add this line to your application's Gemfile:
@@ -28,8 +30,14 @@ gem install test_tube
28
30
 
29
31
  ## Usage
30
32
 
33
+ To make __TestTube__ available:
34
+
35
+ ```ruby
36
+ require "test_tube"
37
+ ```
38
+
31
39
  Assuming we'd like to experiment on the answer to the Ultimate Question of Life,
32
- the Universe, and Everything with the following matcher:
40
+ the Universe, and Everything with the following _matcher_:
33
41
 
34
42
  ```ruby
35
43
  class BeTheAnswer
@@ -39,35 +47,143 @@ class BeTheAnswer
39
47
  end
40
48
  ```
41
49
 
42
- One possibility would be to challenge a whole block of code:
50
+ A _matcher_ is an object that responds to the `matches?` method with a block
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:
43
55
 
44
56
  ```ruby
45
- tt = TestTube.invoke(
46
- -> { "101010".to_i(2) },
57
+ block_of_code = -> { "101010".to_i(2) }
58
+
59
+ experiment = TestTube.invoke(
60
+ block_of_code,
47
61
  isolation: false,
48
62
  matcher: BeTheAnswer.new,
49
63
  negate: false
50
64
  )
51
- # => #<TestTube::Content:0x00007fb3b328b248 @actual=42, @got=true, @error=nil>
52
65
 
53
- tt.actual # => 42
54
- tt.error # => nil
55
- tt.got # => true
66
+ experiment.actual # => 42
67
+ experiment.error # => nil
68
+ experiment.got # => true
56
69
  ```
57
70
 
58
- An alternative would be to challenge a value passed as a parameter:
71
+ An alternative would be to `pass` directly the actual value as a parameter:
59
72
 
60
73
  ```ruby
61
- tt = TestTube.pass(
62
- "101010".to_i(2),
74
+ actual_value = "101010".to_i(2)
75
+
76
+ experiment = TestTube.pass(
77
+ actual_value,
63
78
  matcher: BeTheAnswer.new,
64
79
  negate: false
65
80
  )
66
- # => #<TestTube::Passer:0x00007f85c229c2d8 @actual=42, @got=true>
67
81
 
68
- tt.actual # => 42
69
- tt.error # => nil
70
- tt.got # => true
82
+ experiment.actual # => 42
83
+ experiment.error # => nil
84
+ experiment.got # => true
85
+ ```
86
+
87
+ ### __Matchi__ matchers
88
+ To facilitate the addition of matchers, a collection is available via the
89
+ [__Matchi__ project](https://github.com/fixrb/matchi/).
90
+
91
+ Let's use a built-in __Matchi__ matcher:
92
+
93
+ ```sh
94
+ gem install matchi
95
+ ```
96
+
97
+ ```ruby
98
+ require "matchi"
99
+ ```
100
+
101
+ An example of successful experience:
102
+
103
+ ```ruby
104
+ experiment = TestTube.invoke(
105
+ -> { "foo".blank? },
106
+ isolation: false,
107
+ matcher: Matchi::Matcher::RaiseException.new(NoMethodError),
108
+ negate: false
109
+ )
110
+
111
+ experiment.actual # => #<NoMethodError: undefined method `blank?' for "foo":String>
112
+ experiment.error # => nil
113
+ experiment.got # => true
114
+ ```
115
+
116
+ Another example of an experiment that fails:
117
+
118
+ ```ruby
119
+ experiment = TestTube.invoke(
120
+ -> { 0.1 + 0.2 },
121
+ isolation: false,
122
+ matcher: Matchi::Matcher::Equal.new(0.3),
123
+ negate: false
124
+ )
125
+
126
+ experiment.actual # => 0.30000000000000004
127
+ experiment.error # => nil
128
+ experiment.got # => false
129
+ ```
130
+
131
+ Finally, an experiment which causes an error:
132
+
133
+ ```ruby
134
+ experiment = TestTube.invoke(
135
+ -> { BOOM },
136
+ isolation: false,
137
+ matcher: Matchi::Matcher::Match.new(/^foo$/),
138
+ negate: false
139
+ )
140
+
141
+ experiment.actual # => nil
142
+ experiment.error # => #<NameError: uninitialized constant BOOM>
143
+ experiment.got # => nil
144
+ ```
145
+
146
+ ### Code isolation
147
+
148
+ When experimenting tests, side-effects may occur. Because they may or may not be
149
+ desired, an `isolation` option is available.
150
+
151
+ Let's for instance consider this block of code:
152
+
153
+ ```ruby
154
+ greeting = "Hello, world!"
155
+ block_of_code = -> { greeting.gsub!("world", "Alice") }
156
+ ```
157
+
158
+ By setting the `isolation` option to `true`, we can experiment while avoiding
159
+ side effects:
160
+
161
+ ```ruby
162
+ experiment = TestTube.invoke(
163
+ block_of_code,
164
+ isolation: true,
165
+ matcher: Matchi::Matcher::Eql.new("Hello, Alice!"),
166
+ negate: false
167
+ )
168
+
169
+ experiment.inspect # => <TestTube actual="Hello, Alice!" error=nil got=true>
170
+
171
+ greeting # => "Hello, world!"
172
+ ```
173
+
174
+ Otherwise, we can experiment without any code isolation:
175
+
176
+ ```ruby
177
+ experiment = TestTube.invoke(
178
+ block_of_code,
179
+ isolation: false,
180
+ matcher: Matchi::Matcher::Eql.new("Hello, Alice!"),
181
+ negate: false
182
+ )
183
+
184
+ experiment.inspect # => <TestTube actual="Hello, Alice!" error=nil got=true>
185
+
186
+ greeting # => "Hello, Alice!"
71
187
  ```
72
188
 
73
189
  ## Contact
data/lib/test_tube.rb CHANGED
@@ -11,7 +11,15 @@ module TestTube
11
11
  # @param negate [Boolean] Invert the matcher or not.
12
12
  #
13
13
  # @example
14
- # invoke(
14
+ # require "test_tube"
15
+ #
16
+ # class BeTheAnswer
17
+ # def matches?
18
+ # 42.equal?(yield)
19
+ # end
20
+ # end
21
+ #
22
+ # TestTube.invoke(
15
23
  # -> { "101010".to_i(2) },
16
24
  # isolation: false,
17
25
  # matcher: BeTheAnswer.new,
@@ -28,7 +36,15 @@ module TestTube
28
36
  # @param negate [Boolean] Invert the matcher or not.
29
37
  #
30
38
  # @example
31
- # pass(
39
+ # require "test_tube"
40
+ #
41
+ # class BeTheAnswer
42
+ # def matches?
43
+ # 42.equal?(yield)
44
+ # end
45
+ # end
46
+ #
47
+ # TestTube.pass(
32
48
  # "101010".to_i(2),
33
49
  # matcher: BeTheAnswer.new,
34
50
  # negate: false
@@ -1,15 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TestTube
4
- # The base class.
4
+ # Abstract class representing the state of an experiment.
5
5
  class Base
6
+ # Expectation's actual value.
7
+ #
6
8
  # @return [#object_id] The actual value.
7
9
  attr_reader :actual
8
10
 
9
- # @return [Exception, nil] The raised exception.
11
+ # Expectation's raised error.
12
+ #
13
+ # @return [Exception, nil] The raised error.
10
14
  attr_reader :error
11
15
 
12
- # @return [Boolean, nil] The test result.
16
+ # Expectation's returned boolean value.
17
+ #
18
+ # @return [Boolean, nil] The returned boolean value.
13
19
  attr_reader :got
20
+
21
+ # A string containing a human-readable representation of the experiment.
22
+ #
23
+ # @return [String] The human-readable representation of the experiment.
24
+ def inspect
25
+ "<TestTube actual=#{actual.inspect} error=#{error.inspect} got=#{got.inspect}>"
26
+ end
27
+
28
+ alias to_s inspect
14
29
  end
15
30
  end
@@ -5,9 +5,9 @@ require "defi"
5
5
  require_relative "base"
6
6
 
7
7
  module TestTube
8
- # The invoker class is great for blocks.
8
+ # Evaluate an actual value invoking it with #call method.
9
9
  class Invoker < Base
10
- # Software experiments.
10
+ # Class initializer.
11
11
  #
12
12
  # rubocop:disable Lint/RescueException, Metrics/MethodLength
13
13
  #
@@ -3,9 +3,9 @@
3
3
  require_relative "base"
4
4
 
5
5
  module TestTube
6
- # The passer class is great for values.
6
+ # Evaluate an actual value passed in parameter.
7
7
  class Passer < Base
8
- # Software experiments.
8
+ # Class initializer.
9
9
  #
10
10
  # @param input [#object_id] An actual value to test.
11
11
  # @param matcher [#matches?] A matcher.
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
4
+ version: 1.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-19 00:00:00.000000000 Z
11
+ date: 2021-06-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: defi