previous next

DescriptionResult of running test

This is a Warts and All demonstration of Test Driven Development of a Universal Unit Test. It was not pair programmed. Each snapshot is taken at "it compiles & runs without warnings" stage.

The green lines of code is the text added since the last snapshot, the blue strikeout text is the text deleted.

The red/green box just to the right of this text is the result of running the unit test for the UUT. green == passed, red === failed.

Start off with a generic TDD stub.

The mystik...

if $0 == __FILE__ then

...line is a standard ruby idiom, it you run _this_ file, it assumes you are wanting to run the unit test for the class declared in this file. If you use this module in some other file, the part after that line won't be run. ie. You could pull the UUT class into the unit test of another class.

Loaded suite uut Started . Finished in 0.000535 seconds. 1 tests, 0 assertions, 0 failures, 0 errors

#Generic TDD stub class UUT def initialize() end end if $0 == __FILE__ then require 'test/unit' class TC_Uut < Test::Unit::TestCase def test_uut end end end

previous next

Start with minimal Universal Unit Test, that does nothing, and hence no surprise, it passes! Loaded suite uut Started . Finished in 0.000561 seconds. 1 tests, 0 assertions, 0 failures, 0 errors

#Generic TDD stub
class UUT
  def initialize()
  end
  def test( forward, backward)
  end
end
class TransformationTrait
end
if $0 == __FILE__ then
  require 'test/unit'
  class TC_Uut < Test::Unit::TestCase
    def test_uut
      uut = UUT.new
      # Either the transformation objects need to be quite smart,
      # or we must pass in a Trait class that knows stuff.
      
      # I like the idea of a Trait class.
      forward = TransformationTrait.new
      backward = TransformationTrait.new
      uut.test( forward, backward)
    end
  end
end

previous next

Make our UUT try every sample, transform it forward and back, and compare.

Since the transform does nothing, and the fuzzy_equal? always returns true, the test passes.

Loaded suite uut Started . Finished in 0.001193 seconds. 1 tests, 0 assertions, 0 failures, 0 errors

class TransformationTrait
  def sample_generator
    (1..100)
  end
  def transform( sample)
    sample
  end
  def fuzzy_equal?( a, b)
    true
  end
end
class UUT
  def initialize()
  end
  def test( forward, backward)
    samples = forward.sample_generator
    samples.each do |sample|
      transformed_sample = forward.transform( sample)
      reverted_sample = backward.transform( transformed_sample)
      raise "Forward and back sample differ" unless forward.fuzzy_equal?( sample, reverted_sample)
  end
end
class TransformationTrait
end
if $0 == __FILE__ then
  require 'test/unit'
  class TC_Uut < Test::Unit::TestCase
    def test_uut
      uut = UUT.new
      # Either the transformation objects need to be quite smart,
      # or we must pass in a Trait class that knows stuff.
      
      # I like the idea of a Trait class.
      forward = TransformationTrait.new
      backward = TransformationTrait.new
      uut.test( forward, backward)
    end
  end
end

previous next

Add a new transform trait that squares the input samples.

Since the fuzzy equals is still stupid, it still passes.

Loaded suite uut Started . Finished in 0.00125 seconds. 1 tests, 0 assertions, 0 failures, 0 errors

class TransformationTrait
  def sample_generator
    (1..100)
  end
  def transform( sample)
    sample
  end
  def fuzzy_equal?( a, b)
    true
  end
end
class UUT
  def initialize()
  end
  def test( forward, backward)
    samples = forward.sample_generator
    samples.each do |sample|
      transformed_sample = forward.transform( sample)
      reverted_sample = backward.transform( transformed_sample)
      raise "Forward and back sample differ" unless forward.fuzzy_equal?( sample, reverted_sample)
    end
  end
end
if $0 == __FILE__ then
  require 'test/unit'
  class SqrTrait < TransformationTrait
    def transform( sample)
      sample * sample
    end
  end
  class TC_Uut < Test::Unit::TestCase
    def test_uut
      uut = UUT.new
      # Either the transformation objects need to be quite smart,
      # or we must pass in a Trait class that knows stuff.
      
      # I like the idea of a Trait class.
      forward = TransformationTrait.new
      forward = SqrTrait.new
      backward = TransformationTrait.new
      uut.test( forward, backward)
    end
  end
end

previous next

Make fuzzy_equal? more real, so test fails. (I actually chose the wrong variety of equal? here. A pair programmer may have pick that up. However, the bug shows up later and I fix it later.) Loaded suite uut Started E Finished in 0.00077 seconds. 1) Error: test_uut(TC_Uut): RuntimeError: Forward and back sample differ uut.rb:26:in `test' uut.rb:23:in `each' uut.rb:23:in `test' uut.rb:50:in `test_uut' 1 tests, 0 assertions, 0 failures, 1 errors

class TransformationTrait
  def sample_generator
    (1..100)
  end
  def transform( sample)
    sample
  end
  def fuzzy_equal?( a, b)
    true
    a.equal?( b)
  end
end
class UUT
  def initialize()
  end
  def test( forward, backward)
    samples = forward.sample_generator
    samples.each do |sample|
      transformed_sample = forward.transform( sample)
      reverted_sample = backward.transform( transformed_sample)
      raise "Forward and back sample differ" unless forward.fuzzy_equal?( sample, reverted_sample)
    end
  end
end
if $0 == __FILE__ then
  require 'test/unit'
  class SqrTrait < TransformationTrait
    def transform( sample)
      sample * sample
    end
  end
  class TC_Uut < Test::Unit::TestCase
    def test_uut
      uut = UUT.new
      # Either the transformation objects need to be quite smart,
      # or we must pass in a Trait class that knows stuff.
      
      # I like the idea of a Trait class.
      forward = SqrTrait.new
      backward = TransformationTrait.new
      uut.test( forward, backward)
    end
  end
end

previous next

Added the simplest and stupidest implementation of sqrt. (return 2) Throw an input range exception if the input value is not 4. Loaded suite uut Started uut.rb:47:in `transform': InputRangeExceptionValue '1' out of input range for transformation (InputRangeException) from uut.rb:35:in `test' from uut.rb:34:in `each' from uut.rb:34:in `test' from uut.rb:68:in `test_uut' from /usr/lib/ruby/1.8/test/unit/testcase.rb:70:in `__send__' from /usr/lib/ruby/1.8/test/unit/testcase.rb:70:in `run' from /usr/lib/ruby/1.8/test/unit/testsuite.rb:32:in `run' from /usr/lib/ruby/1.8/test/unit/testsuite.rb:31:in `each' ... 8 levels... from /usr/lib/ruby/1.8/test/unit/autorunner.rb:200:in `run' from /usr/lib/ruby/1.8/test/unit/autorunner.rb:13:in `run' from /usr/lib/ruby/1.8/test/unit.rb:285 from /usr/lib/ruby/1.8/test/unit.rb:283

