test_tube 1.0.0 → 1.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: 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