tdd for legacy code
DESCRIPTION
Short introduction on how to mix TDD and working with legacy code for an effective process recipe.TRANSCRIPT
![Page 1: Tdd for legacy code](https://reader034.vdocuments.net/reader034/viewer/2022052619/555cab64d8b42ab2358b4e1c/html5/thumbnails/1.jpg)
Test Driven Development in the context of large legacy codebases
![Page 2: Tdd for legacy code](https://reader034.vdocuments.net/reader034/viewer/2022052619/555cab64d8b42ab2358b4e1c/html5/thumbnails/2.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022052619/555cab64d8b42ab2358b4e1c/html5/thumbnails/3.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022052619/555cab64d8b42ab2358b4e1c/html5/thumbnails/4.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022052619/555cab64d8b42ab2358b4e1c/html5/thumbnails/5.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022052619/555cab64d8b42ab2358b4e1c/html5/thumbnails/6.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022052619/555cab64d8b42ab2358b4e1c/html5/thumbnails/7.jpg)
Test
• Run the test• If the test fails, go back to implementation• If the test passes, continue.
![Page 8: Tdd for legacy code](https://reader034.vdocuments.net/reader034/viewer/2022052619/555cab64d8b42ab2358b4e1c/html5/thumbnails/8.jpg)
Repeat!
Design:We need more information from the file….
Design
Implement
TestTest
![Page 9: Tdd for legacy code](https://reader034.vdocuments.net/reader034/viewer/2022052619/555cab64d8b42ab2358b4e1c/html5/thumbnails/9.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022052619/555cab64d8b42ab2358b4e1c/html5/thumbnails/10.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022052619/555cab64d8b42ab2358b4e1c/html5/thumbnails/11.jpg)
Design
Implement
Test
Find the code you need to change
Get this code in a test harness
Test
![Page 12: Tdd for legacy code](https://reader034.vdocuments.net/reader034/viewer/2022052619/555cab64d8b42ab2358b4e1c/html5/thumbnails/12.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022052619/555cab64d8b42ab2358b4e1c/html5/thumbnails/13.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022052619/555cab64d8b42ab2358b4e1c/html5/thumbnails/14.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022052619/555cab64d8b42ab2358b4e1c/html5/thumbnails/15.jpg)
Dependency breaking techniques
![Page 16: Tdd for legacy code](https://reader034.vdocuments.net/reader034/viewer/2022052619/555cab64d8b42ab2358b4e1c/html5/thumbnails/16.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022052619/555cab64d8b42ab2358b4e1c/html5/thumbnails/17.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022052619/555cab64d8b42ab2358b4e1c/html5/thumbnails/18.jpg)
[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](https://reader034.vdocuments.net/reader034/viewer/2022052619/555cab64d8b42ab2358b4e1c/html5/thumbnails/19.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022052619/555cab64d8b42ab2358b4e1c/html5/thumbnails/20.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022052619/555cab64d8b42ab2358b4e1c/html5/thumbnails/21.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022052619/555cab64d8b42ab2358b4e1c/html5/thumbnails/22.jpg)
![Page 23: Tdd for legacy code](https://reader034.vdocuments.net/reader034/viewer/2022052619/555cab64d8b42ab2358b4e1c/html5/thumbnails/23.jpg)
![Page 24: Tdd for legacy code](https://reader034.vdocuments.net/reader034/viewer/2022052619/555cab64d8b42ab2358b4e1c/html5/thumbnails/24.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022052619/555cab64d8b42ab2358b4e1c/html5/thumbnails/25.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022052619/555cab64d8b42ab2358b4e1c/html5/thumbnails/26.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022052619/555cab64d8b42ab2358b4e1c/html5/thumbnails/27.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022052619/555cab64d8b42ab2358b4e1c/html5/thumbnails/28.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022052619/555cab64d8b42ab2358b4e1c/html5/thumbnails/29.jpg)
We need the change but we don’t have the time to refactor at all!
![Page 30: Tdd for legacy code](https://reader034.vdocuments.net/reader034/viewer/2022052619/555cab64d8b42ab2358b4e1c/html5/thumbnails/30.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022052619/555cab64d8b42ab2358b4e1c/html5/thumbnails/31.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022052619/555cab64d8b42ab2358b4e1c/html5/thumbnails/32.jpg)
=>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](https://reader034.vdocuments.net/reader034/viewer/2022052619/555cab64d8b42ab2358b4e1c/html5/thumbnails/33.jpg)
More info…
• Working Effectively with Legacy Code by Martin Feathers
• Clean Code – a handbook of agile software craftsmanship by Robert Martin