class InputRangeException < Exception
  def initialize( value)
    @value = value
  end
  def to_s
    super + "Value '#{@value}' out of input range for transformation"
  end
end
class TransformationTrait
  def sample_generator
    (1..100)
  end
  def transform( sample)
    sample
  end
  def fuzzy_equal?( a, b)
    a.equal?( b)
  end
end
class UUT
  def initialize()
  end
  def test( forward, backward)
    samples = forward.sample_generator
    samples.each do |sample|
      transformed_sample = forward.transform( sample)
      reverted_sample = backward.transform( transformed_sample)
      raise "Forward and back sample differ" unless forward.fuzzy_equal?( sample, reverted_sample)
    end
  end
end
if $0 == __FILE__ then
  require 'test/unit'
  class SqrTrait < TransformationTrait
    def transform( sample)
      raise InputRangeException.new( sample) unless sample == 4
      sample * sample
    end
  end
  class SqrtTrait < TransformationTrait
    def transform( sample)
      2
    end
  end
  class TC_Uut < Test::Unit::TestCase
    def test_uut
      uut = UUT.new
      # Either the transformation objects need to be quite smart,
      # or we must pass in a Trait class that knows stuff.
      
      # I like the idea of a Trait class.
      forward = SqrTrait.new
      backward = TransformationTrait.new
      uut.test( forward, backward)
    end
  end
end

previous next

So the forward transform throws an input range error. Good. It should be able to. Ignore it and drop it on the floor.

Now we're past that and it the fuzzy compare is whinging. (But we don't know why...)

Loaded suite uut Started E Finished in 0.000803 seconds. 1) Error: test_uut(TC_Uut): RuntimeError: Forward and back sample differ uut.rb:42:in `test' uut.rb:34:in `each' uut.rb:34:in `test' uut.rb:73:in `test_uut' 1 tests, 0 assertions, 0 failures, 1 errors

class InputRangeException < Exception
  def initialize( value)
    @value = value
  end
  def to_s
    super + "Value '#{@value}' out of input range for transformation"
  end
end
class TransformationTrait
  def sample_generator
    (1..100)
  end
  def transform( sample)
    sample
  end
  def fuzzy_equal?( a, b)
    a.equal?( b)
  end
end
class UUT
  def initialize()
  end
  def test( forward, backward)
    samples = forward.sample_generator
    samples.each do |sample|
      begin
      transformed_sample = forward.transform( sample)
      rescue InputRangeException
        # Ignore input range exceptions here...
      end
      reverted_sample = backward.transform( transformed_sample)
      raise "Forward and back sample differ" unless forward.fuzzy_equal?( sample, reverted_sample)
    end
  end
end
if $0 == __FILE__ then
  require 'test/unit'
  class SqrTrait < TransformationTrait
    def transform( sample)
      raise InputRangeException.new( sample) unless sample == 4
      sample * sample
    end
  end
  class SqrtTrait < TransformationTrait
    def transform( sample)
      2
    end
  end
  class TC_Uut < Test::Unit::TestCase
    def test_uut
      uut = UUT.new
      # Either the transformation objects need to be quite smart,
      # or we must pass in a Trait class that knows stuff.
      
      # I like the idea of a Trait class.
      forward = SqrTrait.new
      backward = TransformationTrait.new
      uut.test( forward, backward)
    end
  end
end

previous next

So print out a bit more info...Loaded suite uut Started E Finished in 0.000804 seconds. 1) Error: test_uut(TC_Uut): RuntimeError: Forward '1' and back '' sample differ uut.rb:42:in `test' uut.rb:34:in `each' uut.rb:34:in `test' uut.rb:73:in `test_uut' 1 tests, 0 assertions, 0 failures, 1 errors

class InputRangeException < Exception
  def initialize( value)
    @value = value
  end
  def to_s
    super + "Value '#{@value}' out of input range for transformation"
  end
end
class TransformationTrait
  def sample_generator
    (1..100)
  end
  def transform( sample)
    sample
  end
  def fuzzy_equal?( a, b)
    a.equal?( b)
  end
end
class UUT
  def initialize()
  end
  def test( forward, backward)
    samples = forward.sample_generator
    samples.each do |sample|
      begin
        transformed_sample = forward.transform( sample)
      rescue InputRangeException
        # Ignore input range exceptions here...
      end
      reverted_sample = backward.transform( transformed_sample)
      raise "Forward and back sample differ" unless forward.fuzzy_equal?( sample, reverted_sample)
      raise "Forward '#{sample}' and back '#{reverted_sample}' sample differ" unless forward.fuzzy_equal?( sample, reverted_sample)
    end
  end
end
if $0 == __FILE__ then
  require 'test/unit'
  class SqrTrait < TransformationTrait
    def transform( sample)
      raise InputRangeException.new( sample) unless sample == 4
      sample * sample
    end
  end
  class SqrtTrait < TransformationTrait
    def transform( sample)
      2
    end
  end
  class TC_Uut < Test::Unit::TestCase
    def test_uut
      uut = UUT.new
      # Either the transformation objects need to be quite smart,
      # or we must pass in a Trait class that knows stuff.
      
      # I like the idea of a Trait class.
      forward = SqrTrait.new
      backward = TransformationTrait.new
      uut.test( forward, backward)
    end
  end
end

previous next

