Jan 27 2008

Synthesis 0.0.2

Synthesis version 0.0.2 was released a bit more than a week ago carrying one prominent new feature: Validation of simulated method call expectations takes into account the types of the arguments of the method's signature.

Let's revisit the example project from the Using Synthesis with Test::Unit and Mocha article and change the save method of the Storage class to take an additional argument - mode.

storage.rb:

class Storage
  def initialize(filename)
    @filename = filename
  end

  def save(val, mode)
    File.open(@filename, mode) {|f| f << val}
  end
end

We should also update the corresponding unit test.

storage_test.rb:

require "test/unit"
require "fileutils"
require File.dirname(__FILE__) + "/../lib/synthesis_example"

class StorageTest < Test::Unit::TestCase
  def test_saves_to_file
    Storage.new('test.txt').save('rock', 'w')
    assert_equal 'rock', File.read('test.txt')
  ensure
    FileUtils.rm_f('test.txt')
  end
end

Running storage_test.rb produces:

TW-MacBook-Pro:synthesis_example gmalamid$ ruby test/storage_test.rb
Loaded suite test/storage_test
Started
.
Finished in 0.004917 seconds.

1 tests, 1 assertions, 0 failures, 0 errors

Also, data_brander_test.rb still passes.

TW-MacBook-Pro:synthesis_example gmalamid$ ruby test/data_brander_test.rb
Loaded suite test/data_brander_test
Started
.
Finished in 0.00047 seconds.

1 tests, 1 assertions, 0 failures, 0 errors

At this point, all of the application's tests are producing a green build, although there is an obvious bug. The signature of Storage#save has changed, so DataBrander#save_branded is broken.

With version 0.0.2, Synthesis attempts to address this issue and, indeed, invoking the test:synthesis task produces:

W-MacBook-Pro:synthesis_example gmalamid$ rake
(in /Users/gmalamid/devel/ruby/whatever_code/synthesis_example)
[Synthesis] Collecting expectations...
Loaded suite /usr/bin/rake
Started
..
Finished in 0.002701 seconds.

2 tests, 2 assertions, 0 failures, 0 errors
[Synthesis] Verifying expectation invocations...
Loaded suite /usr/bin/rake
Started
..
Finished in 0.002483 seconds.

2 tests, 2 assertions, 0 failures, 0 errors
[Synthesis]
[Synthesis] Tested Expectations:
[Synthesis]
[Synthesis] Untested Expectations:
[Synthesis] Storage.new.save(String)
[Synthesis]
[Synthesis] Ignoring:
[Synthesis]
[Synthesis] FAILED.

In order for the Synthesis task to be successful we need to update data_brander.rb and the corresponding test to correctly cover the interaction between DataBrander and Storage, and, conveniently, fix the associated bug.

data_brander.rb:

class DataBrander
  BRAND = "METAL"

  def initialize(storage)
    @storage = storage
  end

  def save_branded(data)
    @storage.save "#{BRAND} - #{data}", "w"
  end
end

data_brander_test.rb:

%w(test/unit rubygems mocha).each { |l| require l }
require File.dirname(__FILE__) + "/../lib/synthesis_example"

class DataBranderTest << Test::Unit::TestCase
  def test_saves_branded_to_storage
    storage = Storage.new 'whatever'
    storage.expects(:save).with('METAL - rock', 'w')
    DataBrander.new(storage).save_branded 'rock'
  end
end

This will hopefully tip the confidence scale a bit closer to the point where we feel it's safe enough to omit having to write some functional tests that would prove the interacting members will integrate nicely when used together.

TW-MacBook-Pro:synthesis_example gmalamid$ rake
(in /Users/gmalamid/devel/ruby/whatever_code/synthesis_example)
[Synthesis] Collecting expectations...
Loaded suite /usr/bin/rake
Started
..
Finished in 0.002724 seconds.

2 tests, 2 assertions, 0 failures, 0 errors
[Synthesis] Verifying expectation invocations...
Loaded suite /usr/bin/rake
Started
..
Finished in 0.002409 seconds.

2 tests, 2 assertions, 0 failures, 0 errors
[Synthesis]
[Synthesis] SUCCESS.