SlideShare a Scribd company logo
Unit testing
legacy code
Lars Thorup
ZeaLake Software Consulting
May, 2014
Who is Lars Thorup?
● Software developer/architect
● JavaScript, C#
● Test Driven Development
● Continuous Integration
● Coach: Teaching TDD and
continuous integration
● Founder of ZeaLake
● @larsthorup
The problems with legacy code
● No tests
● The code probably works...
● Hard to refactor
● Will the code still work?
● Hard to extend
● Need to change the code...
● The code owns us :(
● Did our investment turn sour?
How do tests bring us back in control?
● A refactoring improves the design without changing
behavior
● Tests ensure that behavior is not
accidentally changed
● Without tests, refactoring is scary
● and with no refactoring, the design decays over time
● With tests, we have the courage to refactor
● so we continually keep our design healthy
What comes first: the test or the refactoring?
How do we get to sustainable legacy code?
● Make it easy to add characterization tests
● Have good unit test coverage for important areas
● Don't worry about code you don't need to change
● Test-drive all new code
● Now we own the code :)
Making legacy code sustainable
● Select an important area
● Driven by change requests
● Add characterization tests
● Make code testable
● Refactor the code
● Add unit tests
● Remove characterization tests
● Small steps
Characterization tests
● Characterize current
behavior
● Integration tests
● Either high level unit tests
● Or end-to-end tests
● Don't change existing code
● Faulty behavior = current
behavior: don't change it!
● Make a note to fix later
● Test at a level that makes it
easy
● The characterization tests
are throw-aways
● Demo:
● Web service test: VoteMedia
● End-to-end browser test:
entrylist.demo.test.js
Make code testable
● Avoid large methods
● They require a ton of setup
● They require lots of scenarios to cover all variations
● Avoid outer scope dependencies
● They require you to test at a higher level
● Avoid external dependencies
● ... a ton of setup
● They slow you down
Refactor the code
● Add interface
● Inject a mock instead of the
real thing
● Easier setup
● Infinitely faster
Notifier
EmailSvc
IEmailSvc
EmailSvcStub
NotifierTest
● Extract method
● Split up large methods
● To simplify unit testing single
behaviors
● Demo:
VoteWithVideo_Vimas
● Add parameter
● Pass in outer-scope
dependencies
● The tests can pass in their
own dummy values
● Demo:
Entry.renderResponse
Add unit tests
● Now that the code is testable...
● Write unit tests for you small methods
● Pass in dummy values for parameters
● Mock dependencies
● Rinse and repeat...
Remove the characterization tests
● When unit test code coverage is good enough
● To speed up feedback
● To avoid test duplication
Small steps - elephant carpaccio
● Any big refactoring...
● ...can be done in small steps
● Demo: Security system (see slide 19 through 31)
Test-drive all new code
● Easy, now that unit testing tools are in place
Failing
test
Succeeding
test
Good
design Refactor
Test
Intention
Think, talk
Code
Making legacy code sustainable
● Select an important area
● Driven by change requests
● Add characterization tests
● Make code testable
● Refactor the code
● Add unit tests
● Remove characterization tests
● Small steps
It's not hard - now go do it!
● This is hard
● SQL query efficiency
● Cache invalidation
● Scalability
● Pixel perfect rendering
● Cross-browser compatibility
● Indexing strategies
● Security
● Real time media streaming
● 60fps gaming with HTML5
● ... and robust Selenium tests!
● This is not hard
● Refactoring
● Unit testing
● Dependency injection
● Automated build and test
● Continuous Integration
● Fast feedback will make
you more productive
● ... and more happy
A big refactoring is needed...
Avoid feature branches
● For features as well as large refactorings
● Delayed integration
● Increases risk
● Increases cost
Use feature toggles
● Any big refactoring...
● ...can be done in small
steps
● Allows us to keep
development on
trunk/master
● Drastically lowering the risk
● Commit after every step
● At most a couple of hours
Security example
● Change the code from the
old security system
● To our new extended
security model
interface IPrivilege
{
bool HasRole(Role);
}
class Permission
{
bool IsAdmin();
}
Step 0: existing implementation
● Code instantiates
Legacy.Permission
● and calls methods like
permission.IsAdmin()
● ...all over the place
● We want to replace this
with a new security system
void SomeController()
{
var p = new Permission();
if (p.IsAdmin())
{
...
}
}
Step 1: New security implementation
● Implements an interface
● This can be committed
gradually
interface IPrivilege
{
bool HasRole(Role);
}
class Privilege : IPrivilege
{
bool HasRole(Role r)
{
...
}
}
Step 2: Wrap the old implementation
● Create
Security.LegacyPermission
● Implement new interface
● Wrap existing
implementation
● Expose existing
implementation
class LegacyPermission : IPrivilege
{
LegacyPermission(Permission p)
{
this.p = p;
}
bool HasRole(Role r)
{
if (r == Role.Admin)
return p.IsAdmin();
return false;
}
Permission Permission
{
get: { return p; }
}
private Permission p;
}
Step 3: Factory
● Create a factory
● Have it return the new
implementation
● Unless directed to return
the wrapped old one
class PrivilegeFactory
{
IPrivilege Create(bool old=true)
{
if(!old)
{
return new Privilege();
}
return new LegacyPermission();
}
}
Step 4: Test compatibility
● Write tests
● Run all tests against both
implementations
● Iterate until the new
implementation has a
satisfactory level of
backwards compatibility
● This can be committed
gradually
[TestCase(true)]
[TestCase(false)]
void HasRole(bool old)
{
// given
var f = new PrivilegeFactory();
var p = f.Create(old);
// when
var b = p.HasRole(Role.Admin);
// then
Assert.That(b, Is.True);
}
Step 5: Dumb migration
● Replace all uses of the old
implementation with the
new wrapper
● Immediately use the
exposed old
implementation
● This can be committed
gradually
void SomeController()
{
var priv = f.Create(true)
as LegacyPermission;
var p = priv.Permission;
if (p.IsAdmin())
{
...
}
}
Step 6: Actual migration
● Rewrite code to use the
new implementation
instead of the exposed old
implementation
● This can be committed
gradually
void SomeController()
{
var p = f.Create(true);
if (p.HasRole(Role.Admin)
{
...
}
}
Step 7: Verify migration is code complete
● Delete the property
exposing the old
implementation
● Go back to previous step if
the code does not compile
● Note: at this point the code
is still using the old
implementation
everywhere!
class LegacyPermission : IPrivilege
{
...
// Permission Permission
// {
// get: { return p; }
// }
private Permission p;
}
Step 8: Verify migration works
● Allow QA to explicitly switch
to the new implementation
● We now have a Feature
Toggle
● Do thorough exploratory
testing with the new
implementation
● If unintented behavior is
found, go back to step 4
and add a new test that
fails for this reason, fix the
issue and repeat
class PrivilegeFactory
{
IPrivilege Create(bool old=true)
{
var UseNew = %UseNew%;
if(!old || UseNew)
{
return new Privilege();
}
return new LegacyPermission();
}
}
Step 9: Complete migration
● Always use the new
implementation
● Mark the old
implementation as
Obsolete to prevent new
usages
class PrivilegeFactory
{
IPrivilege Create()
{
return new Privilege();
}
}
[Obsolete]
class Permission
{
...
}
Step 10: Clean up
● After proper validation in
production, delete the old
implementation

More Related Content

PPTX
Refactoring legacy code driven by tests - ENG
PDF
Adding Unit Test To Legacy Code
PPT
Getting Unstuck: Working with Legacy Code and Data
PDF
Refactoring Legacy Code
PPTX
Working Effectively with Legacy Code
PDF
Working Effectively with Legacy Code: Lessons in Practice
PPTX
Unit testing - the hard parts
PDF
Working With Legacy Code
Refactoring legacy code driven by tests - ENG
Adding Unit Test To Legacy Code
Getting Unstuck: Working with Legacy Code and Data
Refactoring Legacy Code
Working Effectively with Legacy Code
Working Effectively with Legacy Code: Lessons in Practice
Unit testing - the hard parts
Working With Legacy Code

What's hot (20)

PPTX
VT.NET 20160411: An Intro to Test Driven Development (TDD)
PPT
TDD And Refactoring
PDF
iOS Test-Driven Development
PPTX
TDD - Test Driven Development
DOCX
Test driven development and unit testing with examples in C++
PDF
An Introduction to Test Driven Development
PPTX
Unit Testing in Action - C#, NUnit, and Moq
PDF
Refactoring - An Introduction
PDF
Introduction to TDD (Test Driven development) - Ahmed Shreef
PDF
Test Driven Development
PDF
Agile Programming Systems # TDD intro
PDF
Dependency Injection in iOS
PDF
Unit Testing Fundamentals
PDF
Unit Test + Functional Programming = Love
PPTX
Working with Legacy Code
PPTX
Roy Osherove on Unit Testing Good Practices and Horrible Mistakes
PDF
Testing Legacy Rails Apps
PDF
Living With Legacy Code
PPTX
TDD & BDD
PPTX
Software Quality via Unit Testing
VT.NET 20160411: An Intro to Test Driven Development (TDD)
TDD And Refactoring
iOS Test-Driven Development
TDD - Test Driven Development
Test driven development and unit testing with examples in C++
An Introduction to Test Driven Development
Unit Testing in Action - C#, NUnit, and Moq
Refactoring - An Introduction
Introduction to TDD (Test Driven development) - Ahmed Shreef
Test Driven Development
Agile Programming Systems # TDD intro
Dependency Injection in iOS
Unit Testing Fundamentals
Unit Test + Functional Programming = Love
Working with Legacy Code
Roy Osherove on Unit Testing Good Practices and Horrible Mistakes
Testing Legacy Rails Apps
Living With Legacy Code
TDD & BDD
Software Quality via Unit Testing
Ad

Similar to Unit testing legacy code (20)

PPTX
Refactoring workshop
PPTX
Testing the untestable
PDF
Keeping code clean
PDF
Agile Developer Immersion Workshop, LASTconf Melbourne, Australia, 19th July ...
PPTX
Improving the Design of Existing Software
PPTX
Writing testable code
ODP
Dealing With Legacy: The Real-World Experience
PDF
Test and Behaviour Driven Development (TDD/BDD)
PPTX
Code quality
PPTX
Bye Bye Cowboy Coder Days! (Legacy Code & TDD)
PPTX
Code Quality
PPTX
Stop fearing legacy code
PPTX
Refactoring For Testability
PPTX
Improving The Quality of Existing Software
PPTX
An Introduction to Developer Testing
PPTX
Unit testing
PDF
Introduction to Automated Testing
PDF
Introduction to-automated-testing
PPT
XP through TDD
Refactoring workshop
Testing the untestable
Keeping code clean
Agile Developer Immersion Workshop, LASTconf Melbourne, Australia, 19th July ...
Improving the Design of Existing Software
Writing testable code
Dealing With Legacy: The Real-World Experience
Test and Behaviour Driven Development (TDD/BDD)
Code quality
Bye Bye Cowboy Coder Days! (Legacy Code & TDD)
Code Quality
Stop fearing legacy code
Refactoring For Testability
Improving The Quality of Existing Software
An Introduction to Developer Testing
Unit testing
Introduction to Automated Testing
Introduction to-automated-testing
XP through TDD
Ad

More from Lars Thorup (17)

PDF
100 tests per second - 40 releases per week
PDF
SQL or NoSQL - how to choose
PDF
Super fast end-to-end-tests
PDF
Extreme Programming - to the next-level
PDF
Advanced Javascript Unit Testing
PDF
Advanced QUnit - Front-End JavaScript Unit Testing
PDF
Put "fast" back in "fast feedback"
PDF
Database Schema Evolution
PDF
Advanced Jasmine - Front-End JavaScript Unit Testing
PDF
Javascript unit testing with QUnit and Sinon
PDF
Continuous Integration for front-end JavaScript
PDF
Automated Performance Testing
PDF
Agile Contracts
PDF
High Performance Software Engineering Teams
PDF
Elephant Carpaccio
PDF
Automated Testing for Embedded Software in C or C++
PDF
Unit Testing in JavaScript with MVC and QUnit
100 tests per second - 40 releases per week
SQL or NoSQL - how to choose
Super fast end-to-end-tests
Extreme Programming - to the next-level
Advanced Javascript Unit Testing
Advanced QUnit - Front-End JavaScript Unit Testing
Put "fast" back in "fast feedback"
Database Schema Evolution
Advanced Jasmine - Front-End JavaScript Unit Testing
Javascript unit testing with QUnit and Sinon
Continuous Integration for front-end JavaScript
Automated Performance Testing
Agile Contracts
High Performance Software Engineering Teams
Elephant Carpaccio
Automated Testing for Embedded Software in C or C++
Unit Testing in JavaScript with MVC and QUnit

Recently uploaded (20)

PPTX
FLIGHT TICKET RESERVATION SYSTEM | FLIGHT BOOKING ENGINE API
PPTX
Essential Infomation Tech presentation.pptx
PDF
medical staffing services at VALiNTRY
PDF
Which alternative to Crystal Reports is best for small or large businesses.pdf
PPT
Introduction Database Management System for Course Database
PDF
Claude Code: Everyone is a 10x Developer - A Comprehensive AI-Powered CLI Tool
PDF
System and Network Administraation Chapter 3
DOCX
Looking for a Tableau Alternative Try Helical Insight Open Source BI Platform...
PPTX
Odoo POS Development Services by CandidRoot Solutions
PDF
Flood Susceptibility Mapping Using Image-Based 2D-CNN Deep Learnin. Overview ...
PPTX
Online Work Permit System for Fast Permit Processing
PDF
Raksha Bandhan Grocery Pricing Trends in India 2025.pdf
PDF
5 Lead Qualification Frameworks Every Sales Team Should Use
PPTX
CRUISE TICKETING SYSTEM | CRUISE RESERVATION SOFTWARE
PDF
PTS Company Brochure 2025 (1).pdf.......
PPTX
Agentic AI : A Practical Guide. Undersating, Implementing and Scaling Autono...
PDF
A REACT POMODORO TIMER WEB APPLICATION.pdf
PPT
JAVA ppt tutorial basics to learn java programming
PDF
T3DD25 TYPO3 Content Blocks - Deep Dive by André Kraus
PDF
2025 Textile ERP Trends: SAP, Odoo & Oracle
FLIGHT TICKET RESERVATION SYSTEM | FLIGHT BOOKING ENGINE API
Essential Infomation Tech presentation.pptx
medical staffing services at VALiNTRY
Which alternative to Crystal Reports is best for small or large businesses.pdf
Introduction Database Management System for Course Database
Claude Code: Everyone is a 10x Developer - A Comprehensive AI-Powered CLI Tool
System and Network Administraation Chapter 3
Looking for a Tableau Alternative Try Helical Insight Open Source BI Platform...
Odoo POS Development Services by CandidRoot Solutions
Flood Susceptibility Mapping Using Image-Based 2D-CNN Deep Learnin. Overview ...
Online Work Permit System for Fast Permit Processing
Raksha Bandhan Grocery Pricing Trends in India 2025.pdf
5 Lead Qualification Frameworks Every Sales Team Should Use
CRUISE TICKETING SYSTEM | CRUISE RESERVATION SOFTWARE
PTS Company Brochure 2025 (1).pdf.......
Agentic AI : A Practical Guide. Undersating, Implementing and Scaling Autono...
A REACT POMODORO TIMER WEB APPLICATION.pdf
JAVA ppt tutorial basics to learn java programming
T3DD25 TYPO3 Content Blocks - Deep Dive by André Kraus
2025 Textile ERP Trends: SAP, Odoo & Oracle

Unit testing legacy code

  • 1. Unit testing legacy code Lars Thorup ZeaLake Software Consulting May, 2014
  • 2. Who is Lars Thorup? ● Software developer/architect ● JavaScript, C# ● Test Driven Development ● Continuous Integration ● Coach: Teaching TDD and continuous integration ● Founder of ZeaLake ● @larsthorup
  • 3. The problems with legacy code ● No tests ● The code probably works... ● Hard to refactor ● Will the code still work? ● Hard to extend ● Need to change the code... ● The code owns us :( ● Did our investment turn sour?
  • 4. How do tests bring us back in control? ● A refactoring improves the design without changing behavior ● Tests ensure that behavior is not accidentally changed ● Without tests, refactoring is scary ● and with no refactoring, the design decays over time ● With tests, we have the courage to refactor ● so we continually keep our design healthy
  • 5. What comes first: the test or the refactoring?
  • 6. How do we get to sustainable legacy code? ● Make it easy to add characterization tests ● Have good unit test coverage for important areas ● Don't worry about code you don't need to change ● Test-drive all new code ● Now we own the code :)
  • 7. Making legacy code sustainable ● Select an important area ● Driven by change requests ● Add characterization tests ● Make code testable ● Refactor the code ● Add unit tests ● Remove characterization tests ● Small steps
  • 8. Characterization tests ● Characterize current behavior ● Integration tests ● Either high level unit tests ● Or end-to-end tests ● Don't change existing code ● Faulty behavior = current behavior: don't change it! ● Make a note to fix later ● Test at a level that makes it easy ● The characterization tests are throw-aways ● Demo: ● Web service test: VoteMedia ● End-to-end browser test: entrylist.demo.test.js
  • 9. Make code testable ● Avoid large methods ● They require a ton of setup ● They require lots of scenarios to cover all variations ● Avoid outer scope dependencies ● They require you to test at a higher level ● Avoid external dependencies ● ... a ton of setup ● They slow you down
  • 10. Refactor the code ● Add interface ● Inject a mock instead of the real thing ● Easier setup ● Infinitely faster Notifier EmailSvc IEmailSvc EmailSvcStub NotifierTest ● Extract method ● Split up large methods ● To simplify unit testing single behaviors ● Demo: VoteWithVideo_Vimas ● Add parameter ● Pass in outer-scope dependencies ● The tests can pass in their own dummy values ● Demo: Entry.renderResponse
  • 11. Add unit tests ● Now that the code is testable... ● Write unit tests for you small methods ● Pass in dummy values for parameters ● Mock dependencies ● Rinse and repeat...
  • 12. Remove the characterization tests ● When unit test code coverage is good enough ● To speed up feedback ● To avoid test duplication
  • 13. Small steps - elephant carpaccio ● Any big refactoring... ● ...can be done in small steps ● Demo: Security system (see slide 19 through 31)
  • 14. Test-drive all new code ● Easy, now that unit testing tools are in place Failing test Succeeding test Good design Refactor Test Intention Think, talk Code
  • 15. Making legacy code sustainable ● Select an important area ● Driven by change requests ● Add characterization tests ● Make code testable ● Refactor the code ● Add unit tests ● Remove characterization tests ● Small steps
  • 16. It's not hard - now go do it! ● This is hard ● SQL query efficiency ● Cache invalidation ● Scalability ● Pixel perfect rendering ● Cross-browser compatibility ● Indexing strategies ● Security ● Real time media streaming ● 60fps gaming with HTML5 ● ... and robust Selenium tests! ● This is not hard ● Refactoring ● Unit testing ● Dependency injection ● Automated build and test ● Continuous Integration ● Fast feedback will make you more productive ● ... and more happy
  • 17. A big refactoring is needed...
  • 18. Avoid feature branches ● For features as well as large refactorings ● Delayed integration ● Increases risk ● Increases cost
  • 19. Use feature toggles ● Any big refactoring... ● ...can be done in small steps ● Allows us to keep development on trunk/master ● Drastically lowering the risk ● Commit after every step ● At most a couple of hours
  • 20. Security example ● Change the code from the old security system ● To our new extended security model interface IPrivilege { bool HasRole(Role); } class Permission { bool IsAdmin(); }
  • 21. Step 0: existing implementation ● Code instantiates Legacy.Permission ● and calls methods like permission.IsAdmin() ● ...all over the place ● We want to replace this with a new security system void SomeController() { var p = new Permission(); if (p.IsAdmin()) { ... } }
  • 22. Step 1: New security implementation ● Implements an interface ● This can be committed gradually interface IPrivilege { bool HasRole(Role); } class Privilege : IPrivilege { bool HasRole(Role r) { ... } }
  • 23. Step 2: Wrap the old implementation ● Create Security.LegacyPermission ● Implement new interface ● Wrap existing implementation ● Expose existing implementation class LegacyPermission : IPrivilege { LegacyPermission(Permission p) { this.p = p; } bool HasRole(Role r) { if (r == Role.Admin) return p.IsAdmin(); return false; } Permission Permission { get: { return p; } } private Permission p; }
  • 24. Step 3: Factory ● Create a factory ● Have it return the new implementation ● Unless directed to return the wrapped old one class PrivilegeFactory { IPrivilege Create(bool old=true) { if(!old) { return new Privilege(); } return new LegacyPermission(); } }
  • 25. Step 4: Test compatibility ● Write tests ● Run all tests against both implementations ● Iterate until the new implementation has a satisfactory level of backwards compatibility ● This can be committed gradually [TestCase(true)] [TestCase(false)] void HasRole(bool old) { // given var f = new PrivilegeFactory(); var p = f.Create(old); // when var b = p.HasRole(Role.Admin); // then Assert.That(b, Is.True); }
  • 26. Step 5: Dumb migration ● Replace all uses of the old implementation with the new wrapper ● Immediately use the exposed old implementation ● This can be committed gradually void SomeController() { var priv = f.Create(true) as LegacyPermission; var p = priv.Permission; if (p.IsAdmin()) { ... } }
  • 27. Step 6: Actual migration ● Rewrite code to use the new implementation instead of the exposed old implementation ● This can be committed gradually void SomeController() { var p = f.Create(true); if (p.HasRole(Role.Admin) { ... } }
  • 28. Step 7: Verify migration is code complete ● Delete the property exposing the old implementation ● Go back to previous step if the code does not compile ● Note: at this point the code is still using the old implementation everywhere! class LegacyPermission : IPrivilege { ... // Permission Permission // { // get: { return p; } // } private Permission p; }
  • 29. Step 8: Verify migration works ● Allow QA to explicitly switch to the new implementation ● We now have a Feature Toggle ● Do thorough exploratory testing with the new implementation ● If unintented behavior is found, go back to step 4 and add a new test that fails for this reason, fix the issue and repeat class PrivilegeFactory { IPrivilege Create(bool old=true) { var UseNew = %UseNew%; if(!old || UseNew) { return new Privilege(); } return new LegacyPermission(); } }
  • 30. Step 9: Complete migration ● Always use the new implementation ● Mark the old implementation as Obsolete to prevent new usages class PrivilegeFactory { IPrivilege Create() { return new Privilege(); } } [Obsolete] class Permission { ... }
  • 31. Step 10: Clean up ● After proper validation in production, delete the old implementation