Test Tube

Version Yard documentation CI RuboCop License

A test tube to conduct software experiments 🧪

A researcher experimenting with Ruby code

Installation

Add this line to your application's Gemfile:

gem "test_tube"

And then execute:

bundle

Or install it yourself as:

gem install test_tube

Usage

To make TestTube available:

require "test_tube"

Assuming we'd like to experiment on the answer to the Ultimate Question of Life, the Universe, and Everything with the following matcher:

class BeTheAnswer
  def matches?
    42.equal?(yield)
  end
end

A matcher is an object that responds to the matches? method with a block parameter representing the actual value to be compared.

Back to our Ruby experiments, one possibility would be to invoke a whole block of code:

block_of_code = -> { "101010".to_i(2) }

experiment = TestTube.invoke(isolate: false, matcher: BeTheAnswer.new, negate: false, &block_of_code)
# => <TestTube actual=42 error=nil got=true>

experiment.actual # => 42
experiment.error  # => nil
experiment.got    # => true

An alternative would be to pass directly the actual value as a parameter:

actual_value = "101010".to_i(2)

experiment = TestTube.pass(actual_value, matcher: BeTheAnswer.new, negate: false)
# => <TestTube actual=42 error=nil got=true>

experiment.actual # => 42
experiment.error  # => nil
experiment.got    # => true

Matchi matchers

To facilitate the addition of matchers, a collection is available via the Matchi project.

Let's use a built-in Matchi matcher:

gem install matchi
require "matchi"

An example of successful experience:

experiment = TestTube.invoke(
  isolate: false,
  matcher: Matchi::Matcher::RaiseException.new(NoMethodError),
  negate:  false
) { "foo".blank? }
# => <TestTube actual=#<NoMethodError: undefined method `blank?' for "foo":String> error=nil got=true>

experiment.actual # => #<NoMethodError: undefined method `blank?' for "foo":String>
experiment.error  # => nil
experiment.got    # => true

Another example of an experiment that fails:

experiment = TestTube.invoke(
  isolate: false,
  matcher: Matchi::Matcher::Equal.new(0.3),
  negate:  false,
  &-> { 0.1 + 0.2 }
) # => <TestTube actual=0.30000000000000004 error=nil got=false>

experiment.actual # => 0.30000000000000004
experiment.error  # => nil
experiment.got    # => false

Finally, an experiment which causes an error:

experiment = TestTube.invoke(
  isolate: false,
  matcher: Matchi::Matcher::Match.new(/^foo$/),
  negate:  false
) { BOOM }
# => <TestTube actual=nil error=#<NameError: uninitialized constant BOOM> got=nil>

experiment.actual # => nil
experiment.error  # => #<NameError: uninitialized constant BOOM>
experiment.got    # => nil

Code isolation

When experimenting tests, side-effects may occur. Because they may or may not be desired, an isolate option is available.

Let's for instance consider this block of code:

greeting = "Hello, world!"
block_of_code = -> { greeting.gsub!("world", "Alice") } # => #<Proc:0x00007f87f71b9690 (irb):42 (lambda)>

By setting the isolate option to true, we can experiment while avoiding side effects:

experiment = TestTube.invoke(
  isolate: true,
  matcher: Matchi::Matcher::Eql.new("Hello, Alice!"),
  negate:  false,
  &block_of_code
) # => <TestTube actual="Hello, Alice!" error=nil got=true>

greeting # => "Hello, world!"

Otherwise, we can experiment without any code isolation:

experiment = TestTube.invoke(
  isolate: false,
  matcher: Matchi::Matcher::Eql.new("Hello, Alice!"),
  negate:  false,
  &block_of_code
) # => <TestTube actual="Hello, Alice!" error=nil got=true>

greeting # => "Hello, Alice!"

Contact

Versioning

Test Tube follows Semantic Versioning 2.0.

License

The gem is available as open source under the terms of the MIT License.


This project is sponsored by:
Sashite