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
  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

Ok, so we can cope with lost signs. Whoops, -1 is out of range for sqrt.Loaded suite uut Started E Finished in 0.020251 seconds. 1) Error: test_uut(TC_Uut): Errno::EDOM: Numerical argument out of domain - sqrt uut.rb:149:in `sqrt' uut.rb:149:in `test_uut' uut.rb:149:in `call' uut.rb:22:in `transform' uut.rb:103:in `one_way_test' uut.rb:101:in `malicious_samples' uut.rb:55:in `each' uut.rb:101:in `one_way_test' uut.rb:122:in `test' uut.rb:150: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
    return true if z < EPSILON
    ((a - b).abs / z) < EPSILON
  end
end
class SignLossyTransformation < FloatingPointTransformationTrait
  def fuzzy_equal?( a, b)
    super( a.abs, b.abs)
  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 = SignLossyTransformation.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})
      forward = SignLossyTransformation.new( Proc.new{|s| s*s})
      backward = FloatingPointTransformationTrait.new( Proc.new{|s| Math.sqrt(s)})
      uut.test( forward, backward)
    end
  end
end

previous next

Translate for floating point ops that Errno::EDOM _is_ inputrange exception.

Perhaps I should make all InputRange Exceptions Errno::EDOM....

I'll think about it later. Maybe.

Anyway, the UUT works for sqr<=>sqrt. And has no explicit knowledge about that transform in it's code. Cool!

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

class InputRangeException < Exception
  def initialize( value)
  def initialize( value, message='')
    super(message)
    @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
    return true if z < EPSILON
    ((a - b).abs / z) < EPSILON
  end
  def transform( sample)
    super( sample)
  rescue Errno::EDOM => details
    raise InputRangeException.new( sample, details.to_s)
  end
end
class SignLossyTransformation < FloatingPointTransformationTrait
  def fuzzy_equal?( a, b)
    super( a.abs, b.abs)
  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 = SignLossyTransformation.new( Proc.new{|s| s*s})
      forward.sample_generator = [2]
      backward = DumbSqrtTrait.new
      uut.test( forward, backward)
      forward = SignLossyTransformation.new( Proc.new{|s| s*s})
      backward = FloatingPointTransformationTrait.new( Proc.new{|s| Math.sqrt(s)})
      uut.test( forward, backward)
    end
  end
end

previous next

Can we work with other reversible transforms? Yip, I put in a transform and an invalid inverse, and it failed. Good.Loaded suite uut Started E. Finished in 0.069877 seconds. 1) Error: test_universality(TC_Uut): RuntimeError: Forward '0.0' and back '42.0' sample differ uut.rb:119:in `one_way_test' uut.rb:108:in `malicious_samples' uut.rb:56:in `each' uut.rb:108:in `one_way_test' uut.rb:128:in `test' uut.rb:162:in `test_universality' 2 tests, 0 assertions, 0 failures, 1 errors

class InputRangeException < Exception
  def initialize( value, message='')
    super(message)
    @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
    return true if z < EPSILON
    ((a - b).abs / z) < EPSILON
  end
  def transform( sample)
    super( sample)
  rescue Errno::EDOM => details
    raise InputRangeException.new( sample, details.to_s)
  end
end
class SignLossyTransformation < FloatingPointTransformationTrait
  def fuzzy_equal?( a, b)
    super( a.abs, b.abs)
  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 = SignLossyTransformation.new( Proc.new{|s| s*s})
      forward.sample_generator = [2]
      backward = DumbSqrtTrait.new
      uut.test( forward, backward)
      forward = SignLossyTransformation.new( Proc.new{|s| s*s})
      backward = FloatingPointTransformationTrait.new( Proc.new{|s| Math.sqrt(s)})
      uut.test( forward, backward)
    end
    def test_universality
      UUT.new.test(
                   FloatingPointTransformationTrait.new( Proc.new{|s| 2 * s + 42}),
                   FloatingPointTransformationTrait.new
                   )
      
    end
  end
end

previous next

The fact that it failed is a Good Thing. So capture it in a unit test. So it stops working, the test will fail.

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

class InputRangeException < Exception
  def initialize( value, message='')
    super(message)
    @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
    return true if z < EPSILON
    ((a - b).abs / z) < EPSILON
  end
  def transform( sample)
    super( sample)
  rescue Errno::EDOM => details
    raise InputRangeException.new( sample, details.to_s)
  end
