Download pptx - Tdd for legacy code

Transcript
Page 1: Tdd for legacy code

Test Driven Development in the context of large legacy codebases

Page 2: Tdd for legacy code

Summary

• TDD – general workflow• Applying TDD when working with legacy code• Methods for breaking dependencies in

existing code• Adding and testing new features

Page 3: Tdd for legacy code

Test Driven Development

1. Write a failing test case.2. Get it to compile.3. Make it pass (do notchange existing code). 4. Remove duplication.5. Repeat.

Design

Implement

TestTest

Page 4: Tdd for legacy code

Design

• We need a method that parses some file and creates an instance of a class based on the parsed information.

Page 5: Tdd for legacy code

Test [TestMethod()] public void ParseProcedureNameTest() { var target = new TCPHandler();

var filePath = Path.GetFullPath("D:\\Projects\\packages\\dutconfig\\main\\ bin\\wind\\Resources\\test_procedures.tcp");

ITCPStructure tcpStructure = target.Parse(filePath); Assert.AreEqual(tcpStructure .Name, "test_procedures.tcp"); }

Page 6: Tdd for legacy code

Implement public static TCPStructure Parse(string summaryFilePath) { TCPStructure tcpStructure = new TCPStructure();

reader = new StreamReader(summaryFilePath);

XmlSerializer tcpSerializer = new XmlSerializer(typeof(ComposerTestStorageSummary));

ComposerTestStorageSummary storageSummaryBase = (ComposerTestStorageSummary)tcpSerializer.Deserialize(reader);

//main procedure name tcpStructure.Name = storageSummaryBase.Name;

return tcpStructure; }

Page 7: Tdd for legacy code

Test

• Run the test• If the test fails, go back to implementation• If the test passes, continue.

Page 8: Tdd for legacy code

Repeat!

Design:We need more information from the file….

Design

Implement

TestTest

Page 9: Tdd for legacy code

Three laws of TDD

• First Law You may not write production code until you have written a failing unit test.

• Second Law You may not write more of a unit test than is sufficient to fail, and not compiling is failing.

• Third Law You may not write more production code than is sufficient to pass the currently failing test.

Page 10: Tdd for legacy code

TDD and Legacy Code

• 0. Get the code you want to change under test.• 1. Write a failing test case.• 2. Get it to compile.• 3. Make it pass. (Try not to change existing

code as you do this.)• 4. Remove duplication.• 5. Repeat.

Page 11: Tdd for legacy code

Design

Implement

Test

Find the code you need to change

Get this code in a test harness

Test

Page 12: Tdd for legacy code

Coping with Legacy Code

• Legacy code = code without tests• When we change code, we should have tests

in place. To put tests in place, we often have to change code => the legacy code dilemma

Page 13: Tdd for legacy code

How do we do it?

1. Identify change points.2. Find test points.3. Break dependencies.4. Write tests.5. Make changes and refactor.

Page 14: Tdd for legacy code

Break dependencies when..

• A method cannot be easily run in a test harness

• A class cannot be easily instantiated in a test harness

=>Fakes get too complex (we need to keep the test code as maintainable as production code)

Page 15: Tdd for legacy code

Dependency breaking techniques

Page 16: Tdd for legacy code

Extract Interface

• Create a new interface• Make the class that you are extracting from

implement this interface• Change the calling methods so that they use

this interface instead of the original class=> fakes can implement a much easier interface and the code gets cleaner

Page 17: Tdd for legacy code

