Skip to content

Instantly share code, notes, and snippets.

@ilake
Last active December 20, 2015 15:39
Show Gist options
  • Save ilake/6156020 to your computer and use it in GitHub Desktop.
Save ilake/6156020 to your computer and use it in GitHub Desktop.

Why We Design

  • If this application need never change. design does not matter.
  • In the absence of design, unmanaged dependencies. Changing one object forces change upon its collaborators, which in turn, forces change upon its collaborators, ad infinitum

Judging Design

  • Easy to change.

How...

  • Classes with a Single Responsibility
  • Managing Dependencies
  • Duck Typing
  • Inheritance
  • Sharing Role Behavior with Modules

Designing Cost-Effective Tests

intentional Testing

  • art of writing changeable code requires the ability to write high-value tests.
  • The true purpose of testing, just like the true purpose of design, is to reduce costs
  • Good design naturally progresses toward small independent objects that rely on abstractions.
  • If a test requires painful setup, the code expects too much context. If testing one object drags a bunch of others into the mix, the code has too many dependencies. If the test is hard to write, other objects will find the code difficult to reuse.
  • Good testing should lead you where to cause the issue.

What to test

  • The safest way to accomplish this is to test everything just once and in the proper place. putting tests in the right place guarantees they’ll be forced to change only when absolutely necessary

Coooooode

class Wheel
  attr_reader :rim, :tire
  def initialize(rim, tire)
    @rim       = rim
    @tire      = tire
  end

  def diameter
    rim + (tire * 2)
  end
end

# Gear and Wheel are coupled over here
# Wheel might be expensive to create
# Gear will pay the cost when wheel is broken, Gear will also fail. and that will be misleading.
class Gear
  attr_reader :chainring, :cog, :rim, :tire
  def initialize(args)
    @chainring = args[:chainring]
    @cog       = args[:cog]
    @rim       = args[:rim]
    @tire      = args[:tire]
  end

  def gear_inches
    ratio * Wheel.new(rim, tire).diameter
  end

  def ratio
    chainring / cog.to_f
  end
# ...
end

class WheelTest < MiniTest::Unit::TestCase

  def test_calculates_diameter
    wheel = Wheel.new(26, 1.5)

    assert_in_delta(29,
                    wheel.diameter,
                    0.01)
  end
end

class GearTest < MiniTest::Unit::TestCase

  def test_calculates_gear_inches
    gear =  Gear.new(
              chainring: 52,
              cog:       11,
              rim:       26,
              tire:      1.5 )

    assert_in_delta(137.1,
                    gear.gear_inches,
                    0.01)
  end
end
# broke the binding with Wheel
# Gear now expects to be injected with an object that understands diameter.
# Gear no longer cares about the class of the injected object
# Free your imagination, you are not testing Wheel, you are testing a Diamerterizable have diameter interface.

class Gear
  attr_reader :chainring, :cog, :wheel
  def initialize(args)
    @chainring = args[:chainring]
    @cog       = args[:cog]
    @wheel     = args[:wheel]
  end

  def gear_inches
    ratio * wheel.diameter
  end

# ...
end

# If Wheels are cheap, injecting an actual Wheel has little negative effect on your tests.
class GearTest < MiniTest::Unit::TestCase
  def test_calculates_gear_inches
    gear =  Gear.new(
              chainring: 52,
              cog:       11,
              wheel:     Wheel.new(26, 1.5))

    assert_in_delta(137.1,
                    gear.gear_inches,
                    0.01)
  end
end
# duck type
# True object is expensive vs Fake object is like a dream
class DiameterDouble
  def diameter
    10
  end
end

class GearTest < MiniTest::Unit::TestCase
  def test_calculates_gear_inches
    gear =  Gear.new(
              chainring: 52,
              cog:       11,
              wheel:     DiameterDouble.new)

    assert_in_delta(47.27,
                    gear.gear_inches,
                    0.01)
  end
end
class WheelTest < MiniTest::Unit::TestCase
  def setup
    @wheel = Wheel.new(26, 1.5)
  end

  def test_implements_the_diameterizable_interface
    assert_respond_to(@wheel, :diameter)
  end

  def test_calculates_diameter
    wheel = Wheel.new(26, 1.5)

    assert_in_delta(29,
                    wheel.diameter,
                    0.01)
  end
end
# Change Wheel interface
class Wheel
  attr_reader :rim, :tire
  def initialize(rim, tire)
    @rim       = rim
    @tire      = tire
  end

  def width   # <---- used to be 'diameter'
    rim + (tire * 2)
  end
# ...
end
module DiameterizableInterfaceTest
  def test_implements_the_diameterizable_interface
   assert_respond_to(@object, :width)
  end
end

class WheelTest < MiniTest::Unit::TestCase
  include DiameterizableInterfaceTest
  ...
end

class DiameterDoubleTest < MiniTest::Unit::TestCase
  include DiameterizableInterfaceTest
  ...
 end

References

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment