Generating Characterization Tests for Legacy CodeJonas Follesø (@follesoe)JavaZone 2010, 09. September
Huge methods (~3000+ lines)
Dave & Karin http://www.flickr.com/photos/dnk_uk/3525103502/~50 slow integration tests
How the development team felt...
What they needed
What I read
Legacy code is code without tests.  Code without tests is bad code. -Michael C. Feathers Fraser Speirs http://www.flickr.com/photos/fraserspeirs/3395595360/
A characterization test is test that characterizes the actual behavior of a piece of code. It acts as a change detector, protecting legacy code form unintended changes
publicdoubleCalc(doubleinv, doublert, inty){doubleret=0;for (inti=1; i<=y; i++)    {ret=inv*Math.Pow(1.0+rt/100.0, i);    }returnret;}
[TestMethod]publicvoidCalc_characterization(){varcalc=newCalcUtil();doubleresult=calc.Calc(10000, 10, 10);Assert.AreEqual(42.0, result);            }
Assert.AreEqual failed. Expected:<42>. Actual:<25937.424601>.
[TestMethod]publicvoidCalc_characterization(){varcalc=newCalcUtil();doubleresult=calc.Calc(10000, 10, 10);    Assert.AreEqual(42, result);                Assert.AreEqual(25937.424601, result);            }
Test run completed. Results 1/1 passed.
publicdoubleCalculateCompoundInterest(doubleinvestment, doubleinterest, intyears){doubleprojectedValue=0.0;for (intyear=1; year<=years; year++)    {projectedValue=investment*Math.Pow(1.0+interest/100.0, year);    }returnprojectedValue;}
…A pinch point is a natural encapsulation boundary. When you find a pinch point, you’ve found a narrow funnel for all the effects of a large piece of code…-Michael C. Feathers Fraser Speirs http://www.flickr.com/photos/fraserspeirs/3395599536/
~85% code coveragehttp://www.flickr.com/photos/shuttercat7/713186211/
// This method is the "pinch point" we want to test...publicobjectCalculateSomething(objectsomeParameter){// 1) Record input parameters...// 2) Do the work...// 3) Write parameters and return value to XML file.}
[TestMethod]publicvoidCalculateSomething_test1(){Run("Recordings/CalculateSomething1.xml");}[TestMethod]publicvoidCalculateSomething_test2(){Run("Recordings/CalculateSomething2.xml");}publicvoidRun(stringfilename){// Load input parameters from XML file// Load return value from XML file// Call method under test// Use reflection to compare actual vs. recorded}
~300 fast characterization tests
How can we reuse this?
http://follesoe.github.com/BlackBoxRecorder
[Recording]
[Dependency]
[Recording]publicList<EmployeeEntity>GetMakingMoreThan(doublesalary){vardal=newEmployeeDAL();varemployees=dal.GetAllEmployees();returnemployees.Where(e=>e.Salary>salary).ToList();}
[Dependency]publicclassEmployeeDAL{publicList<EmployeeEntity>GetAllEmployees()    {// Calls to database...    }}
<Recording>  <Name>GetEmployeesMakingMoreThan_salary</Name>  <Method>GetEmployeesMakingMoreThan</Method>  <Type><![CDATA[BlackBox.Demo.App.SimpleAnemic.EmployeeBL]]></Type><InputParameters>    <Parameter>      <Name>salary</Name>      <Type><![CDATA[System.Double]]></Type>      <Value><![CDATA[5000]]></Value>    </Parameter>  </InputParameters>  <Return>    <Type><![CDATA[List<BlackBox.Demo.App.SimpleAnemic.EmployeeEntity>]]></Type>    <Value><![CDATA[XML representation of employees retruned from method]]></Value>  </Return>  <Dependencies>    <Dependency>      <Type><![CDATA[BlackBox.Demo.App.SimpleAnemic.EmployeeDAL]]></Type><Method>        <Name>GetAllEmployees</Name>        <ReturnValues>          <ReturnValue><Value><![CDATA[XML representation of all employees returned from db]]><Value>          </ReturnValue>        </ReturnValues>      </Method>    </Dependency></Dependencies></Recording>
[TestMethod]publicvoid GetEmployeesMakingMoreThan_salary(){    Run(@"GetEmployeesMakingMoreThan_salary.xml");}
http://github.com/oc/jackbox
@RecordingpublicintexampleMethod(intparameter, intparameter2) {returnparameter+parameter2;}@DependencypublicStringinvokedMethodOnDependency(Stringargument) {returnargument.toUpperCase();}
Jonas FollesøSenior Consultant+47 977 06660Jonas.folleso@bekk.no / jonas@follesoe.no

Generating characterization tests for legacy code

Editor's Notes

  • #29 Johannes BroadwallOle Christian RynningMarius B.KotsbakGeir Amdal