Ah! That rescue is in the wrong place. Drat! Still wrong...Loaded suite uut Started E Finished in 0.000915 seconds. 1) Error: test_uut(TC_Uut): RuntimeError: Forward '4' and back '16' sample differ uut.rb:38:in `test' uut.rb:34:in `each' uut.rb:34:in `test' uut.rb:72:in `test_uut' 1 tests, 0 assertions, 0 failures, 1 errors

class InputRangeException < Exception
  def initialize( value)
    @value = value
  end
  def to_s
    super + "Value '#{@value}' out of input range for transformation"
  end
end
class TransformationTrait
  def sample_generator
    (1..100)
  end
  def transform( sample)
    sample
  end
  def fuzzy_equal?( a, b)
    a.equal?( b)
  end
end
class UUT
  def initialize()
  end
  def test( forward, backward)
    samples = forward.sample_generator
    samples.each do |sample|
      begin
        transformed_sample = forward.transform( sample)
        reverted_sample = backward.transform( transformed_sample)
        raise "Forward '#{sample}' and back '#{reverted_sample}' sample differ" unless forward.fuzzy_equal?( sample, reverted_sample)
      rescue InputRangeException
        # Ignore input range exceptions here...
      end
      reverted_sample = backward.transform( transformed_sample)
      raise "Forward '#{sample}' and back '#{reverted_sample}' sample differ" unless forward.fuzzy_equal?( sample, reverted_sample)
    end
  end
end
if $0 == __FILE__ then
  require 'test/unit'
  class SqrTrait < TransformationTrait
    def transform( sample)
      raise InputRangeException.new( sample) unless sample == 4
      sample * sample
    end
  end
  class SqrtTrait < TransformationTrait
    def transform( sample)
      2
    end
  end
  class TC_Uut < Test::Unit::TestCase
    def test_uut
      uut = UUT.new
      # Either the transformation objects need to be quite smart,
      # or we must pass in a Trait class that knows stuff.
      
      # I like the idea of a Trait class.
      forward = SqrTrait.new
      backward = TransformationTrait.new
      uut.test( forward, backward)
    end
  end
end

previous next

It helps if you actually use the SqrtTrait you defined. :-) Still wrong, but I can see what is wrong..Loaded suite uut Started E Finished in 0.001878 seconds. 1) Error: test_uut(TC_Uut): RuntimeError: Forward '4' and back '2' sample differ uut.rb:38:in `test' uut.rb:34:in `each' uut.rb:34:in `test' uut.rb:72:in `test_uut' 1 tests, 0 assertions, 0 failures, 1 errors

class InputRangeException < Exception
  def initialize( value)
    @value = value
  end
  def to_s
    super + "Value '#{@value}' out of input range for transformation"
  end
end
class TransformationTrait
  def sample_generator
    (1..100)
  end
  def transform( sample)
    sample
  end
  def fuzzy_equal?( a, b)
    a.equal?( b)
  end
end
class UUT
  def initialize()
  end
  def test( forward, backward)
    samples = forward.sample_generator
    samples.each do |sample|
      begin
        transformed_sample = forward.transform( sample)
        reverted_sample = backward.transform( transformed_sample)
        raise "Forward '#{sample}' and back '#{reverted_sample}' sample differ" unless forward.fuzzy_equal?( sample, reverted_sample)
      rescue InputRangeException
        # Ignore input range exceptions here...
      end
    end
  end
end
if $0 == __FILE__ then
  require 'test/unit'
  class SqrTrait < TransformationTrait
    def transform( sample)
      raise InputRangeException.new( sample) unless sample == 4
      sample * sample
    end
  end
  class SqrtTrait < TransformationTrait
    def transform( sample)
      2
    end
  end
  class TC_Uut < Test::Unit::TestCase
    def test_uut
      uut = UUT.new
      # Either the transformation objects need to be quite smart,
      # or we must pass in a Trait class that knows stuff.
      
      # I like the idea of a Trait class.
      forward = SqrTrait.new
      backward = TransformationTrait.new
      backward = SqrtTrait.new
      uut.test( forward, backward)
    end
  end
end

previous next

Should have used 2 not 4. Pair programming would be a Good idea...Loaded suite uut Started . Finished in 0.004899 seconds. 1 tests, 0 assertions, 0 failures, 0 errors

class InputRangeException < Exception
  def initialize( value)
    @value = value
  end
  def to_s
    super + "Value '#{@value}' out of input range for transformation"
  end
end
class TransformationTrait
  def sample_generator
    (1..100)
  end
  def transform( sample)
    sample
  end
  def fuzzy_equal?( a, b)
    a.equal?( b)
  end
end
class UUT
  def initialize()
  end
  def test( forward, backward)
    samples = forward.sample_generator
    samples.each do |sample|
      begin
        transformed_sample = forward.transform( sample)
        reverted_sample = backward.transform( transformed_sample)
        raise "Forward '#{sample}' and back '#{reverted_sample}' sample differ" unless forward.fuzzy_equal?( sample, reverted_sample)
      rescue InputRangeException
        # Ignore input range exceptions here...
      end
    end
  end
end
if $0 == __FILE__ then
  require 'test/unit'
  class SqrTrait < TransformationTrait
    def transform( sample)
      raise InputRangeException.new( sample) unless sample == 4
      raise InputRangeException.new( sample) unless sample == 2
      sample * sample
    end
  end
  class SqrtTrait < TransformationTrait
    def transform( sample)
      2
    end
  end
  class TC_Uut < Test::Unit::TestCase
    def test_uut
      uut = UUT.new
      # Either the transformation objects need to be quite smart,
      # or we must pass in a Trait class that knows stuff.
      
      # I like the idea of a Trait class.
      forward = SqrTrait.new
      backward = SqrtTrait.new
      uut.test( forward, backward)
    end
  end
end

previous next