end
class SignLossyTransformation < FloatingPointTransformationTrait
  def fuzzy_equal?( a, b)
    super( a.abs, b.abs)
  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 = SignLossyTransformation.new( Proc.new{|s| s*s})
      forward.sample_generator = [2]
      backward = DumbSqrtTrait.new
      uut.test( forward, backward)
      forward = SignLossyTransformation.new( Proc.new{|s| s*s})
      backward = FloatingPointTransformationTrait.new( Proc.new{|s| Math.sqrt(s)})
      uut.test( forward, backward)
    end
    def test_universality
      begin
      UUT.new.test(
                   FloatingPointTransformationTrait.new( Proc.new{|s| 2 * s + 42}),
                   FloatingPointTransformationTrait.new
                   )
        assert( false)
      rescue RuntimeError => details
        assert( details.to_s =~ %r{ differ }x)
      end
      
    end
  end
end

previous next

Try a proper implementation of the inverse. It failed. Silly me, my Bad.Loaded suite uut Started E. Finished in 0.067634 seconds. 1) Error: test_universality(TC_Uut): RuntimeError: Forward '0.0' and back '-21.0' sample differ uut.rb:119:in `one_way_test' uut.rb:108:in `malicious_samples' uut.rb:56:in `each' uut.rb:108:in `one_way_test' uut.rb:128:in `test' uut.rb:172:in `test_universality' 2 tests, 1 assertions, 0 failures, 1 errors

class InputRangeException < Exception
  def initialize( value, message='')
    super(message)
    @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
    return true if z < EPSILON
    ((a - b).abs / z) < EPSILON
  end
  def transform( sample)
    super( sample)
  rescue Errno::EDOM => details
    raise InputRangeException.new( sample, details.to_s)
  end
end
class SignLossyTransformation < FloatingPointTransformationTrait
  def fuzzy_equal?( a, b)
    super( a.abs, b.abs)
  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 = SignLossyTransformation.new( Proc.new{|s| s*s})
      forward.sample_generator = [2]
      backward = DumbSqrtTrait.new
      uut.test( forward, backward)
      forward = SignLossyTransformation.new( Proc.new{|s| s*s})
      backward = FloatingPointTransformationTrait.new( Proc.new{|s| Math.sqrt(s)})
      uut.test( forward, backward)
    end
    def test_universality
      begin
        UUT.new.test(
                     FloatingPointTransformationTrait.new( Proc.new{|s| 2 * s + 42}),
                     FloatingPointTransformationTrait.new
                     )
        assert( false)
      rescue RuntimeError => details
        assert( details.to_s =~ %r{ differ }x)
      end
      UUT.new.test(
                   FloatingPointTransformationTrait.new( Proc.new{|s| 2 * s + 42}),
                   FloatingPointTransformationTrait.new( Proc.new{|s| s / 2.0 - 42}))
      
    end
  end
end

previous next

Do my math's properly and it works.Loaded suite uut Started .. Finished in 0.096799 seconds. 2 tests, 1 assertions, 0 failures, 0 errors

class InputRangeException < Exception
  def initialize( value, message='')
    super(message)
    @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
    return true if z < EPSILON
    ((a - b).abs / z) < EPSILON
  end
  def transform( sample)
    super( sample)
  rescue Errno::EDOM => details
    raise InputRangeException.new( sample, details.to_s)
  end
end
class SignLossyTransformation < FloatingPointTransformationTrait
  def fuzzy_equal?( a, b)
    super( a.abs, b.abs)
  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 = SignLossyTransformation.new( Proc.new{|s| s*s})
      forward.sample_generator = [2]
      backward = DumbSqrtTrait.new
      uut.test( forward, backward)
      forward = SignLossyTransformation.new( Proc.new{|s| s*s})
      backward = FloatingPointTransformationTrait.new( Proc.new{|s| Math.sqrt(s)})
      uut.test( forward, backward)
    end
    def test_universality
      begin
        UUT.new.test(
                     FloatingPointTransformationTrait.new( Proc.new{|s| 2 * s + 42}),
                     FloatingPointTransformationTrait.new
                     )
        assert( false)
      rescue RuntimeError => details
        assert( details.to_s =~ %r{ differ }x)
      end
      UUT.new.test(
                   FloatingPointTransformationTrait.new( Proc.new{|s| 2 * s + 42}),
                   FloatingPointTransformationTrait.new( Proc.new{|s| s / 2.0 - 42}))
                   FloatingPointTransformationTrait.new( Proc.new{|s| (s - 42) / 2.0}))
      
    end
  end
end

previous next

I said Universal and meant Universal.

Let's move off the realm of mathmetical inverses and into encryption. Try the classic Caesar cipher.

Whoops, it failed. The default input sequence of 0..100 are not valid strings.

