Generating characterization tests for legacy code

Preview:

DESCRIPTION

Lightning talk from JavaZone 2010 on generating characterization tests for legacy code.

Citation preview

Generating Characterization Tests for Legacy Code

Jonas Follesø (@follesoe)

JavaZone 2010, 09. September

Huge methods (~3000+ lines)

Dav

e &

Kar

in h

ttp:

//w

ww

.flic

kr.c

om/p

hoto

s/dn

k_uk

/352

5103

502/

~50 slow integration tests

How the development team felt...

What they needed

What I read

Fras

er S

peirs

htt

p://

ww

w.fl

ickr

.com

/pho

tos/

fras

ersp

eirs

/339

5595

360/

Legacy code is code without tests.  Code without tests is bad code. 

-Michael C. Feathers

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

public double Calc(double inv, double rt, int y){ double ret = 0; for (int i = 1; i <= y; i++) { ret = inv * Math.Pow(1.0 + rt / 100.0, i); } return ret;}

[TestMethod]public void Calc_characterization(){ var calc = new CalcUtil(); double result = calc.Calc(10000, 10, 10);

Assert.AreEqual(42.0, result); }

Assert.AreEqual failed. Expected:<42>.

Actual:<25937.424601>.

[TestMethod]public void Calc_characterization(){ var calc = new CalcUtil(); double result = calc.Calc(10000, 10, 10);

Assert.AreEqual(42, result); Assert.AreEqual(25937.424601, result); }

Test run completed. Results 1/1 passed.

public double CalculateCompoundInterest(double investment, double interest, int

years){ double projectedValue = 0.0; for (int year = 1; year <= years; year++) { projectedValue = investment *

Math.Pow(1.0 + interest / 100.0, year); }

return projectedValue;}

Fras

er S

peirs

htt

p://

ww

w.fl

ickr

.com

/pho

tos/

fras

ersp

eirs

/339

5599

536/

…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

~85% code coverage

http:

//w

ww

.flic

kr.c

om/p

hoto

s/sh

utter

cat7

/713

1862

11/

// This method is the "pinch point" we want to test...public object CalculateSomething(object someParameter){ // 1) Record input parameters... // 2) Do the work... // 3) Write parameters and return value to XML file.}

[TestMethod]public void CalculateSomething_test1(){ Run("Recordings/CalculateSomething1.xml");}

[TestMethod]public void CalculateSomething_test2(){ Run("Recordings/CalculateSomething2.xml");}

public void Run(string filename){ // 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]public List<EmployeeEntity> GetMakingMoreThan(double salary){ var dal = new EmployeeDAL();

var employees = dal.GetAllEmployees();

return employees.Where(e => e.Salary > salary).ToList();}

[Dependency]public class EmployeeDAL{ public List<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]public void GetEmployeesMakingMoreThan_salary(){ Run(@"GetEmployeesMakingMoreThan_salary.xml");}

http://github.com/oc/jackbox

@Recordingpublic int exampleMethod(int parameter, int parameter2) { return parameter + parameter2;}

@Dependencypublic String invokedMethodOnDependency(String argument) { return argument.toUpperCase();}

BEKK CONSULTING ASSKUR 39, VIPPETANGEN. P.O. BOX 134 SENTRUM, 0102 OSLO, NORWAY.

WWW.BEKK.NO

Jonas FollesøSenior Consultant

+47 977 06660Jonas.folleso@bekk.no /

jonas@follesoe.no

Recommended