So that was a really dumb implementation of sqrt, called it so instead of deleting it. (I'm sort of attached to it, I hate to lose code that sort of does something sane.) Loaded suite uut Started . Finished in 0.00513 seconds. 1 tests, 0 assertions, 0 failures, 0 errors

class InputRangeException < Exception
  def initialize( value)
    @value = value
  end
  def to_s
    super + "Value '#{@value}' out of input range for transformation"
  end
end
class TransformationTrait
  def sample_generator
    (1..100)
  end
  def transform( sample)
    sample
  end
  def fuzzy_equal?( a, b)
    a.equal?( b)
  end
end
class UUT
  def initialize()
  end
  def test( forward, backward)
    samples = forward.sample_generator
    samples.each do |sample|
      begin
        transformed_sample = forward.transform( sample)
        reverted_sample = backward.transform( transformed_sample)
        raise "Forward '#{sample}' and back '#{reverted_sample}' sample differ" unless forward.fuzzy_equal?( sample, reverted_sample)
      rescue InputRangeException
        # Ignore input range exceptions here...
      end
    end
  end
end
if $0 == __FILE__ then
  require 'test/unit'
  class SqrTrait < TransformationTrait
    def transform( sample)
      raise InputRangeException.new( sample) unless sample == 2
      sample * sample
    end
  end
  class SqrtTrait < TransformationTrait
  class DumbSqrtTrait < TransformationTrait
    def transform( sample)
      2
    end
  end
  class TC_Uut < Test::Unit::TestCase
    def test_uut
      uut = UUT.new
      # Either the transformation objects need to be quite smart,
      # or we must pass in a Trait class that knows stuff.
      
      # I like the idea of a Trait class.
      forward = SqrTrait.new
      backward = SqrtTrait.new
      backward = DumbSqrtTrait.new
      uut.test( forward, backward)
    end
  end
end

previous next

Wake up!

Important step here! Which way is forward and which is back? Who cares, it should work both ways. Oh dear! It doesn't.

Loaded suite uut Started E Finished in 0.00518 seconds. 1) Error: test_uut(TC_Uut): RuntimeError: Forward '1' and back '4' sample differ uut.rb:38:in `one_way_test' uut.rb:34:in `each' uut.rb:34:in `one_way_test' uut.rb:47:in `test' uut.rb:77:in `test_uut' 1 tests, 0 assertions, 0 failures, 1 errors

class InputRangeException < Exception
  def initialize( value)
    @value = value
  end
  def to_s
    super + "Value '#{@value}' out of input range for transformation"
  end
end
class TransformationTrait
  def sample_generator
    (1..100)
  end
  def transform( sample)
    sample
  end
  def fuzzy_equal?( a, b)
    a.equal?( b)
  end
end
class UUT
  def initialize()
  end
  def test( forward, backward)
  def one_way_test( forward, backward)
    samples = forward.sample_generator
    samples.each do |sample|
      begin
        transformed_sample = forward.transform( sample)
        reverted_sample = backward.transform( transformed_sample)
        raise "Forward '#{sample}' and back '#{reverted_sample}' sample differ" unless forward.fuzzy_equal?( sample, reverted_sample)
      rescue InputRangeException
        # Ignore input range exceptions here...
      end
    end
  end
  def test( forward, backward)
    one_way_test( forward, backward)
    one_way_test( backward, forward)
  end
end
if $0 == __FILE__ then
  require 'test/unit'
  class SqrTrait < TransformationTrait
    def transform( sample)
      raise InputRangeException.new( sample) unless sample == 2
      sample * sample
    end
  end
  class DumbSqrtTrait < TransformationTrait
    def transform( sample)
      2
    end
  end
  class TC_Uut < Test::Unit::TestCase
    def test_uut
      uut = UUT.new
      # Either the transformation objects need to be quite smart,
      # or we must pass in a Trait class that knows stuff.
      
      # I like the idea of a Trait class.
      forward = SqrTrait.new
      backward = DumbSqrtTrait.new
      uut.test( forward, backward)
    end
  end
end

previous next

Just need to constrain the range for this dumbsqrt thing again.Loaded suite uut Started . Finished in 0.009366 seconds. 1 tests, 0 assertions, 0 failures, 0 errors

class InputRangeException < Exception
  def initialize( value)
    @value = value
  end
  def to_s
    super + "Value '#{@value}' out of input range for transformation"
  end
end
class TransformationTrait
  def sample_generator
    (1..100)
  end
  def transform( sample)
    sample
  end
  def fuzzy_equal?( a, b)
    a.equal?( b)
  end
end
class UUT
  def initialize()
  end
  def one_way_test( forward, backward)
    samples = forward.sample_generator
    samples.each do |sample|
      begin
        transformed_sample = forward.transform( sample)
        reverted_sample = backward.transform( transformed_sample)
        raise "Forward '#{sample}' and back '#{reverted_sample}' sample differ" unless forward.fuzzy_equal?( sample, reverted_sample)
      rescue InputRangeException
        # Ignore input range exceptions here...
      end
    end
  end
  def test( forward, backward)
    one_way_test( forward, backward)
    one_way_test( backward, forward)
  end
end
if $0 == __FILE__ then
  require 'test/unit'
  class SqrTrait < TransformationTrait
    def transform( sample)
      raise InputRangeException.new( sample) unless sample == 2
      sample * sample
    end
  end
  class DumbSqrtTrait < TransformationTrait
    def transform( sample)
      raise InputRangeException.new( sample) unless sample == 4
      2
    end
  end
  class TC_Uut < Test::Unit::TestCase
    def test_uut
      uut = UUT.new
      # Either the transformation objects need to be quite smart,
      # or we must pass in a Trait class that knows stuff.
      
      # I like the idea of a Trait class.
      forward = SqrTrait.new
      backward = DumbSqrtTrait.new
      uut.test( forward, backward)
    end
  end
end

previous next

Hmm. But that constraint is really a property of the dumb implementation of sqrt, it shouldn't even be on the SqrTrait.

Ooh. I didn't need it...

Loaded suite uut Started . Finished in 0.010423 seconds. 1 tests, 0 assertions, 0 failures, 0 errors

class InputRangeException < Exception
  def initialize( value)
    @value = value
  end
  def to_s
    super + "Value '#{@value}' out of input range for transformation"
  end
end
class TransformationTrait
  def sample_generator
    (1..100)
  end
  def transform( sample)
    sample
  end
  def fuzzy_equal?( a, b)
    a.equal?( b)
  end
end
class UUT
  def initialize()
  end
  def one_way_test( forward, backward)
    samples = forward.sample_generator
    samples.each do |sample|
      begin
        transformed_sample = forward.transform( sample)
        reverted_sample = backward.transform( transformed_sample)
        raise "Forward '#{sample}' and back '#{reverted_sample}' sample differ" unless forward.fuzzy_equal?( sample, reverted_sample)
      rescue InputRangeException
        # Ignore input range exceptions here...
      end
    end
  end
  def test( forward, backward)
    one_way_test( forward, backward)
    one_way_test( backward, forward)
  end