private string BuildCompositeExpression(VariableInfo variable){ … foreach (VariableInfo varInfo in variable.statistics) { if (varInfo != null) { // create tcl variable value if (varInfo.scalarValue != null) { varValue.Append(formatVariableValueForTcl(varInfo.scalarValue.ToString())); } else if (varInfo.vectorValue != null) { … foreach (Object element in varInfo.vectorValue) { varValue.AppendFormat(“{0} ”,formatVariableValueForTcl(element.ToString())); } } if (varInfo.group != "") { varName.Append(variable.name); varName.Append("."); varName.Append(varInfo.group); ……

Page 18: Tdd for legacy code

[Serializable]

public class VariableInfo : Value, ILightVariableInfo

{

public VariableInfo();

public VariableInfo(eValueType i_valueType);

public VariableInfo(string i_name, eValueType i_valueType);

public VariableInfo(VariableInfo i_variableInfo);

public VariableInfo(IVariableInfo i_varInfo);

public VariableInfo(IValue i_value);

public VariableInfo(IComposite i_composite);

public virtual string name { get; set; }

public virtual string description { get; set; }

public virtual string units { get; set; }

public virtual string group { get; set; }

public virtual string fullName { get; }

public eStatisticType statisticType { get; set; }

public List<VariableInfo> statistics { get; set; }

public new eValueType valueType { get; set; }

  ……………

}

Page 19: Tdd for legacy code

interface ILightVariableInfo{ string Name { get; set; } string Group{ get; set; } object scalarValue { get; } IList vectorValue { get; } List<ILightVariableInfo> Statistics { get; set; }}

Page 20: Tdd for legacy code

class LightVariableInfo : Ixia.TestComposer.Interpreter.api.ILightVariableInfo{ #region Constructors

public LightVariableInfo(string i_name, string i_group, object i_scalarValue, IList i_vectorValue) { name = i_name; group = i_group; scalarValue = i_scalarValue; vectorValue = i_vectorValue; }

#endregion

#region ILightVariableInfo Members

public List<ILightVariableInfo> Statistics { get; set; }

public string group {get; set; }

public string name {get; set; }

public object scalarValue{get; private set; } public IList vectorValue {get; private set; }

#endregion}

Page 21: Tdd for legacy code

Subclass and Override

• Find the smallest set of methods that you need to override to achieve the test’s goal

• Make each of these methods overridable. If required, adjust visibility.

• Create a subclass that overrides these methods and implement the behavior you need

=> simple Fake objects

Page 22: Tdd for legacy code
Page 23: Tdd for legacy code
Page 24: Tdd for legacy code

Break Out Method Object

• Extract the method in a new class. • Define a constructor for this class with the

same signature as the method => parameters become class members

• Lean on the compiler• Replace the code in the original method to use

an instance of the new class.=> Tests are easier to write

Page 25: Tdd for legacy code

eRegressionRunResult CalculatePassFail(IRegressionRun i_run)

=>

class PassFailCalculator{ private IRegressionRun m_regressionRun; private int m_failedTestsCount; ...

public PassFailCalculator(IRegressionRun i_run) { … } public eRegressionRunResult CalculateResult() { … }}

Page 26: Tdd for legacy code

Introduce Static Setter

• Test == mini-application: totally isolated from the other tests => we need to relax the singleton property– Introduce static setter– Add a new method that resets the singleton

property and gets called when the tests are being initialized

Page 27: Tdd for legacy code

public sealed partial class ModuleManager{ public static ModuleManager Instance{…} …}=>public sealed partial class ModuleManager{ public static ModuleManager Instance{…} public static void ResetInstance() { instance = null; }}

Page 28: Tdd for legacy code

public sealed partial class ModuleManager{ private ModuleManager() { … } public static ModuleManager Instance{…} …}=> public sealed partial class ModuleManager{ public static ModuleManager Instance{…} public static void SetInstance (ModuleManager i_instance) { instance = i_instance; }

public static ModuleManager CreateNewInstance() { return new ModuleManager() }}

Page 29: Tdd for legacy code

We need the change but we don’t have the time to refactor at all!

Page 30: Tdd for legacy code

Sprout Method (Class)

When the change can be formulated as a single sequence of statements in one place in a method• Identify where you need to make a change• Create a new method; required local variables

become arguments for this method.• Develop the method using TDD.• Make a call to this new method where the

change is needed.

Page 31: Tdd for legacy code

private List<string> GetVariablesMarkedForExport(IVariableList varList, ref StatisticsCsvGenerator csvGenerator) { List<string> statisticNames = new List<string>(); foreach (VariableInfo var in varList) { if (var.Export) { if (var.statistics.Count == 0) { csvGenerator.SetStatisticValues (convertToCsvGeneratorStructure(var, string.Empty)); statisticNames.Add(var.fullName); } CheckStatisticsForExport(var, ref csvGenerator); } } }

if (var.statistics.Count > 0) { foreach (VariableInfo subvar in var.statistics) { if (subvar.Export) { statisticsCsvGenerator.SetStatisticValues(convertToCsvGeneratorStructure(subvar, var.fullName)); statisticNames.Add(var.fullName + "." + subvar.fullName); } }}

Page 32: Tdd for legacy code

=>private List<string> CheckStatisticsForExport(VariableInfo var,

ref StatisticsCsvGenerator statisticsCsvGenerator) {List<string> result = new List<string>(); if (var.statistics.Count > 0) { foreach (VariableInfo subvar in var.statistics) { if (subvar.Export) {…} } return result;}

Page 33: Tdd for legacy code

More info…

• Working Effectively with Legacy Code by Martin Feathers

• Clean Code – a handbook of agile software craftsmanship by Robert Martin