Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
William Munn, CGFWB - @dubmun
Overview Hands-on coding
 Seed Code is written in C#
 Get it from Github:
https://github.com/KatasForLegacyCode/kCSharp/archive/Step0.zip
 Any v...
Perception - COBOL
Perception - COBOL Reality – Code W/O Unit tests
Expectation Hyperbole
Code Kata
A code kata is an exercise in
programming which helps a us
hone our skills through practice
and repetition.
Th...
Legacy Dependency Kata: V2.0
 Written in C#
 Less tool dependencies
 Better theme
 Easier to read and follow
 Bug fix...
DoItAllTests.cs
Database.cs
DoItAll.cs
UserDetails.cs
ConsoleAdapter.cs
Program.cs
Logger.cs
Existing Files
We Will
Create
Let’s start by getting the existing test running!
 Running the integration test FAILS
 Begin by abstracting and breaking...
 Create new constructor to accept IConsoleAdapter & set private variable
private readonly IConsoleAdapter _consoleAdapter...
We have broken instantiations of DoItAll
 Instantiate and pass in our new handler
var doItAll = new DoItAll(new ConsoleAd...
 When the test is run we now get a meaningful exception. All of the
dependencies that caused the hang are gone.
 the tes...
 Following good testing practice,
let’s add an assert:
[Test, Category("Integration")]
public void DoItAll_Does_ItAll()
{...
Some of our legacy code has no coverage and produces no quantifiable results
 Copy the existing test and rename it DoItAl...
 Change Do()’s return type to string
public string Do()
 Add/update return statements in 3 locations:
public string Do()...
 Now create variables to hold the messages to return
public string Do()
{
[…]
if (_userDetails.PasswordEncrypted […])
{
v...
 Return the new values as appropriate
public string Do()
{
[…]
if (_userDetails.PasswordEncrypted […])
{
[…]
Console.Writ...
Abstract Console completely
 Add a new method stub
void SetOutput(string output);
 Update ConsoleAdapter implementation
...
DoItAll.Do() is trying to do too much. Let’s Refactor!
 Extract logging functionality by creating a new interface
public ...
 Extract the code in the try/catch block in DoItAll.Do()
into the implementation of Logger.LogMessage()
Make sure all pat...
 Update the constructor of DoItAll with an ILogger
public DoItAll(IOutputInputAdapter ioAdapter, ILogger logger)
{
_ioAda...
We have broken instantiations of DoItAll
 Instantiate and pass in our new handler
var doItAll = new DoItAll(new ConsoleAd...
 Copy the second test and rename the copy
DoItAll_Succeeds_WithMockLogging
 Update the copied assert
Assert.That(doItAll...
There is still plenty to refactor in this legacy code.
 What would you do next?
William Munn, CGFWB
@dubmun
dubmun
http://blog.dubmun.com
Resources
Michael Feathers
Roy Osherove
Robert C. Martin
Dave Th...
Legacy Dependency Kata v2.0
Legacy Dependency Kata v2.0
Upcoming SlideShare
Loading in …5
×

Legacy Dependency Kata v2.0

1,238 views

Published on

Having trouble wrapping you mind around unit testing in legacy code? Practice this kata and you'll have a good understanding of some basics. Break dependencies, inject stubs, write meaningful tests. Refactor with confidence. Version 2 is a complete overhaul to make the kata more readable and usable.

Published in: Software
  • Be the first to comment

  • Be the first to like this

Legacy Dependency Kata v2.0

  1. 1. William Munn, CGFWB - @dubmun
  2. 2. Overview Hands-on coding
  3. 3.  Seed Code is written in C#  Get it from Github: https://github.com/KatasForLegacyCode/kCSharp/archive/Step0.zip  Any version of Visual Studio 2012/2013/2015 that can run console apps and unit tests.  An nUnit test runner.  I recommend nCrunch.
  4. 4. Perception - COBOL
  5. 5. Perception - COBOL Reality – Code W/O Unit tests
  6. 6. Expectation Hyperbole
  7. 7. Code Kata A code kata is an exercise in programming which helps a us hone our skills through practice and repetition. The word kata is taken from Japanese arts most traditionally Martial Arts Why a code kata? Because we learn by doing.
  8. 8. Legacy Dependency Kata: V2.0  Written in C#  Less tool dependencies  Better theme  Easier to read and follow  Bug fixes
  9. 9. DoItAllTests.cs Database.cs DoItAll.cs UserDetails.cs ConsoleAdapter.cs Program.cs Logger.cs Existing Files We Will Create
  10. 10. Let’s start by getting the existing test running!  Running the integration test FAILS  Begin by abstracting and breaking the dependency on Console.Readline()  We’ll use an adapter, but we could use the Console.SetIn method and a TextReader.  Create an interface public interface IConsoleAdapter { string GetInput(); }  Create a class that implements the interface & implement method to handle our dependency public class ConsoleAdapter : IConsoleAdapter { public string GetInput() { return Console.ReadLine(); } } ConsoleAdapter.cs ConsoleAdapter.cs DoItAll.cs Did you know? In software engineering, the adapter pattern is a software design pattern that allows the interface of an existing class to be used as another interface.
  11. 11.  Create new constructor to accept IConsoleAdapter & set private variable private readonly IConsoleAdapter _consoleAdapter; public DoItAll(IConsoleAdapter consoleAdapter) { _consoleAdapter = consoleAdapter; }  Replace 4 calls to Console.ReadLine() WITH _consoleAdapter.GetInput()  Replace 2 calls to Console.ReadKey() WITH _consoleAdapter.GetInput() DoItAll.cs DoItAll.cs DoItAll.cs
  12. 12. We have broken instantiations of DoItAll  Instantiate and pass in our new handler var doItAll = new DoItAll(new ConsoleAdapter()); Let’s update our integration test or it will also fail.  Create a new implementation of IConsoleAdapter public class DoItAllTests { […] var doItAll = new DoItAll(new FakeConsoleAdapter()); […] } public class FakeConsoleAdapter : IConsoleAdapter { public string GetInput() { return string.Empty; } } Program.cs DoItAllTests.cs
  13. 13.  When the test is run we now get a meaningful exception. All of the dependencies that caused the hang are gone.  the test is now failing because the password is empty. This is a meaningful case but let’s just update our fake for now. public string GetInput() { return “someTestString”; }  Run the test AND IT SHOULD BE GREEN! DoItAllTests.cs
  14. 14.  Following good testing practice, let’s add an assert: [Test, Category("Integration")] public void DoItAll_Does_ItAll() { var doItAll = new DoItAll(new FakeConsoleAdapter()); Assert.That(() => doItAll.Do(), Throws.Nothing); }  Our test should still pass. DoItAllTests.cs
  15. 15. Some of our legacy code has no coverage and produces no quantifiable results  Copy the existing test and rename it DoItAll_Fails_ToWriteToDB [Test, Category("Integration")] public void DoItAll_Does_ItAll() { […] } [Test, Category("Integration")] public void DoItAll_Fails_ToWriteToDB() { […] }  Change the assert Assert.That(doItAll.Do(), Is.StringContaining("Database.SaveToLog Exception:”));  Build will fail because our Do() method returns void. DoItAllTests.cs DoItAllTests.cs
  16. 16.  Change Do()’s return type to string public string Do()  Add/update return statements in 3 locations: public string Do() { […] if (_userDetails.PasswordEncrypted […]) { […] return string.Empty; } […] { […] using (var writer = new StreamWriter("log.txt", true)) { […] return string.Empty; } } […] return string.Empty; } DoItAll.cs DoItAll.cs
  17. 17.  Now create variables to hold the messages to return public string Do() { […] if (_userDetails.PasswordEncrypted […]) { var passwordsDontMatch = "The passwords don't match."; […] } […] { […] using (var writer = new StreamWriter("log.txt", true)) { var errorMessage = string.Format("{0} - Database.SaveToLog Exception: rn{1}", message, ex.Message); […] } } […] } DoItAll.cs
  18. 18.  Return the new values as appropriate public string Do() { […] if (_userDetails.PasswordEncrypted […]) { […] Console.WriteLine(passwordsDontMatch); Console.ReadKey(); return passwordsDontMatch; } […] { […] using (var writer = new StreamWriter("log.txt", true)) { […] writer.WriteLine(errorMessage); return errorMessage; } } }  Our new DoItAll_Fails_ToWriteToDB() test should pass DoItAll.cs
  19. 19. Abstract Console completely  Add a new method stub void SetOutput(string output);  Update ConsoleAdapter implementation public void SetOutput(string output) { Console.WriteLine(output); }  Update FakeConsoleAdapter implementation public void SetOutput(string output) {}  Update DoItAll Implementation Replace 6 instances of Console.WriteLine AND Console.Write WITH _consoleAdapter.SetOutput ConsoleAdapter.cs ConsoleAdapter.cs DoItAllTests.cs DoItAll.cs OUR TESTS SHOULD STILL PASS
  20. 20. DoItAll.Do() is trying to do too much. Let’s Refactor!  Extract logging functionality by creating a new interface public interface ILogger { string LogMessage(string message); }  Create a new class implementing ILogger public class Logger : ILogger { public string LogMessage(string message) { throw new NotImplementedException(); } }  Now extract the code in the try/catch block in DoItAll.Do() into the implementation of ILogger.LogMessage()  Make sure all paths return a message  Now update the constructor of DoItAll with an ILogger AND REPLACE TRY/CATCH: message = _logger.LogMessage(message); Logger.cs Logger.cs
  21. 21.  Extract the code in the try/catch block in DoItAll.Do() into the implementation of Logger.LogMessage() Make sure all paths return a message public string LogMessage(string message) { try { Database.SaveToLog(message); } catch (Exception ex) { // If database write fails, write to file using (var writer = new StreamWriter("log.txt", true)) { message = message + "nDatabase.SaveToLog Exception: " + ex.Message; writer.WriteLine(message); } } return message; } Logger.cs
  22. 22.  Update the constructor of DoItAll with an ILogger public DoItAll(IOutputInputAdapter ioAdapter, ILogger logger) { _ioAdapter = ioAdapter; _logger = logger; } […] private readonly ILogger _logger;  Update the Do() method public string Do() { […] _ioAdapter.SetOutput(message); message = _logger.LogMessage(message); return message; } DoItAll.cs DoItAll.cs
  23. 23. We have broken instantiations of DoItAll  Instantiate and pass in our new handler var doItAll = new DoItAll(new ConsoleAdapter(), new Logger()); Let’s update our tests or they will also fail.  Create a new implementation of ILogger public class DoItAllTests { […] var doItAll = new DoItAll(new FakeConsoleAdapter(), new FakeLogger()); […] } […] public class FakeLogger : ILogger { public string LogMessage( string message) { return string.Empty; } } Program.cs DoItAllTests.cs OUR FIRST TEST PASSES, BUT THE SECOND FAILS
  24. 24.  Copy the second test and rename the copy DoItAll_Succeeds_WithMockLogging  Update the copied assert Assert.That(doItAll.Do(), Is.EqualTo(string.Empty));  Let the original test remain an integration test by depending on Logger and it will pass as well DoItAllTests.cs DoItAllTests.cs
  25. 25. There is still plenty to refactor in this legacy code.  What would you do next?
  26. 26. William Munn, CGFWB @dubmun dubmun http://blog.dubmun.com Resources Michael Feathers Roy Osherove Robert C. Martin Dave Thomas & Andy Hunt Legacy Dependency Kata – V2.0

×