end
if $0 == __FILE__ then
  require 'test/unit'
  class SqrTrait < TransformationTrait
    def transform( sample)
      raise InputRangeException.new( sample) unless sample == 2
      sample * sample
    end
  end
  class DumbSqrtTrait < TransformationTrait
    def transform( sample)
      raise InputRangeException.new( sample) unless sample == 4
      2
    end
  end
  class TC_Uut < Test::Unit::TestCase
    def test_uut
      uut = UUT.new
      # Either the transformation objects need to be quite smart,
      # or we must pass in a Trait class that knows stuff.
      
      # I like the idea of a Trait class.
      forward = SqrTrait.new
      backward = DumbSqrtTrait.new
      uut.test( forward, backward)
    end
  end
end

previous next

That's because I have the rescue over the forward and back transformation. It should only be over the forward. ie. The output of the the forward transform should always be OK for the input of the backward.

That's better, the test breaks again.

Loaded suite uut Started E Finished in 0.000815 seconds. 1) Error: test_uut(TC_Uut): RuntimeError: Unit Test failure, output range of transformation larger than input range of reverse - 'InputRangeExceptionValue '1' out of input range for transformation' uut.rb:42:in `one_way_test' uut.rb:34:in `each' uut.rb:34:in `one_way_test' uut.rb:54:in `test' uut.rb:85:in `test_uut' 1 tests, 0 assertions, 0 failures, 1 errors

class InputRangeException < Exception
  def initialize( value)
    @value = value
  end
  def to_s
    super + "Value '#{@value}' out of input range for transformation"
  end
end
class TransformationTrait
  def sample_generator
    (1..100)
  end
  def transform( sample)
    sample
  end
  def fuzzy_equal?( a, b)
    a.equal?( b)
  end
end
class UUT
  def initialize()
  end
  def one_way_test( forward, backward)
    samples = forward.sample_generator
    samples.each do |sample|
      begin
        transformed_sample = forward.transform( sample)
        begin
        reverted_sample = backward.transform( transformed_sample)
        raise "Forward '#{sample}' and back '#{reverted_sample}' sample differ" unless forward.fuzzy_equal?( sample, reverted_sample)
        rescue InputRangeException => details
          raise "Unit Test failure, output range of transformation larger
than input range of reverse - '#{details}'"
        end
        raise "Forward '#{sample}' and back '#{reverted_sample}' sample differ" unless
          forward.fuzzy_equal?( sample, reverted_sample)
      rescue InputRangeException
        # Ignore input range exceptions here...
      end
    end
  end
  def test( forward, backward)
    one_way_test( forward, backward)
    one_way_test( backward, forward)
  end
end
if $0 == __FILE__ then
  require 'test/unit'
  class SqrTrait < TransformationTrait
    def transform( sample)
      sample * sample
    end
  end
  class DumbSqrtTrait < TransformationTrait
    def transform( sample)
      raise InputRangeException.new( sample) unless sample == 4
      2
    end
  end
  class TC_Uut < Test::Unit::TestCase
    def test_uut
      uut = UUT.new
      # Either the transformation objects need to be quite smart,
      # or we must pass in a Trait class that knows stuff.
      
      # I like the idea of a Trait class.
      forward = SqrTrait.new
      backward = DumbSqrtTrait.new
      uut.test( forward, backward)
    end
  end
end

previous next

Make sample_generator and attribute of the trait so I can tamper with it. (test still broken - good.)Loaded suite uut Started E Finished in 0.000824 seconds. 1) Error: test_uut(TC_Uut): RuntimeError: Unit Test failure, output range of transformation larger than input range of reverse - 'InputRangeExceptionValue '1' out of input range for transformation' uut.rb:44:in `one_way_test' uut.rb:36:in `each' uut.rb:36:in `one_way_test' uut.rb:56:in `test' uut.rb:87:in `test_uut' 1 tests, 0 assertions, 0 failures, 1 errors

class InputRangeException < Exception
  def initialize( value)
    @value = value
  end
  def to_s
    super + "Value '#{@value}' out of input range for transformation"
  end
end
class TransformationTrait
  def sample_generator
    (1..100)
  attr_accessor :sample_generator
  def initialize
    @sample_generator = (1..100)
  end
  def transform( sample)
    sample
  end
  def fuzzy_equal?( a, b)
    a.equal?( b)
  end
end
class UUT
  def initialize()
  end
  def one_way_test( forward, backward)
    samples = forward.sample_generator
    samples.each do |sample|
      begin
        transformed_sample = forward.transform( sample)
        begin
          reverted_sample = backward.transform( transformed_sample)
        rescue InputRangeException => details
          raise "Unit Test failure, output range of transformation larger
than input range of reverse - '#{details}'"
        end
        raise "Forward '#{sample}' and back '#{reverted_sample}' sample differ" unless
          forward.fuzzy_equal?( sample, reverted_sample)
      rescue InputRangeException
        # Ignore input range exceptions here...
      end
    end
  end
  def test( forward, backward)
    one_way_test( forward, backward)
    one_way_test( backward, forward)
  end
end
if $0 == __FILE__ then
  require 'test/unit'
  class SqrTrait < TransformationTrait
    def transform( sample)
      sample * sample
    end
  end
  class DumbSqrtTrait < TransformationTrait
    def transform( sample)
      raise InputRangeException.new( sample) unless sample == 4
      2
    end
  end
  class TC_Uut < Test::Unit::TestCase
    def test_uut
      uut = UUT.new
      # Either the transformation objects need to be quite smart,
      # or we must pass in a Trait class that knows stuff.
      
      # I like the idea of a Trait class.
      forward = SqrTrait.new
      backward = DumbSqrtTrait.new
      uut.test( forward, backward)
    end
  end
end

previous next

Constrain the forward sample generator to generate just one and only one sample. 2.Loaded suite uut Started . Finished in 0.00503 seconds. 1 tests, 0 assertions, 0 failures, 0 errors

class InputRangeException < Exception
  def initialize( value)
    @value = value
  end
  def to_s
    super + "Value '#{@value}' out of input range for transformation"
  end
end
class TransformationTrait
  attr_accessor :sample_generator
  def initialize
    @sample_generator = (1..100)
  end
  def transform( sample)
    sample
  end
  def fuzzy_equal?( a, b)
    a.equal?( b)
  end
end
class UUT
  def initialize()
  end
  def one_way_test( forward, backward)
    samples = forward.sample_generator
    samples.each do |sample|
      begin
        transformed_sample = forward.transform( sample)
        begin
          reverted_sample = backward.transform( transformed_sample)
        rescue InputRangeException => details
          raise "Unit Test failure, output range of transformation larger
