You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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
classWheelattr_reader:rim,:tiredefinitialize(rim,tire)@rim=rim@tire=tireenddefdiameterrim + (tire * 2)endend# 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.classGearattr_reader:chainring,:cog,:rim,:tiredefinitialize(args)@chainring=args[:chainring]@cog=args[:cog]@rim=args[:rim]@tire=args[:tire]enddefgear_inchesratio * Wheel.new(rim,tire).diameterenddefratiochainring / cog.to_fend# ...endclassWheelTest < MiniTest::Unit::TestCasedeftest_calculates_diameterwheel=Wheel.new(26,1.5)assert_in_delta(29,wheel.diameter,0.01)endendclassGearTest < MiniTest::Unit::TestCasedeftest_calculates_gear_inchesgear=Gear.new(chainring: 52,cog: 11,rim: 26,tire: 1.5)assert_in_delta(137.1,gear.gear_inches,0.01)endend
# 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.classGearattr_reader:chainring,:cog,:wheeldefinitialize(args)@chainring=args[:chainring]@cog=args[:cog]@wheel=args[:wheel]enddefgear_inchesratio * wheel.diameterend# ...end# If Wheels are cheap, injecting an actual Wheel has little negative effect on your tests.classGearTest < MiniTest::Unit::TestCasedeftest_calculates_gear_inchesgear=Gear.new(chainring: 52,cog: 11,wheel: Wheel.new(26,1.5))assert_in_delta(137.1,gear.gear_inches,0.01)endend
# duck type# True object is expensive vs Fake object is like a dreamclassDiameterDoubledefdiameter10endendclassGearTest < MiniTest::Unit::TestCasedeftest_calculates_gear_inchesgear=Gear.new(chainring: 52,cog: 11,wheel: DiameterDouble.new)assert_in_delta(47.27,gear.gear_inches,0.01)endend
# Change Wheel interfaceclassWheelattr_reader:rim,:tiredefinitialize(rim,tire)@rim=rim@tire=tireenddefwidth# <---- used to be 'diameter'rim + (tire * 2)end# ...end
moduleDiameterizableInterfaceTestdeftest_implements_the_diameterizable_interfaceassert_respond_to(@object,:width)endendclassWheelTest < MiniTest::Unit::TestCaseincludeDiameterizableInterfaceTest
...
endclassDiameterDoubleTest < MiniTest::Unit::TestCaseincludeDiameterizableInterfaceTest
...
end