Loaded suite uut Started E.. Finished in 0.096815 seconds. 1) Error: test_rot13(TC_Uut): NoMethodError: undefined method `tr' for 1:Fixnum uut.rb:180:in `test_rot13' uut.rb:180:in `call' uut.rb:23:in `transform' uut.rb:110:in `one_way_test' uut.rb:108:in `each' uut.rb:108:in `one_way_test' uut.rb:128:in `test' uut.rb:179:in `test_rot13' 3 tests, 1 assertions, 0 failures, 1 errors

class InputRangeException < Exception
  def initialize( value, message='')
    super(message)
    @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
    return true if z < EPSILON
    ((a - b).abs / z) < EPSILON
  end
  def transform( sample)
    super( sample)
  rescue Errno::EDOM => details
    raise InputRangeException.new( sample, details.to_s)
  end
end
class SignLossyTransformation < FloatingPointTransformationTrait
  def fuzzy_equal?( a, b)
    super( a.abs, b.abs)
  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 = SignLossyTransformation.new( Proc.new{|s| s*s})
      forward.sample_generator = [2]
      backward = DumbSqrtTrait.new
      uut.test( forward, backward)
      forward = SignLossyTransformation.new( Proc.new{|s| s*s})
      backward = FloatingPointTransformationTrait.new( Proc.new{|s| Math.sqrt(s)})
      uut.test( forward, backward)
    end
    def test_universality
      begin
        UUT.new.test(
                     FloatingPointTransformationTrait.new( Proc.new{|s| 2 * s + 42}),
                     FloatingPointTransformationTrait.new
                     )
        assert( false)
      rescue RuntimeError => details
        assert( details.to_s =~ %r{ differ }x)
      end
      UUT.new.test(
                   FloatingPointTransformationTrait.new( Proc.new{|s| 2 * s + 42}),
                   FloatingPointTransformationTrait.new( Proc.new{|s| (s - 42) / 2.0}))
      
    end
    def test_rot13
      UUT.new.test(
                   TransformationTrait.new( Proc.new{|s| s.tr 'a-z' 'n-za-m'}),
                   TransformationTrait.new( Proc.new{|s| s.tr 'n-za-m' 'a-z'})
                   )
      
    end
  end
end

previous next

Let's concoct a fairly harsh set of test strings.

Ah! It fails! Why?

Loaded suite uut Started E.. Finished in 0.098302 seconds. 1) Error: test_rot13(TC_Uut): RuntimeError: Forward '' and back '' sample differ uut.rb:164:in `one_way_test' uut.rb:153:in `malicious_samples' uut.rb:101:in `each' uut.rb:153:in `one_way_test' uut.rb:173:in `test' uut.rb:224:in `test_rot13' 3 tests, 1 assertions, 0 failures, 1 errors

class InputRangeException < Exception
  def initialize( value, message='')
    super(message)
    @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)
  def initialize( transform = Proc.new{|sample| sample}, p_sample_generator=(0..100))
    @sample_generator = p_sample_generator
    @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 StringSampleGenerator
  def initialize( n_random_samples = 200, n_systematic = 10)
    @n_random_samples = n_random_samples
    @n_systematic = n_systematic
  end
  def random_samples
    (0...@n_random_samples).each do |i|
      i = rand(512)
      s = ''
      (0...i).each {|i|
        r = ('a'[0] + rand(26)).chr
        s += r
      }
      yield s
    end
  end
  def systematic_samples
    s = "abc"
    for i in (0...@n_systematic)
      yield s
      s.succ!
    end
  end
  def each( &block)
    malicious_samples(&block)
    systematic_samples(&block)
    random_samples( &block)
  end
    
  def malicious_samples
    yield ''
    yield 'a'
    yield 'aa'
    yield 'abcdefghijklmnopqrstuvwxyz'
    yield 'bcdefghijklmnopqrstuvwxyz'
    yield 'abcdefghijklmnopqrstuvwxy'
    yield 'FruitLoop'
    yield '01234'
  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
    return true if z < EPSILON
    ((a - b).abs / z) < EPSILON
  end
  def transform( sample)
    super( sample)
  rescue Errno::EDOM => details
    raise InputRangeException.new( sample, details.to_s)
  end
end
class SignLossyTransformation < FloatingPointTransformationTrait
  def fuzzy_equal?( a, b)
    super( a.abs, b.abs)
  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 = SignLossyTransformation.new( Proc.new{|s| s*s})
      forward.sample_generator = [2]
      backward = DumbSqrtTrait.new
      uut.test( forward, backward)
      forward = SignLossyTransformation.new( Proc.new{|s| s*s})
      backward = FloatingPointTransformationTrait.new( Proc.new{|s| Math.sqrt(s)})
      uut.test( forward, backward)
    end
    def test_universality
      begin
        UUT.new.test(
                     FloatingPointTransformationTrait.new( Proc.new{|s| 2 * s + 42}),
                     FloatingPointTransformationTrait.new
                     )
        assert( false)
      rescue RuntimeError => details
        assert( details.to_s =~ %r{ differ }x)
      end
      UUT.new.test(
                   FloatingPointTransformationTrait.new( Proc.new{|s| 2 * s + 42}),
                   FloatingPointTransformationTrait.new( Proc.new{|s| (s - 42) / 2.0}))
      
    end
    def test_rot13
      UUT.new.test(
                   TransformationTrait.new( Proc.new{|s| s.tr 'a-z' 'n-za-m'}),
                   TransformationTrait.new( Proc.new{|s| s.tr 'n-za-m' 'a-z'})
                   TransformationTrait.new( Proc.new{|s| s.tr( 'a-z', 'n-za-m')}, StringSampleGenerator.new),
                   TransformationTrait.new( Proc.new{|s| s.tr( 'n-za-m', 'a-z')}, StringSampleGenerator.new)
                   )
      
    end
  end
end

previous next

Used the wrong method. Used .equal?, should have used .eql?

The UUT works for string transforms as well! Cool!

But we are left exposing the weakness of the UUT. We have done a fairly harsh test of my caesar code. But is it the caesar code?

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

class InputRangeException < Exception
  def initialize( value, message='')
    super(message)
    @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}, p_sample_generator=(0..100))
    @sample_generator = p_sample_generator
    @transform = transform
  end
  def transform( sample)
    @transform.call(sample)
  end
  def fuzzy_equal?( a, b)
    a.equal?( b)
    a.eql?( 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 StringSampleGenerator
  def initialize( n_random_samples = 200, n_systematic = 10)
    @n_random_samples = n_random_samples
    @n_systematic = n_systematic
  end
  def random_samples
    (0...@n_random_samples).each do |i|
      i = rand(512)
      s = ''
      (0...i).each {|i|
        r = ('a'[0] + rand(26)).chr
        s += r
      }
      yield s
    end
  end
  def systematic_samples
    s = "abc"
    for i in (0...@n_systematic)
      yield s
      s.succ!
    end
  end
  def each( &block)
    malicious_samples(&block)
    systematic_samples(&block)
    random_samples( &block)
  end
    
  def malicious_samples
    yield ''
    yield 'a'
    yield 'aa'
    yield 'abcdefghijklmnopqrstuvwxyz'
    yield 'bcdefghijklmnopqrstuvwxyz'
    yield 'abcdefghijklmnopqrstuvwxy'
    yield 'FruitLoop'
    yield '01234'
  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
    return true if z < EPSILON
    ((a - b).abs / z) < EPSILON
  end
  def transform( sample)
    super( sample)
  rescue Errno::EDOM => details
    raise InputRangeException.new( sample, details.to_s)
  end
end
class SignLossyTransformation < FloatingPointTransformationTrait
  def fuzzy_equal?( a, b)
    super( a.abs, b.abs)
  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 = SignLossyTransformation.new( Proc.new{|s| s*s})
      forward.sample_generator = [2]
      backward = DumbSqrtTrait.new
      uut.test( forward, backward)
      forward = SignLossyTransformation.new( Proc.new{|s| s*s})
      backward = FloatingPointTransformationTrait.new( Proc.new{|s| Math.sqrt(s)})
      uut.test( forward, backward)
    end
    def test_universality
      begin
        UUT.new.test(
                     FloatingPointTransformationTrait.new( Proc.new{|s| 2 * s + 42}),
                     FloatingPointTransformationTrait.new
                     )
        assert( false)
      rescue RuntimeError => details
        assert( details.to_s =~ %r{ differ }x)
      end
      UUT.new.test(
                   FloatingPointTransformationTrait.new( Proc.new{|s| 2 * s + 42}),
                   FloatingPointTransformationTrait.new( Proc.new{|s| (s - 42) / 2.0}))
      
    end
    def test_rot13
      UUT.new.test(
                   TransformationTrait.new( Proc.new{|s| s.tr( 'a-z', 'n-za-m')}, StringSampleGenerator.new),
                   TransformationTrait.new( Proc.new{|s| s.tr( 'n-za-m', 'a-z')}, StringSampleGenerator.new)
                   )
      
    end
  end
end

previous next

So pull in an example off the wikipedia....Loaded suite uut Started E.. Finished in 0.662482 seconds. 1) Error: test_rot13(TC_Uut): RuntimeError: Unit Test failure, output range of transformation larger than input range of reverse - 'Value 'Hbj pna lbh gryy na rkgebireg sebz na vagebireg ng NSA? Ia gur ryringbef, gur rkgebiregf ybbx ng gur OTHER thl'f fubrf.' out of input range for transformation' uut.rb:161:in `one_way_test' uut.rb:153:in `each' uut.rb:153:in `one_way_test' uut.rb:173:in `test' uut.rb:236:in `test_rot13' 3 tests, 1 assertions, 0 failures, 1 errors

class InputRangeException < Exception
  def initialize( value, message='')
    super(message)
    @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}, p_sample_generator=(0..100))
    @sample_generator = p_sample_generator
    @transform = transform
  end
  def transform( sample)
    @transform.call(sample)
  end
  def fuzzy_equal?( a, b)
    a.eql?( 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 StringSampleGenerator
  def initialize( n_random_samples = 200, n_systematic = 10)
    @n_random_samples = n_random_samples
    @n_systematic = n_systematic
  end
  def random_samples
    (0...@n_random_samples).each do |i|
      i = rand(512)
      s = ''
      (0...i).each {|i|
        r = ('a'[0] + rand(26)).chr
        s += r
      }
      yield s
    end
  end
  def systematic_samples
    s = "abc"
    for i in (0...@n_systematic)
      yield s
      s.succ!
    end
  end
  def each( &block)
    malicious_samples(&block)
    systematic_samples(&block)
    random_samples( &block)
  end
    
  def malicious_samples
    yield ''
    yield 'a'
    yield 'aa'
    yield 'abcdefghijklmnopqrstuvwxyz'
    yield 'bcdefghijklmnopqrstuvwxyz'
    yield 'abcdefghijklmnopqrstuvwxy'
    yield 'FruitLoop'
    yield '01234'
  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
    return true if z < EPSILON
    ((a - b).abs / z) < EPSILON
  end
  def transform( sample)
    super( sample)
  rescue Errno::EDOM => details
    raise InputRangeException.new( sample, details.to_s)
  end
end
class SignLossyTransformation < FloatingPointTransformationTrait
  def fuzzy_equal?( a, b)
    super( a.abs, b.abs)
  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 = SignLossyTransformation.new( Proc.new{|s| s*s})
      forward.sample_generator = [2]
      backward = DumbSqrtTrait.new
      uut.test( forward, backward)
      forward = SignLossyTransformation.new( Proc.new{|s| s*s})
      backward = FloatingPointTransformationTrait.new( Proc.new{|s| Math.sqrt(s)})
      uut.test( forward, backward)
    end
    def test_universality
      begin
        UUT.new.test(
                     FloatingPointTransformationTrait.new( Proc.new{|s| 2 * s + 42}),
                     FloatingPointTransformationTrait.new
                     )
        assert( false)
      rescue RuntimeError => details
        assert( details.to_s =~ %r{ differ }x)
      end
      UUT.new.test(
                   FloatingPointTransformationTrait.new( Proc.new{|s| 2 * s + 42}),
                   FloatingPointTransformationTrait.new( Proc.new{|s| (s - 42) / 2.0}))
      
    end
    def test_rot13
      UUT.new.test(
                   TransformationTrait.new( Proc.new{|s| s.tr( 'a-z', 'n-za-m')}, StringSampleGenerator.new),
                   TransformationTrait.new( Proc.new{|s| s.tr( 'n-za-m', 'a-z')}, StringSampleGenerator.new)
                   )
      
      src = "How can you tell an extrovert from an introvert at NSA? In the elevators, the extroverts look at the OTHER guy's shoes."
      crypt = "Ubj pna lbh gryy na rkgebireg sebz na vagebireg ng AFN? Va gur ryringbef, gur rkgebiregf ybbx ng gur BGURE thl'f fubrf."
      forward = TransformationTrait.new( Proc.new{|s| s.tr( 'a-z', 'n-za-m')}, StringSampleGenerator.new)
      forward.sample_generator = [src]
      UUT.new.test(
                   forward,
                   TransformationTrait.new( Proc.new{|s|
                                              raise InputRangeException, s unless s == crypt
                                              src},
                                            StringSampleGenerator.new)
                   )
      
    end
  end
end

previous next

So this is real live coding. I came up with the wrong fix for the problem. Instead of extending the translate I downcased everything.

Stupid.

Tough! This is testing the UUT not my intelligence.

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

class InputRangeException < Exception
  def initialize( value, message='')
    super(message)
    @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}, p_sample_generator=(0..100))
    @sample_generator = p_sample_generator
    @transform = transform
  end
  def transform( sample)
    @transform.call(sample)
  end
  def fuzzy_equal?( a, b)
    a.eql?( 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 StringSampleGenerator
  def initialize( n_random_samples = 200, n_systematic = 10)
    @n_random_samples = n_random_samples
    @n_systematic = n_systematic
  end
  def random_samples
    (0...@n_random_samples).each do |i|
      i = rand(512)
      s = ''
      (0...i).each {|i|
        r = ('a'[0] + rand(26)).chr
        s += r
      }
      yield s
    end
  end
  def systematic_samples
    s = "abc"
    for i in (0...@n_systematic)
      yield s
      s.succ!
    end
  end
  def each( &block)
    malicious_samples(&block)
    systematic_samples(&block)
    random_samples( &block)
  end
    
  def malicious_samples
    yield ''
    yield 'a'
    yield 'aa'
    yield 'abcdefghijklmnopqrstuvwxyz'
    yield 'bcdefghijklmnopqrstuvwxyz'
    yield 'abcdefghijklmnopqrstuvwxy'
    yield 'FruitLoop'
    yield '01234'
  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
    return true if z < EPSILON
    ((a - b).abs / z) < EPSILON
  end
  def transform( sample)
    super( sample)
  rescue Errno::EDOM => details
    raise InputRangeException.new( sample, details.to_s)
  end
end
class SignLossyTransformation < FloatingPointTransformationTrait
  def fuzzy_equal?( a, b)
    super( a.abs, b.abs)
  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 = SignLossyTransformation.new( Proc.new{|s| s*s})
      forward.sample_generator = [2]
      backward = DumbSqrtTrait.new
      uut.test( forward, backward)
      forward = SignLossyTransformation.new( Proc.new{|s| s*s})
      backward = FloatingPointTransformationTrait.new( Proc.new{|s| Math.sqrt(s)})
      uut.test( forward, backward)
    end
    def test_universality
      begin
        UUT.new.test(
                     FloatingPointTransformationTrait.new( Proc.new{|s| 2 * s + 42}),
                     FloatingPointTransformationTrait.new
                     )
        assert( false)
      rescue RuntimeError => details
        assert( details.to_s =~ %r{ differ }x)
      end
      UUT.new.test(
                   FloatingPointTransformationTrait.new( Proc.new{|s| 2 * s + 42}),
                   FloatingPointTransformationTrait.new( Proc.new{|s| (s - 42) / 2.0}))
      
    end
    def test_rot13
      UUT.new.test(
                   TransformationTrait.new( Proc.new{|s| s.tr( 'a-z', 'n-za-m')}, StringSampleGenerator.new),
                   TransformationTrait.new( Proc.new{|s| s.tr( 'n-za-m', 'a-z')}, StringSampleGenerator.new)
                   )
      
      src = "How can you tell an extrovert from an introvert at NSA? In the elevators, the extroverts look at the OTHER guy's shoes."
      src.downcase!
      crypt = "Ubj pna lbh gryy na rkgebireg sebz na vagebireg ng AFN? Va gur ryringbef, gur rkgebiregf ybbx ng gur BGURE thl'f fubrf."
      crypt.downcase!
      forward = TransformationTrait.new( Proc.new{|s| s.tr( 'a-z', 'n-za-m')}, StringSampleGenerator.new)
      forward.sample_generator = [src]
      UUT.new.test(
                   forward,
                   TransformationTrait.new( Proc.new{|s|
                                              raise InputRangeException, s unless s == crypt
                                              src},
                                            StringSampleGenerator.new)
                   )
      
    end
  end
end

previous next

What about a very lossy transform? For example an MD5 checksum? That isn't invertible.

Loaded suite uut Started ...E Finished in 0.954034 seconds. 1) Error: test_very_lossy(TC_Uut): RuntimeError: Forward '' and back 'd41d8cd98f00b204e9800998ecf8427e' sample differ uut.rb:164:in `one_way_test' uut.rb:153:in `malicious_samples' uut.rb:101:in `each' uut.rb:153:in `one_way_test' uut.rb:173:in `test' uut.rb:250:in `test_very_lossy' 4 tests, 1 assertions, 0 failures, 1 errors

class InputRangeException < Exception
  def initialize( value, message='')
    super(message)
    @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}, p_sample_generator=(0..100))
    @sample_generator = p_sample_generator
    @transform = transform
  end
  def transform( sample)
    @transform.call(sample)
  end
  def fuzzy_equal?( a, b)
    a.eql?( 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 StringSampleGenerator
  def initialize( n_random_samples = 200, n_systematic = 10)
    @n_random_samples = n_random_samples
    @n_systematic = n_systematic
  end
  def random_samples
    (0...@n_random_samples).each do |i|
      i = rand(512)
      s = ''
      (0...i).each {|i|
        r = ('a'[0] + rand(26)).chr
        s += r
      }
      yield s
    end
  end
  def systematic_samples
    s = "abc"
    for i in (0...@n_systematic)
      yield s
      s.succ!
    end
  end
  def each( &block)
    malicious_samples(&block)
    systematic_samples(&block)
    random_samples( &block)
  end
    
  def malicious_samples
    yield ''
    yield 'a'
    yield 'aa'
    yield 'abcdefghijklmnopqrstuvwxyz'
    yield 'bcdefghijklmnopqrstuvwxyz'
    yield 'abcdefghijklmnopqrstuvwxy'
    yield 'FruitLoop'
    yield '01234'
  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
    return true if z < EPSILON
    ((a - b).abs / z) < EPSILON
  end
  def transform( sample)
    super( sample)
  rescue Errno::EDOM => details
    raise InputRangeException.new( sample, details.to_s)
  end
end
class SignLossyTransformation < FloatingPointTransformationTrait
  def fuzzy_equal?( a, b)
    super( a.abs, b.abs)
  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'
  require 'digest/md5'
  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 = SignLossyTransformation.new( Proc.new{|s| s*s})
      forward.sample_generator = [2]
      backward = DumbSqrtTrait.new
      uut.test( forward, backward)
      forward = SignLossyTransformation.new( Proc.new{|s| s*s})
      backward = FloatingPointTransformationTrait.new( Proc.new{|s| Math.sqrt(s)})
      uut.test( forward, backward)
    end
    def test_universality
      begin
        UUT.new.test(
                     FloatingPointTransformationTrait.new( Proc.new{|s| 2 * s + 42}),
                     FloatingPointTransformationTrait.new
                     )
        assert( false)
      rescue RuntimeError => details
        assert( details.to_s =~ %r{ differ }x)
      end
      UUT.new.test(
                   FloatingPointTransformationTrait.new( Proc.new{|s| 2 * s + 42}),
                   FloatingPointTransformationTrait.new( Proc.new{|s| (s - 42) / 2.0}))
      
    end
    def test_rot13
      UUT.new.test(
                   TransformationTrait.new( Proc.new{|s| s.tr( 'a-z', 'n-za-m')}, StringSampleGenerator.new),
                   TransformationTrait.new( Proc.new{|s| s.tr( 'n-za-m', 'a-z')}, StringSampleGenerator.new)
                   )
      
      src = "How can you tell an extrovert from an introvert at NSA? In the elevators, the extroverts look at the OTHER guy's shoes."
      src.downcase!
      crypt = "Ubj pna lbh gryy na rkgebireg sebz na vagebireg ng AFN? Va gur ryringbef, gur rkgebiregf ybbx ng gur BGURE thl'f fubrf."
      crypt.downcase!
      forward = TransformationTrait.new( Proc.new{|s| s.tr( 'a-z', 'n-za-m')}, StringSampleGenerator.new)
      forward.sample_generator = [src]
      UUT.new.test(
                   forward,
                   TransformationTrait.new( Proc.new{|s|
                                              raise InputRangeException, s unless s == crypt
                                              src},
                                            StringSampleGenerator.new)
                   )
      
    end
    def test_very_lossy
      UUT.new.test(
                   TransformationTrait.new( Proc.new{|s| Digest::MD5.new(s).hexdigest}, StringSampleGenerator.new),
                   TransformationTrait.new( Proc.new{|s|
                                              raise InputRangeException, s unless
                                                s =~ %r{ ^[0-9a-f]+ $ }x
                                              s
                                            }, StringSampleGenerator.new)
                   )
      
    end
  end
end

previousnext

Ok, so the UtterlyLossyTrait doesn't check much. But strangely enough quite a bit is being checked! I wouldn't called it a Good test of the MD5 checksum, but it certainly a _lot_ more thorough than nothing!

Conclusion

So, I have certainly proved it is possible. But in some sense the result is slightly vacuous, I have moved a bunch of the smarts onto the transformation traits.

However, I still think the idea is useful for the following reasons...

  • The transformation traits are reusable in their domains. ie. You can make them much smarter, so a *unit framework could construct a very smart set of transformation traits that really tested each domain very intensively.
  • The UUT could be a lot smarter. You don't really care how many samples you run. You care how long the test runs for. ie. You could give the UUT the smarts to run 5 seconds worth of samples random and systematic.
  • If the transformation traits could also generate samples known to be in the input range and samples known to be outside the range, the UUT could assert more about them.
  • This implementation doesn't count how many assertions it makes. A smarter implementation would.

The Universal in UUT is in fact pretty Universal. The same UUT can be reused for reversible things like XML parsers all the way through to lossy transformations like compilers. The trick is how much you can constrain things in the input range tests and via the "fuzzy_equal?" method. As the SignLossyTransformation and UtterlyLossyTrait demonstrated the UUT provides usable assertions on a continuum from exact to utterly lossy.

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

class InputRangeException < Exception
  def initialize( value, message='')
    super(message)
    @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}, p_sample_generator=(0..100))
    @sample_generator = p_sample_generator
    @transform = transform
  end
  def transform( sample)
    @transform.call(sample)
  end
  def fuzzy_equal?( a, b)
    a.eql?( b)
  end
end
class UtterlyLossyTrait < TransformationTrait
  def fuzzy_equal?( a, b)
    true
  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 StringSampleGenerator
  def initialize( n_random_samples = 200, n_systematic = 10)
    @n_random_samples = n_random_samples
    @n_systematic = n_systematic
  end
  def random_samples
    (0...@n_random_samples).each do |i|
      i = rand(512)
      s = ''
      (0...i).each {|i|
        r = ('a'[0] + rand(26)).chr
        s += r
      }
      yield s
    end
  end
  def systematic_samples
    s = "abc"
    for i in (0...@n_systematic)
      yield s
      s.succ!
    end
  end
  def each( &block)
    malicious_samples(&block)
    systematic_samples(&block)
    random_samples( &block)
  end
    
  def malicious_samples
    yield ''
    yield 'a'
    yield 'aa'
    yield 'abcdefghijklmnopqrstuvwxyz'
    yield 'bcdefghijklmnopqrstuvwxyz'
    yield 'abcdefghijklmnopqrstuvwxy'
    yield 'FruitLoop'
    yield '01234'
  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
    return true if z < EPSILON
    ((a - b).abs / z) < EPSILON
  end
  def transform( sample)
    super( sample)
  rescue Errno::EDOM => details
    raise InputRangeException.new( sample, details.to_s)
  end
end
class SignLossyTransformation < FloatingPointTransformationTrait
  def fuzzy_equal?( a, b)
    super( a.abs, b.abs)
  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'
  require 'digest/md5'
  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 = SignLossyTransformation.new( Proc.new{|s| s*s})
      forward.sample_generator = [2]
      backward = DumbSqrtTrait.new
      uut.test( forward, backward)
      forward = SignLossyTransformation.new( Proc.new{|s| s*s})
      backward = FloatingPointTransformationTrait.new( Proc.new{|s| Math.sqrt(s)})
      uut.test( forward, backward)
    end
    def test_universality
      begin
        UUT.new.test(
                     FloatingPointTransformationTrait.new( Proc.new{|s| 2 * s + 42}),
                     FloatingPointTransformationTrait.new
                     )
        assert( false)
      rescue RuntimeError => details
        assert( details.to_s =~ %r{ differ }x)
      end
      UUT.new.test(
                   FloatingPointTransformationTrait.new( Proc.new{|s| 2 * s + 42}),
                   FloatingPointTransformationTrait.new( Proc.new{|s| (s - 42) / 2.0}))
      
    end
    def test_rot13
      UUT.new.test(
                   TransformationTrait.new( Proc.new{|s| s.tr( 'a-z', 'n-za-m')}, StringSampleGenerator.new),
                   TransformationTrait.new( Proc.new{|s| s.tr( 'n-za-m', 'a-z')}, StringSampleGenerator.new)
                   )
      
      src = "How can you tell an extrovert from an introvert at NSA? In the elevators, the extroverts look at the OTHER guy's shoes."
      src.downcase!
      crypt = "Ubj pna lbh gryy na rkgebireg sebz na vagebireg ng AFN? Va gur ryringbef, gur rkgebiregf ybbx ng gur BGURE thl'f fubrf."
      crypt.downcase!
      forward = TransformationTrait.new( Proc.new{|s| s.tr( 'a-z', 'n-za-m')}, StringSampleGenerator.new)
      forward.sample_generator = [src]
      UUT.new.test(
                   forward,
                   TransformationTrait.new( Proc.new{|s|
                                              raise InputRangeException, s unless s == crypt
                                              src},
                                            StringSampleGenerator.new)
                   )
      
    end
    def test_very_lossy
      UUT.new.test(
                   TransformationTrait.new( Proc.new{|s| Digest::MD5.new(s).hexdigest}, StringSampleGenerator.new),
                   TransformationTrait.new( Proc.new{|s|
                   UtterlyLossyTrait.new( Proc.new{|s| Digest::MD5.new(s).hexdigest}, StringSampleGenerator.new),
                   UtterlyLossyTrait.new( Proc.new{|s|
                                              raise InputRangeException, s unless
                                                s =~ %r{ ^[0-9a-f]+ $ }x
                                              s
                                            }, StringSampleGenerator.new)
                   )
      
    end
  end
end