than input range of reverse - '#{details}'"
        end
        raise "Forward '#{sample}' and back '#{reverted_sample}' sample differ" unless
          forward.fuzzy_equal?( sample, reverted_sample)
      rescue InputRangeException
        # Ignore input range exceptions here...
      end
    end
  end
  def test( forward, backward)
    one_way_test( forward, backward)
    one_way_test( backward, forward)
  end
end
if $0 == __FILE__ then
  require 'test/unit'
  class SqrTrait < TransformationTrait
    def transform( sample)
      sample * sample
    end
  end
  class DumbSqrtTrait < TransformationTrait
    def transform( sample)
      raise InputRangeException.new( sample) unless sample == 4
      2
    end
  end
  class TC_Uut < Test::Unit::TestCase
    def test_uut
      uut = UUT.new
      # Either the transformation objects need to be quite smart,
      # or we must pass in a Trait class that knows stuff.
      
      # I like the idea of a Trait class.
      forward = SqrTrait.new
      forward.sample_generator = [2]
      backward = DumbSqrtTrait.new
      uut.test( forward, backward)
    end
  end
end

previous next

Put in a real implementation of Sqrt and the test breaks. It breaks because I used the wrong version of equal? but I didn't notice that yet. I assumed it's due to floating point arithmetic. Twit.Loaded suite uut Started E Finished in 0.005217 seconds. 1) Error: test_uut(TC_Uut): RuntimeError: Forward '1' and back '1.0' sample differ uut.rb:47:in `one_way_test' uut.rb:36:in `each' uut.rb:36:in `one_way_test' uut.rb:56:in `test' uut.rb:97:in `test_uut' 1 tests, 0 assertions, 0 failures, 1 errors

class InputRangeException < Exception
  def initialize( value)
    @value = value
  end
  def to_s
    super + "Value '#{@value}' out of input range for transformation"
  end
end
class TransformationTrait
  attr_accessor :sample_generator
  def initialize
    @sample_generator = (1..100)
  end
  def transform( sample)
    sample
  end
  def fuzzy_equal?( a, b)
    a.equal?( b)
  end
end
class UUT
  def initialize()
  end
  def one_way_test( forward, backward)
    samples = forward.sample_generator
    samples.each do |sample|
      begin
        transformed_sample = forward.transform( sample)
        begin
          reverted_sample = backward.transform( transformed_sample)
        rescue InputRangeException => details
          raise "Unit Test failure, output range of transformation larger
than input range of reverse - '#{details}'"
        end
        raise "Forward '#{sample}' and back '#{reverted_sample}' sample differ" unless
          forward.fuzzy_equal?( sample, reverted_sample)
      rescue InputRangeException
        # Ignore input range exceptions here...
      end
    end
  end
  def test( forward, backward)
    one_way_test( forward, backward)
    one_way_test( backward, forward)
  end
end
if $0 == __FILE__ then
  require 'test/unit'
  class SqrTrait < TransformationTrait
    def transform( sample)
      sample * sample
    end
  end
  class DumbSqrtTrait < TransformationTrait
    def transform( sample)
      raise InputRangeException.new( sample) unless sample == 4
      2
    end
  end
  class SqrtTrait < TransformationTrait
    def transform( sample)
      Math.sqrt( sample)
    end
  end
  class TC_Uut < Test::Unit::TestCase
    def test_uut
      uut = UUT.new
      # Either the transformation objects need to be quite smart,
      # or we must pass in a Trait class that knows stuff.
      
      # I like the idea of a Trait class.
      forward = SqrTrait.new
      forward.sample_generator = [2]
      backward = DumbSqrtTrait.new
      uut.test( forward, backward)
      forward = SqrTrait.new
      backward = SqrtTrait.new
      uut.test( forward, backward)
    end
  end
end

previous next

Put in a floating point fuzzy equal, things work. Cool.Loaded suite uut Started . Finished in 0.007402 seconds. 1 tests, 0 assertions, 0 failures, 0 errors

class InputRangeException < Exception
  def initialize( value)
    @value = value
  end
  def to_s
    super + "Value '#{@value}' out of input range for transformation"
  end
end
class TransformationTrait
  attr_accessor :sample_generator
  def initialize
    @sample_generator = (1..100)
  end
  def transform( sample)
    sample
  end
  def fuzzy_equal?( a, b)
    a.equal?( b)
  end
end
class FloatingPointTransformationTrait < TransformationTrait
  def fuzzy_equal?( a, b)
    z = (a.abs + b.abs) / 2.0
    ((a - b).abs / z) < 0.0001
  end
end
class UUT
  def initialize()
  end
  def one_way_test( forward, backward)
    samples = forward.sample_generator
    samples.each do |sample|
      begin
        transformed_sample = forward.transform( sample)
        begin
          reverted_sample = backward.transform( transformed_sample)
        rescue InputRangeException => details
          raise "Unit Test failure, output range of transformation larger
than input range of reverse - '#{details}'"
        end
        raise "Forward '#{sample}' and back '#{reverted_sample}' sample differ" unless
          forward.fuzzy_equal?( sample, reverted_sample)
      rescue InputRangeException
        # Ignore input range exceptions here...
      end
    end
  end
  def test( forward, backward)
    one_way_test( forward, backward)
    one_way_test( backward, forward)
  end
end
if $0 == __FILE__ then
  require 'test/unit'
  class SqrTrait < TransformationTrait
  class SqrTrait < FloatingPointTransformationTrait
    def transform( sample)
      sample * sample
    end
  end
  class DumbSqrtTrait < TransformationTrait
    def transform( sample)
      raise InputRangeException.new( sample) unless sample == 4
      2
    end
  end
  class SqrtTrait < TransformationTrait
  class SqrtTrait < FloatingPointTransformationTrait
    def transform( sample)
      Math.sqrt( sample)
    end
  end
  class TC_Uut < Test::Unit::TestCase
    def test_uut
      uut = UUT.new
      # Either the transformation objects need to be quite smart,
      # or we must pass in a Trait class that knows stuff.
      
      # I like the idea of a Trait class.
      forward = SqrTrait.new
      forward.sample_generator = [2]
      backward = DumbSqrtTrait.new
      uut.test( forward, backward)
      forward = SqrTrait.new
      backward = SqrtTrait.new
      uut.test( forward, backward)
    end
  end
