14 July 2011© Agile Institute 20111CaféTDD?Sure, but What About My Legacy Code?Rob MyersforTest Driven DevelopersBay Area14 Jul 2011
What is “Legacy Code”?14 July 2011© Agile Institute 20112
14 July 2011© Agile Institute 20113“Code we’re afraid to change.”-- James Shore“Code without tests.”-- Michael Feathers
14 July 2011© Agile Institute 20114
Adding Tests Is Difficult14 July 2011© Agile Institute 20115Circumstantial coupling: "I can’t test this without instantiating half the system!"Peculiar encapsulation:  “I wish I could just test that private method!”Duplication:"I'll have to test this on all the subclasses!"Poor cohesion: "This MasterControlClass does everything, so the test will be enormous and complex!"
A Conundrum14 July 2011© Agile Institute 20116We add tests to refactor safely.We have to refactor to add safe tests.
14 July 2011© Agile Institute 20117What could possibly go wrong?!
Choose Your BattlesTest the code you need to change now (unless it is very difficult)Test the code that changes most often (even if it is difficult)Test the code that haunts your nightmares!14 July 2011© Agile Institute 20118
Write “Pinning” TestsWe don’t add or change behavior.We’re not bug-hunting.We aim for coverage.We must see GREEN.14 July 2011© Agile Institute 20119
Technique: “The Three Questions”What behavior needs test coverage?What’s preventing us from writing a test?What can we do about that?14 July 2011© Agile Institute 201110
Technique:Separate Behavior from InitializationIt’s easier to test behavior when not coupled to initialization code.Use ‘Extract Method’ to preserve existing interfaces.14 July 2011© Agile Institute 201111
Before14 July 2011© Agile Institute 201112public void OpenOrderReport(OutputGadget output, string accountNumber) {    Account account =LunExData.QueryAccount(accountNumber);    Order[] openOrders =LunExData.QueryOrders(            "open", accountNumber, DateTime.Now);foreach (Order order in openOrders) {        // ...output.Write("Order #" + order.Number);        // ...    }}
After ‘Extract Method’14 July 2011© Agile Institute 201113public void OpenOrderReport(OutputGadget output, string accountNumber) {    Account account =LunExData.QueryAccount(accountNumber);    Order[] openOrders =LunExData.QueryOrders(            "open", accountNumber, DateTime.Now);FormatOpenOrderReport(output, account, openOrders);}private void FormatOpenOrderReport(OutputGadget output,    Account account, Order[] openOrders) {foreach (Order order in openOrders) {        // ...output.Write("Order #" + order.Number);        // ...
Technique:Expose A Private Variable or MethodMake a method public (at least accessible to tests)Wrap a variable in getter/setter methods.It may smell really bad.It will eventually be a useful addition to the interface, or will move to another object.14 July 2011© Agile Institute 201114
Before14 July 2011© Agile Institute 201115private void FormatOpenOrderReport(OutputGadget output,    Account account, Order[] openOrders) {foreach (Order order in openOrders) {        // ...output.Write("Order #" + order.Number);        // ...    }}
After14 July 2011© Agile Institute 201116public void FormatOpenOrderReport(OutputGadget output,    Account account, Order[] openOrders) {foreach (Order order in openOrders) {        // ...output.Write("Order #" + order.Number);        // ...    }}
Technique:Wrap, Then MockAdd a simple delegating Proxy to…Code we cannot change.Final (Java) or sealed (.NET) classes.Classes with no accessible constructors.Anything with a broad (mostly-unused) interface.Feathers’s “Adapt Parameter” is an example of this.14 July 2011© Agile Institute 201117
Delegating Proxy14 July 2011© Agile Institute 201118ObjectUnderTest+ MethodToTest()BrainlessProxyXYZZY+ intFoo()+ string Bar(int)+ intFoo()+ string Bar(int)+ double Baz()
A New Production Class14 July 2011© Agile Institute 201119public class BrainlessProxy {    private XYZZY realOne;    public BrainlessProxy(XYZZY theRealXYZZY) {this.realOne = theRealXYZZY;    }    public virtual intFoo() {        return realOne.Foo();    }    public virtual string Bar(int parameter) {        return realOne.Bar(parameter);    }}
Tough Problem (Simple Example)14 July 2011© Agile Institute 201120public class TrafficLight {    public void SwitchLights() {        Color newColor;        if (currentColor.IsRed())newColor = new Green();        else if (currentColor.IsGreen())newColor = new Yellow();        elsenewColor = new Red();currentColor.Flash();        Pause(3);currentColor.StopFlashing();newColor.LightUp();currentColor.Off();currentColor = newColor;    }
Technique:Factory-Method InjectionExtract a Factory Method for the dependency.Let the test create a “Testable” subclass of the Object Under Test.TestableFoo overrides the Factory Method and injects the mock.14 July 2011© Agile Institute 201121
Before ‘Extract Method’14 July 2011© Agile Institute 201122public void SwitchLights() {        Color newColor;        if (currentColor.IsRed())newColor = new Green();        else if (currentColor.IsGreen())newColor = new Yellow();        elsenewColor = new Red();currentColor.Flash();        Pause(3);currentColor.StopFlashing();newColor.LightUp();currentColor.Off();currentColor = newColor;    }
After ‘Extract Method’ part I14 July 2011© Agile Institute 201123public void SwitchLights() {    Color newColor;newColor = NextColor(currentColor);currentColor.Flash();    Pause(3);currentColor.StopFlashing();newColor.LightUp();currentColor.Off();currentColor = newColor;}
After ‘Extract Method’ Part II14 July 2011© Agile Institute 201124private Color NextColor(Color currentColor) {    Color newColor;    if (currentColor.IsRed())newColor = new Green();    else if (currentColor.IsGreen())newColor = new Yellow();    elsenewColor = new Red();    return newColor;}
A Small Change14 July 2011© Agile Institute 201125protected virtual Color NextColor(Color currentColor) {    Color newColor;    if (currentColor.IsRed())newColor = new Green();    else if (currentColor.IsGreen())newColor = new Yellow();    elsenewColor = new Red();    return newColor;}
The Test, Part 114 July 2011© Agile Institute 201126public static MockColornewColor;[TestMethod]public void TrafficLightSwitchTurnsNextColorOn() {newColor = new MockColor();TrafficLighttrafficLight = new TestableTrafficLight();trafficLight.SwitchLights();Assert.IsTrue(newColor.WasTurnedOn());}
The Test, Part 2Override Just  the Factory Method14 July 2011© Agile Institute 201127public class TestableTrafficLight : TrafficLight {    protected override Color NextColor(Color currentColor) {        return newColor;    }}public class MockColor : Color {    bool wasLit = false;    public override void LightUp() {        wasLit = true;    }    public bool WasTurnedOn() {        return wasLit;    }}
When to Use a Factory Method“I don’t know, but…”If creation of the dependency is buried deep within conditional code.If the Factory Method is (or can be) used in multiple places.If Dependency Injection seems worse (e.g. at a published API).14 July 2011© Agile Institute 201128
14 July 2011© Agile Institute 201129
14 July 2011© Agile Institute 201130
14 July 2011© Agile Institute 201131 Rob.Myers@agileInstitute.comhttp://PowersOfTwo.agileInstitute.com/

TDD? Sure, but What About My Legacy Code?

  • 1.
    14 July 2011©Agile Institute 20111CaféTDD?Sure, but What About My Legacy Code?Rob MyersforTest Driven DevelopersBay Area14 Jul 2011
  • 2.
    What is “LegacyCode”?14 July 2011© Agile Institute 20112
  • 3.
    14 July 2011©Agile Institute 20113“Code we’re afraid to change.”-- James Shore“Code without tests.”-- Michael Feathers
  • 4.
    14 July 2011©Agile Institute 20114
  • 5.
    Adding Tests IsDifficult14 July 2011© Agile Institute 20115Circumstantial coupling: "I can’t test this without instantiating half the system!"Peculiar encapsulation: “I wish I could just test that private method!”Duplication:"I'll have to test this on all the subclasses!"Poor cohesion: "This MasterControlClass does everything, so the test will be enormous and complex!"
  • 6.
    A Conundrum14 July2011© Agile Institute 20116We add tests to refactor safely.We have to refactor to add safe tests.
  • 7.
    14 July 2011©Agile Institute 20117What could possibly go wrong?!
  • 8.
    Choose Your BattlesTestthe code you need to change now (unless it is very difficult)Test the code that changes most often (even if it is difficult)Test the code that haunts your nightmares!14 July 2011© Agile Institute 20118
  • 9.
    Write “Pinning” TestsWedon’t add or change behavior.We’re not bug-hunting.We aim for coverage.We must see GREEN.14 July 2011© Agile Institute 20119
  • 10.
    Technique: “The ThreeQuestions”What behavior needs test coverage?What’s preventing us from writing a test?What can we do about that?14 July 2011© Agile Institute 201110
  • 11.
    Technique:Separate Behavior fromInitializationIt’s easier to test behavior when not coupled to initialization code.Use ‘Extract Method’ to preserve existing interfaces.14 July 2011© Agile Institute 201111
  • 12.
    Before14 July 2011©Agile Institute 201112public void OpenOrderReport(OutputGadget output, string accountNumber) { Account account =LunExData.QueryAccount(accountNumber); Order[] openOrders =LunExData.QueryOrders( "open", accountNumber, DateTime.Now);foreach (Order order in openOrders) { // ...output.Write("Order #" + order.Number); // ... }}
  • 13.
    After ‘Extract Method’14July 2011© Agile Institute 201113public void OpenOrderReport(OutputGadget output, string accountNumber) { Account account =LunExData.QueryAccount(accountNumber); Order[] openOrders =LunExData.QueryOrders( "open", accountNumber, DateTime.Now);FormatOpenOrderReport(output, account, openOrders);}private void FormatOpenOrderReport(OutputGadget output, Account account, Order[] openOrders) {foreach (Order order in openOrders) { // ...output.Write("Order #" + order.Number); // ...
  • 14.
    Technique:Expose A PrivateVariable or MethodMake a method public (at least accessible to tests)Wrap a variable in getter/setter methods.It may smell really bad.It will eventually be a useful addition to the interface, or will move to another object.14 July 2011© Agile Institute 201114
  • 15.
    Before14 July 2011©Agile Institute 201115private void FormatOpenOrderReport(OutputGadget output, Account account, Order[] openOrders) {foreach (Order order in openOrders) { // ...output.Write("Order #" + order.Number); // ... }}
  • 16.
    After14 July 2011©Agile Institute 201116public void FormatOpenOrderReport(OutputGadget output, Account account, Order[] openOrders) {foreach (Order order in openOrders) { // ...output.Write("Order #" + order.Number); // ... }}
  • 17.
    Technique:Wrap, Then MockAdda simple delegating Proxy to…Code we cannot change.Final (Java) or sealed (.NET) classes.Classes with no accessible constructors.Anything with a broad (mostly-unused) interface.Feathers’s “Adapt Parameter” is an example of this.14 July 2011© Agile Institute 201117
  • 18.
    Delegating Proxy14 July2011© Agile Institute 201118ObjectUnderTest+ MethodToTest()BrainlessProxyXYZZY+ intFoo()+ string Bar(int)+ intFoo()+ string Bar(int)+ double Baz()
  • 19.
    A New ProductionClass14 July 2011© Agile Institute 201119public class BrainlessProxy { private XYZZY realOne; public BrainlessProxy(XYZZY theRealXYZZY) {this.realOne = theRealXYZZY; } public virtual intFoo() { return realOne.Foo(); } public virtual string Bar(int parameter) { return realOne.Bar(parameter); }}
  • 20.
    Tough Problem (SimpleExample)14 July 2011© Agile Institute 201120public class TrafficLight { public void SwitchLights() { Color newColor; if (currentColor.IsRed())newColor = new Green(); else if (currentColor.IsGreen())newColor = new Yellow(); elsenewColor = new Red();currentColor.Flash(); Pause(3);currentColor.StopFlashing();newColor.LightUp();currentColor.Off();currentColor = newColor; }
  • 21.
    Technique:Factory-Method InjectionExtract aFactory Method for the dependency.Let the test create a “Testable” subclass of the Object Under Test.TestableFoo overrides the Factory Method and injects the mock.14 July 2011© Agile Institute 201121
  • 22.
    Before ‘Extract Method’14July 2011© Agile Institute 201122public void SwitchLights() { Color newColor; if (currentColor.IsRed())newColor = new Green(); else if (currentColor.IsGreen())newColor = new Yellow(); elsenewColor = new Red();currentColor.Flash(); Pause(3);currentColor.StopFlashing();newColor.LightUp();currentColor.Off();currentColor = newColor; }
  • 23.
    After ‘Extract Method’part I14 July 2011© Agile Institute 201123public void SwitchLights() { Color newColor;newColor = NextColor(currentColor);currentColor.Flash(); Pause(3);currentColor.StopFlashing();newColor.LightUp();currentColor.Off();currentColor = newColor;}
  • 24.
    After ‘Extract Method’Part II14 July 2011© Agile Institute 201124private Color NextColor(Color currentColor) { Color newColor; if (currentColor.IsRed())newColor = new Green(); else if (currentColor.IsGreen())newColor = new Yellow(); elsenewColor = new Red(); return newColor;}
  • 25.
    A Small Change14July 2011© Agile Institute 201125protected virtual Color NextColor(Color currentColor) { Color newColor; if (currentColor.IsRed())newColor = new Green(); else if (currentColor.IsGreen())newColor = new Yellow(); elsenewColor = new Red(); return newColor;}
  • 26.
    The Test, Part114 July 2011© Agile Institute 201126public static MockColornewColor;[TestMethod]public void TrafficLightSwitchTurnsNextColorOn() {newColor = new MockColor();TrafficLighttrafficLight = new TestableTrafficLight();trafficLight.SwitchLights();Assert.IsTrue(newColor.WasTurnedOn());}
  • 27.
    The Test, Part2Override Just the Factory Method14 July 2011© Agile Institute 201127public class TestableTrafficLight : TrafficLight { protected override Color NextColor(Color currentColor) { return newColor; }}public class MockColor : Color { bool wasLit = false; public override void LightUp() { wasLit = true; } public bool WasTurnedOn() { return wasLit; }}
  • 28.
    When to Usea Factory Method“I don’t know, but…”If creation of the dependency is buried deep within conditional code.If the Factory Method is (or can be) used in multiple places.If Dependency Injection seems worse (e.g. at a published API).14 July 2011© Agile Institute 201128
  • 29.
    14 July 2011©Agile Institute 201129
  • 30.
    14 July 2011©Agile Institute 201130
  • 31.
    14 July 2011©Agile Institute 201131 Rob.Myers@agileInstitute.comhttp://PowersOfTwo.agileInstitute.com/

Editor's Notes

  • #5 We want to convert the old tin can into <click>Why?
  • #7 It’s a compromise for quality’s sake.Unless you buy TypeMock ($$$). There is a down-side to TypeMock: If you can test without improving your design, will you *ever*…???
  • #8 Unplug the network!
  • #10 This isn’t TDD. But you won’t be able to proceed with TDD without these techniques.If we find a bug, record it: In a bug tracker, or as a story.Not isolation. These are not “unit tests” - These tests can cover as much behavior as possible (though they must still be deterministic and pretty fast). And they don’t have to be pretty!Pinning tests should pass.The test is trying to “pin” behavior so we can refactor further.
  • #13 What needs testing?
  • #14 Very easyLean on the compiler
  • #20 Production code!Can you test it?What good is it?
  • #21 Varying dependency creation ORDuplicated dependency creation OR both.
  • #23 Varying dependency creation ORDuplicated dependency creation OR both.
  • #25 Creation has been separated from use. JUST A LITTLE, BUT ENOUGH.
  • #26 What are we up to?
  • #29 Alex Chaffee and WilliamPietre
  • #32 Thank you!