Tdd for legacy code


Published on

Short introduction on how to mix TDD and working with legacy code for an effective process recipe.

Published in: Technology
  • Be the first to comment

No Downloads
Total Views
On Slideshare
From Embeds
Number of Embeds
Embeds 0
No embeds

No notes for slide

Tdd for legacy code

  1. 1. Test Driven Development in thecontext of large legacy codebases
  2. 2. Summary• TDD – general workflow• Applying TDD when working with legacy code• Methods for breaking dependencies in existing code• Adding and testing new features
  3. 3. Test Driven Development1. Write a failing test case. Design2. Get it to compile.3. Make it pass (do not Test Testchange existing code).4. Remove duplication. Implement5. Repeat.
  4. 4. Design• We need a method that parses some file and creates an instance of a class based on the parsed information.
  5. 5. Test[TestMethod()]public void ParseProcedureNameTest(){ var target = new TCPHandler(); var filePath = Path.GetFullPath("D:Projectspackagesdutconfigmain binwindResourcestest_procedures.tcp"); ITCPStructure tcpStructure = target.Parse(filePath); Assert.AreEqual(tcpStructure .Name, "test_procedures.tcp");}
  6. 6. Implementpublic 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;}
  7. 7. Test• Run the test• If the test fails, go back to implementation• If the test passes, continue.
  8. 8. Repeat! Design Test Test ImplementDesign:We need more information from the file….
  9. 9. 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.
  10. 10. 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.
  11. 11. Find the code you need to change Get this code in a test harness DesignTest Test Implement
  12. 12. 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
  13. 13. How do we do it?1. Identify change points.2. Find test points.3. Break dependencies.4. Write tests.5. Make changes and refactor.
  14. 14. 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 thetest code as maintainable as production code)
  15. 15. Dependency breaking techniques
  16. 16. 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 interfaceand the code gets cleaner
  17. 17. 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 ( != "") { varName.Append(; varName.Append("."); varName.Append(; ……
  18. 18. [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; } …………… }
  19. 19. interface ILightVariableInfo{ string Name { get; set; } string Group{ get; set; } object scalarValue { get; } IList vectorValue { get; } List<ILightVariableInfo> Statistics { get; set; }}
  20. 20. class LightVariableInfo : Ixia.TestComposer.Interpreter.api.ILightVariableInfo{ #region Constructors public LightVariableInfo(string i_name, string i_group, object i_scalarValue, IListi_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}
  21. 21. 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
  22. 22. 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
  23. 23. eRegressionRunResult CalculatePassFail(IRegressionRun i_run)=>class PassFailCalculator{ private IRegressionRun m_regressionRun; private int m_failedTestsCount; ... public PassFailCalculator(IRegressionRun i_run) { … } public eRegressionRunResult CalculateResult() { … }}
  24. 24. 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
  25. 25. public sealed partial class ModuleManager{ public static ModuleManager Instance{…} …}=>public sealed partial class ModuleManager{ public static ModuleManager Instance{…} public static void ResetInstance() { instance = null; }}
  26. 26. 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() }}
  27. 27. We need the change but we don’t have the time to refactor at all!
  28. 28. Sprout Method (Class)When the change can be formulated as a singlesequence of statements in one place in amethod• 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.
  29. 29. private List<string> GetVariablesMarkedForExport(IVariableListvarList, 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(convertToCsvGenera torStructure(subvar, var.fullName)); statisticNames.Add(var.fullName + "." + subvar.fullName); } } }
  30. 30. =>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;}
  31. 31. More info…• Working Effectively with Legacy Code by Martin Feathers• Clean Code – a handbook of agile software craftsmanship by Robert Martin
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.