end

previous next

I'm starting to get a clutter of Traits. Let's refactor and clean up.Loaded suite uut Started . Finished in 0.00842 seconds. 1 tests, 0 assertions, 0 failures, 0 errors

class InputRangeException < Exception
  def initialize( value)
    @value = value
  end
  def to_s
    super + "Value '#{@value}' out of input range for transformation"
  end
end
class TransformationTrait
  attr_accessor :sample_generator
  def initialize
  def initialize( transform = Proc.new{|sample| sample} )
    @sample_generator = (1..100)
    @transform = transform
  end
  def transform( sample)
    sample
    @transform.call(sample)
  end
  def fuzzy_equal?( a, b)
    a.equal?( b)
  end
end
class FloatingPointTransformationTrait < TransformationTrait
  def fuzzy_equal?( a, b)
    z = (a.abs + b.abs) / 2.0
    ((a - b).abs / z) < 0.0001
  end
end
class UUT
  def initialize()
  end
  def one_way_test( forward, backward)
    samples = forward.sample_generator
    samples.each do |sample|
      begin
        transformed_sample = forward.transform( sample)
        begin
          reverted_sample = backward.transform( transformed_sample)
        rescue InputRangeException => details
          raise "Unit Test failure, output range of transformation larger
than input range of reverse - '#{details}'"
        end
        raise "Forward '#{sample}' and back '#{reverted_sample}' sample differ" unless
          forward.fuzzy_equal?( sample, reverted_sample)
      rescue InputRangeException
        # Ignore input range exceptions here...
      end
    end
  end
  def test( forward, backward)
    one_way_test( forward, backward)
    one_way_test( backward, forward)
  end
end
if $0 == __FILE__ then
  require 'test/unit'
  class SqrTrait < FloatingPointTransformationTrait
    def transform( sample)
      sample * sample
    end
  end
  class DumbSqrtTrait < TransformationTrait
    def transform( sample)
      raise InputRangeException.new( sample) unless sample == 4
      2
    end
  end
  class SqrtTrait < FloatingPointTransformationTrait
    def transform( sample)
      Math.sqrt( sample)
    end
  end
  class TC_Uut < Test::Unit::TestCase
    def test_uut
      uut = UUT.new
      # Either the transformation objects need to be quite smart,
      # or we must pass in a Trait class that knows stuff.
      
      # I like the idea of a Trait class.
      forward = SqrTrait.new
      forward = FloatingPointTransformationTrait.new( Proc.new{|s| s*s})
      forward.sample_generator = [2]
      backward = DumbSqrtTrait.new
      uut.test( forward, backward)
      forward = SqrTrait.new
      forward = FloatingPointTransformationTrait.new( Proc.new{|s| s*s})
      backward = SqrtTrait.new
      uut.test( forward, backward)
    end
  end
end

previous next

Grreat. That worked. Let's clobber another. I know I'm on the right track when by deleting code I'm adding functionality... Loaded suite uut Started . Finished in 0.009252 seconds. 1 tests, 0 assertions, 0 failures, 0 errors

class InputRangeException < Exception
  def initialize( value)
    @value = value
  end
  def to_s
    super + "Value '#{@value}' out of input range for transformation"
  end
end
class TransformationTrait
  attr_accessor :sample_generator
  def initialize( transform = Proc.new{|sample| sample} )
    @sample_generator = (1..100)
    @transform = transform
  end
  def transform( sample)
    @transform.call(sample)
  end
  def fuzzy_equal?( a, b)
    a.equal?( b)
  end
end
class FloatingPointTransformationTrait < TransformationTrait
  def fuzzy_equal?( a, b)
    z = (a.abs + b.abs) / 2.0
    ((a - b).abs / z) < 0.0001
  end
end
class UUT
  def initialize()
  end
  def one_way_test( forward, backward)
    samples = forward.sample_generator
    samples.each do |sample|
      begin
        transformed_sample = forward.transform( sample)
        begin
          reverted_sample = backward.transform( transformed_sample)
        rescue InputRangeException => details
          raise "Unit Test failure, output range of transformation larger
than input range of reverse - '#{details}'"
        end
        raise "Forward '#{sample}' and back '#{reverted_sample}' sample differ" unless
          forward.fuzzy_equal?( sample, reverted_sample)
      rescue InputRangeException
        # Ignore input range exceptions here...
      end
    end
  end
  def test( forward, backward)
    one_way_test( forward, backward)
    one_way_test( backward, forward)
  end
end
if $0 == __FILE__ then
  require 'test/unit'
  class DumbSqrtTrait < TransformationTrait
    def transform( sample)
      raise InputRangeException.new( sample) unless sample == 4
      2
    end
  end
  class SqrtTrait < FloatingPointTransformationTrait
    def transform( sample)
      Math.sqrt( sample)
    end
  end
  class TC_Uut < Test::Unit::TestCase
    def test_uut
      uut = UUT.new
      # Either the transformation objects need to be quite smart,
      # or we must pass in a Trait class that knows stuff.
      
      # I like the idea of a Trait class.
      forward = FloatingPointTransformationTrait.new( Proc.new{|s| s*s})
      forward.sample_generator = [2]
      backward = DumbSqrtTrait.new
      uut.test( forward, backward)
      forward = FloatingPointTransformationTrait.new( Proc.new{|s| s*s})
      backward = SqrtTrait.new
      backward = FloatingPointTransformationTrait.new( Proc.new{|s| Math.sqrt(s)})
      uut.test( forward, backward)
    end
  end
end

previous next

But really. My testing is very weak. Let's do some real testing. Malicious samples, systematic samples and random samples. Let's break stuff.

Ooo - it broke. Ah. Well. 0 is a fairly malicious sort of number.

Loaded suite uut Started E Finished in 0.005381 seconds. 1) Error: test_uut(TC_Uut): RuntimeError: Forward '0.0' and back '0.0' sample differ uut.rb:102:in `one_way_test' uut.rb:91:in `malicious_samples' uut.rb:55:in `each' uut.rb:91:in `one_way_test' uut.rb:111:in `test' uut.rb:140:in `test_uut' 1 tests, 0 assertions, 0 failures, 1 errors

class InputRangeException < Exception
  def initialize( value)
    @value = value
  end
  def to_s
    super + "Value '#{@value}' out of input range for transformation"
  end
end
class TransformationTrait
  attr_accessor :sample_generator
  def initialize( transform = Proc.new{|sample| sample} )
    @sample_generator = (1..100)
    @transform = transform
  end
  def transform( sample)
    @transform.call(sample)
  end
  def fuzzy_equal?( a, b)
    a.equal?( b)
  end
end
class FloatingPointSampleGenerator
  def initialize( n_random_samples = 200, n_systematic = 200)
    @n_random_samples = n_random_samples
    @n_systematic = n_systematic
  end
  def random_samples
    (0...@n_random_samples).each do |i|
      r = rand * 10.0 - 5.0
      # Cube it.
      r *= r * r
      yield r
    end
  end
  def systematic_samples
    x = -100.0
    step = x / (@n_systematic * 2.0)
    for i in (0...@n_systematic)
      yield x
      x += step
    end
  end
  def each( &block)
    malicious_samples(&block)
    systematic_samples(&block)
    random_samples( &block)
  end
    
  def malicious_samples
    yield 0.0
    yield( -1.0)
    yield( -2.0)
    yield 1.0
    yield 2.0
    yield 100.0
    yield 0.5
    yield( -0.5)
  end
end
class FloatingPointTransformationTrait < TransformationTrait
  def initialize( transform = Proc.new{|sample| sample} )
    super( transform)
    @sample_generator = FloatingPointSampleGenerator.new
  end
  def fuzzy_equal?( a, b)
    z = (a.abs + b.abs) / 2.0
    ((a - b).abs / z) < 0.0001
  end
end
class UUT
  def initialize()
  end
  def one_way_test( forward, backward)
    samples = forward.sample_generator
    samples.each do |sample|
      begin
        transformed_sample = forward.transform( sample)
        begin
          reverted_sample = backward.transform( transformed_sample)
        rescue InputRangeException => details
          raise "Unit Test failure, output range of transformation larger
than input range of reverse - '#{details}'"
        end
        raise "Forward '#{sample}' and back '#{reverted_sample}' sample differ" unless
          forward.fuzzy_equal?( sample, reverted_sample)
      rescue InputRangeException
        # Ignore input range exceptions here...
      end
    end
  end
  def test( forward, backward)
    one_way_test( forward, backward)
    one_way_test( backward, forward)
  end
end
if $0 == __FILE__ then
  require 'test/unit'
  class DumbSqrtTrait < TransformationTrait
    def transform( sample)
      raise InputRangeException.new( sample) unless sample == 4
      2
    end
  end
  class TC_Uut < Test::Unit::TestCase
    def test_uut
      uut = UUT.new
      # Either the transformation objects need to be quite smart,
      # or we must pass in a Trait class that knows stuff.
      
      # I like the idea of a Trait class.
      forward = FloatingPointTransformationTrait.new( Proc.new{|s| s*s})
      forward.sample_generator = [2]
      backward = DumbSqrtTrait.new
      uut.test( forward, backward)
      forward = FloatingPointTransformationTrait.new( Proc.new{|s| s*s})
      backward = FloatingPointTransformationTrait.new( Proc.new{|s| Math.sqrt(s)})
      uut.test( forward, backward)
    end
  end
end

previous next

Let's do that fuzzy equal properly.

Dang! Still broke. Ah yes! Squaring a number is a lossy transformation, it loses all info about the sign of the number.

Loaded suite uut Started E Finished in 0.005358 seconds. 1) Error: test_uut(TC_Uut): RuntimeError: Forward '-1.0' and back '1.0' sample differ uut.rb:105:in `one_way_test' uut.rb:94:in `malicious_samples' uut.rb:55:in `each' uut.rb:94:in `one_way_test' uut.rb:114:in `test' uut.rb:143:in `test_uut' 1 tests, 0 assertions, 0 failures, 1 errors

class InputRangeException < Exception
  def initialize( value)
    @value = value
  end
  def to_s
    super + "Value '#{@value}' out of input range for transformation"
  end
end
class TransformationTrait
  attr_accessor :sample_generator
  def initialize( transform = Proc.new{|sample| sample} )
    @sample_generator = (1..100)
    @transform = transform
  end
  def transform( sample)
    @transform.call(sample)
  end
  def fuzzy_equal?( a, b)
    a.equal?( b)
  end
end
class FloatingPointSampleGenerator
  def initialize( n_random_samples = 200, n_systematic = 200)
    @n_random_samples = n_random_samples
    @n_systematic = n_systematic
  end
  def random_samples
    (0...@n_random_samples).each do |i|
      r = rand * 10.0 - 5.0
      # Cube it.
      r *= r * r
      yield r
    end
  end
  def systematic_samples
    x = -100.0
    step = x / (@n_systematic * 2.0)
    for i in (0...@n_systematic)
      yield x
      x += step
    end
  end
  def each( &block)
    malicious_samples(&block)
    systematic_samples(&block)
    random_samples( &block)
  end
    
  def malicious_samples
    yield 0.0
    yield( -1.0)
    yield( -2.0)
    yield 1.0
    yield 2.0
    yield 100.0
    yield 0.5
    yield( -0.5)
  end
end
class FloatingPointTransformationTrait < TransformationTrait
  def initialize( transform = Proc.new{|sample| sample} )
    super( transform)
    @sample_generator = FloatingPointSampleGenerator.new
  end
  EPSILON = 0.0001
  def fuzzy_equal?( a, b)
    z = (a.abs + b.abs) / 2.0
    ((a - b).abs / z) < 0.0001
    return true if z < EPSILON
    ((a - b).abs / z) < EPSILON
  end
end
class UUT
  def initialize()
  end
  def one_way_test( forward, backward)
    samples = forward.sample_generator
    samples.each do |sample|
      begin
        transformed_sample = forward.transform( sample)
        begin
          reverted_sample = backward.transform( transformed_sample)
        rescue InputRangeException => details
          raise "Unit Test failure, output range of transformation larger
than input range of reverse - '#{details}'"
        end
        raise "Forward '#{sample}' and back '#{reverted_sample}' sample differ" unless
          forward.fuzzy_equal?( sample, reverted_sample)
      rescue InputRangeException
        # Ignore input range exceptions here...
      end
    end