SlideShare a Scribd company logo
Applying TDD
to Legacy Code
Background
• Experience: 10+ years as a developer of various systems
• Projects from the scratch: 8 (2 very large)
• Projects with legacy code: 6 (4 very large)
• Projects with good tests: 4 (1 with legacy code)
• Frameworks: xUnit, MsTests, NUnit
Rule 1: Use Bottom-Up (Inside-Out)
• For new systems → Top-Down (Outside-In)
• For systems with legacy code → Bottom-Up (Inside-Out)
• Reasons
• Cannot be properly designed upfront
• Cannot modify components
• Cannot grasp all components
• Cannot mock/stub legacy components
• Interactions have side effects
• Change as little as possible
• Balance
• When injecting the change → bottom-up
• When implementing the change → top-down
Rule 1: Example (way of thinking)
public class EuropeShop : Shop
{
public override void CreateSale()
{
var items = LoadSelectedItemsFromDb();
var taxes = new EuropeTaxes();
var saleItems = items.Select(item => taxes.ApplyTaxes(item)).ToList();
var cart = new Cart();
cart.Add(saleItems);
taxes.ApplyTaxes(cart);
SaveToDb(cart);
}
}
// TODO > NEW FEATURE > Send notification to accounting if cart total amount exceeds 1000
Source code: https://github.com/xtrmstep/ApplyingTddToLegacyCode
Rule 2: Test Modified Code ONLY
• Cannot grasp all use cases
• Writing tests for all use cases requires enormous time
• Legacy Code was accepted → works as expected
• Your modification is the only thing which breaks the
stable system
• Don’t bother about Code Coverage
Rule 2: Example (new feature)
public class EuropeShop : Shop
{
public override void CreateSale()
{
var items = LoadSelectedItemsFromDb();
var taxes = new EuropeTaxes();
var saleItems = items.Select(item => taxes.ApplyTaxes(item)).ToList();
var cart = new Cart();
cart.Add(saleItems);
taxes.ApplyTaxes(cart);
// TODO
SaveToDb(cart);
}
}
public class EuropeShop : Shop
{
public override void CreateSale()
{
var items = LoadSelectedItemsFromDb();
var taxes = new EuropeTaxes();
var saleItems = items.Select(item => taxes.ApplyTaxes(item)).ToList();
var cart = new Cart();
cart.Add(saleItems);
taxes.ApplyTaxes(cart);
new EuropeShopNotifier().Send(cart); // NEW FEATURE
SaveToDb(cart);
}
}
Source code: https://github.com/xtrmstep/ApplyingTddToLegacyCode
Rule 3: Test Requirements ONLY
• Users = classes which use your code
• Consider use cases of method, class, component
• Consider behaviour of the code
• BDD, Feature testing
• Extract responsibilities
Rule 3: Example (responsibilities)
public class EuropeShop : Shop
{
public override void CreateSale()
{
// 1) load from DB
var items = LoadSelectedItemsFromDb();
// 2) Tax-object creates SaleItem and
// 4) goes through items and apply taxes
var taxes = new EuropeTaxes();
var saleItems = items.Select(item => taxes.ApplyTaxes(item)).ToList();
// 3) creates a cart and 4) applies taxes
var cart = new Cart();
cart.Add(saleItems);
taxes.ApplyTaxes(cart);
new EuropeShopNotifier().Send(cart);
// 4) store to DB
SaveToDb(cart);
}
}
Source code: https://github.com/xtrmstep/ApplyingTddToLegacyCode
public class EuropeShop : Shop
{
public override void CreateSale()
{
// 1) extracted to a repository
var itemsRepository = new ItemsRepository();
var items = itemsRepository.LoadSelectedItems();
// 2) extracted to a mapper
var saleItems = items.ConvertToSaleItems();
// 3) still creates a cart
var cart = new Cart();
cart.Add(saleItems);
// 4) all routines to apply taxes are extracted to the Tax-object
new EuropeTaxes().ApplyTaxes(cart);
new EuropeShopNotifier().Send(cart);
// 5) extracted to a repository
itemsRepository.Save(cart);
}
}
Rule 3: Example (tests)
public class EuropeTaxesTests
{
public void Should_not_fail_for_null()
{
}
public void Should_apply_taxes_to_items()
{
}
public void Should_apply_taxes_to_whole_cart()
{
}
public void Should_apply_taxes_to_whole_cart_and_change_items()
{
}
}
Source code: https://github.com/xtrmstep/ApplyingTddToLegacyCode
public class EuropeShopNotifierTests
{
public void Should_not_send_when_less_or_equals_to_1000()
{
// arrange
var cart = new Cart();
var notifier = new EuropeShopNotifier();
// add items to cart
// act
notifier.Send(cart);
// assert
// PROBLEM: how to verify if we don't have access to the object?
}
public void Should_send_when_greater_than_1000()
{
}
public void Should_raise_exception_when_cannot_send()
{
}
}
Rule 4: Inject Tested Code ONLY
• Don’t change legacy code
• Use technique Sprout method/class
• New testable code is called from the old not-testable
• Old code is not changed
• Use technique Wrap method
• New code goes before/after the old not-testable one
• Create a new method to call the new and old one
Rule 4: Example (Sprout method)
public class EuropeShop : Shop
{
public override void CreateSale()
{
var items = LoadSelectedItemsFromDb();
var taxes = new EuropeTaxes();
var saleItems = items.Select(item => taxes.ApplyTaxes(item)).ToList();
var cart = new Cart();
cart.Add(saleItems);
taxes.ApplyTaxes(cart);
// TODO
SaveToDb(cart);
}
}
public class EuropeShop : Shop
{
public override void CreateSale()
{
var items = LoadSelectedItemsFromDb();
var taxes = new EuropeTaxes();
var saleItems = items.Select(item => taxes.ApplyTaxes(item)).ToList();
var cart = new Cart();
cart.Add(saleItems);
taxes.ApplyTaxes(cart);
new EuropeShopNotifier().Send(cart); // Sprout method
SaveToDb(cart);
}
}
Source code: https://github.com/xtrmstep/ApplyingTddToLegacyCode
Rule 4: Example (Wrap method)
public class EuropeTaxes : Taxes
{
internal override SaleItem ApplyTaxes(Item item)
{
var saleItem = new SaleItem(item)
{
SalePrice = item.Price*1.2m
};
return saleItem;
}
internal override void ApplyTaxes(Cart cart)
{
if (cart.TotalSalePrice <= 300m) return;
var exclusion = 30m/cart.SaleItems.Count;
foreach (var item in cart.SaleItems)
if (item.SalePrice - exclusion > 100m)
item.SalePrice -= exclusion;
}
}
public class EuropeTaxes : Taxes
{
internal override void ApplyTaxes(Cart cart)
{
ApplyToItems(cart);
ApplyToCart(cart);
}
private void ApplyToItems(Cart cart)
{
foreach (var item in cart.SaleItems)
item.SalePrice = item.Price*1.2m;
}
private void ApplyToCart(Cart cart)
{
if (cart.TotalSalePrice <= 300m) return;
var exclusion = 30m / cart.SaleItems.Count;
foreach (var item in cart.SaleItems)
if (item.SalePrice - exclusion > 100m)
item.SalePrice -= exclusion;
}
}
Source code: https://github.com/xtrmstep/ApplyingTddToLegacyCode
Rule 5: Break Hidden Dependencies
• Stop instantiate classes in methods
• Extract initialization to constructor
• Replace usages of class with usages of interface/abstract
• Low coupling, High cohesion
• Extract giant methods to classes
• Remove violations of Law of Demeter
“Cohesion partitions your functionality so that it is concise and closest to
the data relevant to it, whilst decoupling ensures that the functional
implementation is isolated from the rest of the system.”
- Adrian Regan @ stackoverflow.com
Rule 5: Example (dependencies)
public class EuropeShop : Shop
{
public override void CreateSale()
{
var itemsRepository = new ItemsRepository();
var items = itemsRepository.LoadSelectedItems();
var saleItems = items.ConvertToSaleItems();
var cart = new Cart();
cart.Add(saleItems);
new EuropeTaxes().ApplyTaxes(cart);
new EuropeShopNotifier().Send(cart);
itemsRepository.Save(cart);
}
}
public class EuropeShop : Shop
{
private readonly IItemsRepository _itemsRepository;
private readonly Taxes.Taxes _europeTaxes;
private readonly INotifier _europeShopNotifier;
public override void CreateSale()
{
var items = _itemsRepository.LoadSelectedItems();
var saleItems = items.ConvertToSaleItems();
var cart = new Cart();
cart.Add(saleItems);
_europeTaxes.ApplyTaxes(cart);
_europeShopNotifier.Send(cart);
_itemsRepository.Save(cart);
}
}
Source code: https://github.com/xtrmstep/ApplyingTddToLegacyCode
Skipped by now, but we can use Factory to isolate
Rule 6: The Less “Big” tests, the Better
• “Big” tests
• Integration tests
• End-to-end tests
• UI tests
• Complexity grows by exponent
• Double time to maintain and write
• Special cases:
• Performance tests
• Load tests
• DON’T substitute unit tests with integration tests
Rule 7: Don’t Test Private Methods
• Responsibility violation sign
• Fragile tests
• Not WHAT class does, but HOW it does
• Don’t convert private methods to public ones
• Extract and use internal for module inside logic
• Consider new classes for private methods
• Consider internal classes as complete tools
Rule 7: Example (test)
public class EuropeTaxes : Taxes {
public override void ApplyTaxes(Cart cart) {
ApplyToItems(cart);
ApplyToCart(cart);
}
private void ApplyToItems(Cart cart) {
foreach (var item in cart.SaleItems)
item.SalePrice = item.Price*1.2m;
}
private void ApplyToCart(Cart cart) {
if (cart.TotalSalePrice <= 300m) return;
var exclusion = 30m / cart.SaleItems.Count;
foreach (var item in cart.SaleItems)
if (item.SalePrice - exclusion > 100m)
item.SalePrice -= exclusion;
}
}
public class EuropeTaxesTests {
public void Should_not_fail_for_null() {
}
public void Should_apply_taxes_to_items() {
}
public void Should_apply_taxes_to_whole_cart() {
}
public void Should_apply_taxes_to_whole_cart_and_change_items() {
}
public void Should_apply_taxes_to_cart_greater_300() {
}
}
Source code: https://github.com/xtrmstep/ApplyingTddToLegacyCode
Rule 7: Example (test body)
public void Should_apply_taxes_to_cart_greater_300()
{
// arrange
// list of items which will create a cart greater 300
var saleItems = new List<Item>(new []{new Item {Price = 83.34m},
new Item {Price = 83.34m},new Item {Price = 83.34m}})
.ConvertToSaleItems();
var cart = new Cart();
cart.Add(saleItems);
const decimal expected = 83.34m*3*1.2m;
// act
new EuropeTaxes().ApplyTaxes(cart);
// assert
Assert.Equal(expected, cart.TotalSalePrice);
}
Source code: https://github.com/xtrmstep/ApplyingTddToLegacyCode
Rule 8: Don’t Test Algorithms
• Fragile tests
• Don’t care about number of calls
• Don’t care about which method is executed
• Do care about effects
Rule 9: Stop Modifying Legacy Code w/o Tests
• Add tests to Definition of Done & Code Review
• May look as a waste of time at the early stage
• Cumulative result at the later stage
• Always practicing
See Also
• Book “Working Effectively with Legacy Code” by Michael Feathers
• https://danlimerick.wordpress.com/2012/04/25/tdd-when-up-to-your-neck-in-legacy-code/
• https://danlimerick.wordpress.com/2012/06/11/breaking-hidden-dependencies/
• https://danlimerick.wordpress.com/2012/04/25/the-legacy-code-lifecycle/
• https://www.quora.com/Should-you-unit-test-private-methods-on-a-class
• http://blog.ploeh.dk/2015/09/22/unit-testing-internals/
• https://medium.com/javascript-scene/5-common-misconceptions-about-tdd-unit-tests-863d5beb3ce9#.uav3gih3k
• http://www.daedtech.com/intro-to-unit-testing-5-invading-legacy-code-in-the-name-of-testability/
• https://en.wikipedia.org/wiki/Law_of_Demeter

More Related Content

PDF
Mutation testing in Java
PPT
Stopping the Rot - Putting Legacy C++ Under Test
PPTX
TDD and the Legacy Code Black Hole
PPT
Presentation_C++UnitTest
PPT
20111018 boost and gtest
PDF
Modern Python Testing
PDF
Living With Legacy Code
PDF
Testing Legacy Rails Apps
Mutation testing in Java
Stopping the Rot - Putting Legacy C++ Under Test
TDD and the Legacy Code Black Hole
Presentation_C++UnitTest
20111018 boost and gtest
Modern Python Testing
Living With Legacy Code
Testing Legacy Rails Apps

What's hot (19)

PPT
Google C++ Testing Framework in Visual Studio 2008
ODT
Testing in-python-and-pytest-framework
PDF
MUTANTS KILLER - PIT: state of the art of mutation testing system
PDF
Write readable tests
PDF
Doing the Impossible
PDF
Write testable code in java, best practices
PPT
Google mock for dummies
KEY
Unit Test Your Database
ODP
Automated testing in Python and beyond
 
PDF
Python Testing Fundamentals
PDF
C++ Unit Test with Google Testing Framework
DOCX
Test driven development and unit testing with examples in C++
PDF
MUTANTS KILLER (Revised) - PIT: state of the art of mutation testing system
PDF
TDD CrashCourse Part3: TDD Techniques
PPTX
Quickly Testing Qt Desktop Applications
PPT
Introduzione al TDD
ODP
Python unit testing
PPTX
TDD with Visual Studio 2010
PDF
JUnit 5 - Evolution and Innovation - SpringOne Platform 2019
Google C++ Testing Framework in Visual Studio 2008
Testing in-python-and-pytest-framework
MUTANTS KILLER - PIT: state of the art of mutation testing system
Write readable tests
Doing the Impossible
Write testable code in java, best practices
Google mock for dummies
Unit Test Your Database
Automated testing in Python and beyond
 
Python Testing Fundamentals
C++ Unit Test with Google Testing Framework
Test driven development and unit testing with examples in C++
MUTANTS KILLER (Revised) - PIT: state of the art of mutation testing system
TDD CrashCourse Part3: TDD Techniques
Quickly Testing Qt Desktop Applications
Introduzione al TDD
Python unit testing
TDD with Visual Studio 2010
JUnit 5 - Evolution and Innovation - SpringOne Platform 2019
Ad

Similar to Applying TDD to Legacy Code (20)

PPTX
Solid Software Design Principles
PPTX
Unit Testing, TDD and ATDD
PPT
TDD And Refactoring
PPTX
PPTX
Improving the Design of Existing Software
PDF
Keeping code clean
PPTX
TDD? Sure, but What About My Legacy Code?
PPTX
Software design principles SOLID
PPTX
Functional DDD
PPT
KEY
Solid principles
PDF
Unit testing legacy code
PPSX
DotNet Conference: code smells
PPT
6. Compile And Run
PPTX
Improving the Quality of Existing Software
PPTX
Improving the Quality of Existing Software - DevIntersection April 2016
PPTX
TDD With Legacy Systems
PPTX
Building rich domain models with ddd and tdd ivan paulovich - betsson
PDF
Testing survival Guide
Solid Software Design Principles
Unit Testing, TDD and ATDD
TDD And Refactoring
Improving the Design of Existing Software
Keeping code clean
TDD? Sure, but What About My Legacy Code?
Software design principles SOLID
Functional DDD
Solid principles
Unit testing legacy code
DotNet Conference: code smells
6. Compile And Run
Improving the Quality of Existing Software
Improving the Quality of Existing Software - DevIntersection April 2016
TDD With Legacy Systems
Building rich domain models with ddd and tdd ivan paulovich - betsson
Testing survival Guide
Ad

Recently uploaded (20)

PPTX
Chapter-7-The-Spiritual-Self-.pptx-First
PPTX
Attitudes presentation for psychology.pptx
PPTX
Self -Management and Self Awareness.pptx
PPTX
show1- motivational ispiring positive thinking
PPTX
How to Deal with Imposter Syndrome for Personality Development?
PPTX
Pradeep Kumar Roll no.30 Paper I.pptx....
PDF
The Spotlight Effect No One Is Thinking About You as Much as You Think - by M...
PDF
SEX-GENDER-AND-SEXUALITY-LESSON-1-M (2).pdf
PPTX
Presentation on interview preparation.pt
PDF
My 'novel' Account of Human Possibility pdf.pdf
PPTX
Learn about numerology and do tarot reading
PPTX
Learn numerology content and join tarot reading
PDF
Elle Lalli on The Role of Emotional Intelligence in Entrepreneurship
PPTX
Learn how to use Portable Grinders Safely
PPTX
PERDEV-LESSON-3 DEVELOPMENTMENTAL STAGES.pptx
PPTX
cấu trúc sử dụng mẫu Cause - Effects.pptx
PPTX
Travel mania in india needs to change the world
PPTX
diasspresentationndkcnskndncelklkfndc.pptx
PDF
Attachment Theory What Childhood Says About Your Relationships.pdf
PDF
Top 10 Visionary Entrepreneurs to Watch in 2025
Chapter-7-The-Spiritual-Self-.pptx-First
Attitudes presentation for psychology.pptx
Self -Management and Self Awareness.pptx
show1- motivational ispiring positive thinking
How to Deal with Imposter Syndrome for Personality Development?
Pradeep Kumar Roll no.30 Paper I.pptx....
The Spotlight Effect No One Is Thinking About You as Much as You Think - by M...
SEX-GENDER-AND-SEXUALITY-LESSON-1-M (2).pdf
Presentation on interview preparation.pt
My 'novel' Account of Human Possibility pdf.pdf
Learn about numerology and do tarot reading
Learn numerology content and join tarot reading
Elle Lalli on The Role of Emotional Intelligence in Entrepreneurship
Learn how to use Portable Grinders Safely
PERDEV-LESSON-3 DEVELOPMENTMENTAL STAGES.pptx
cấu trúc sử dụng mẫu Cause - Effects.pptx
Travel mania in india needs to change the world
diasspresentationndkcnskndncelklkfndc.pptx
Attachment Theory What Childhood Says About Your Relationships.pdf
Top 10 Visionary Entrepreneurs to Watch in 2025

Applying TDD to Legacy Code

  • 2. Background • Experience: 10+ years as a developer of various systems • Projects from the scratch: 8 (2 very large) • Projects with legacy code: 6 (4 very large) • Projects with good tests: 4 (1 with legacy code) • Frameworks: xUnit, MsTests, NUnit
  • 3. Rule 1: Use Bottom-Up (Inside-Out) • For new systems → Top-Down (Outside-In) • For systems with legacy code → Bottom-Up (Inside-Out) • Reasons • Cannot be properly designed upfront • Cannot modify components • Cannot grasp all components • Cannot mock/stub legacy components • Interactions have side effects • Change as little as possible • Balance • When injecting the change → bottom-up • When implementing the change → top-down
  • 4. Rule 1: Example (way of thinking) public class EuropeShop : Shop { public override void CreateSale() { var items = LoadSelectedItemsFromDb(); var taxes = new EuropeTaxes(); var saleItems = items.Select(item => taxes.ApplyTaxes(item)).ToList(); var cart = new Cart(); cart.Add(saleItems); taxes.ApplyTaxes(cart); SaveToDb(cart); } } // TODO > NEW FEATURE > Send notification to accounting if cart total amount exceeds 1000 Source code: https://github.com/xtrmstep/ApplyingTddToLegacyCode
  • 5. Rule 2: Test Modified Code ONLY • Cannot grasp all use cases • Writing tests for all use cases requires enormous time • Legacy Code was accepted → works as expected • Your modification is the only thing which breaks the stable system • Don’t bother about Code Coverage
  • 6. Rule 2: Example (new feature) public class EuropeShop : Shop { public override void CreateSale() { var items = LoadSelectedItemsFromDb(); var taxes = new EuropeTaxes(); var saleItems = items.Select(item => taxes.ApplyTaxes(item)).ToList(); var cart = new Cart(); cart.Add(saleItems); taxes.ApplyTaxes(cart); // TODO SaveToDb(cart); } } public class EuropeShop : Shop { public override void CreateSale() { var items = LoadSelectedItemsFromDb(); var taxes = new EuropeTaxes(); var saleItems = items.Select(item => taxes.ApplyTaxes(item)).ToList(); var cart = new Cart(); cart.Add(saleItems); taxes.ApplyTaxes(cart); new EuropeShopNotifier().Send(cart); // NEW FEATURE SaveToDb(cart); } } Source code: https://github.com/xtrmstep/ApplyingTddToLegacyCode
  • 7. Rule 3: Test Requirements ONLY • Users = classes which use your code • Consider use cases of method, class, component • Consider behaviour of the code • BDD, Feature testing • Extract responsibilities
  • 8. Rule 3: Example (responsibilities) public class EuropeShop : Shop { public override void CreateSale() { // 1) load from DB var items = LoadSelectedItemsFromDb(); // 2) Tax-object creates SaleItem and // 4) goes through items and apply taxes var taxes = new EuropeTaxes(); var saleItems = items.Select(item => taxes.ApplyTaxes(item)).ToList(); // 3) creates a cart and 4) applies taxes var cart = new Cart(); cart.Add(saleItems); taxes.ApplyTaxes(cart); new EuropeShopNotifier().Send(cart); // 4) store to DB SaveToDb(cart); } } Source code: https://github.com/xtrmstep/ApplyingTddToLegacyCode public class EuropeShop : Shop { public override void CreateSale() { // 1) extracted to a repository var itemsRepository = new ItemsRepository(); var items = itemsRepository.LoadSelectedItems(); // 2) extracted to a mapper var saleItems = items.ConvertToSaleItems(); // 3) still creates a cart var cart = new Cart(); cart.Add(saleItems); // 4) all routines to apply taxes are extracted to the Tax-object new EuropeTaxes().ApplyTaxes(cart); new EuropeShopNotifier().Send(cart); // 5) extracted to a repository itemsRepository.Save(cart); } }
  • 9. Rule 3: Example (tests) public class EuropeTaxesTests { public void Should_not_fail_for_null() { } public void Should_apply_taxes_to_items() { } public void Should_apply_taxes_to_whole_cart() { } public void Should_apply_taxes_to_whole_cart_and_change_items() { } } Source code: https://github.com/xtrmstep/ApplyingTddToLegacyCode public class EuropeShopNotifierTests { public void Should_not_send_when_less_or_equals_to_1000() { // arrange var cart = new Cart(); var notifier = new EuropeShopNotifier(); // add items to cart // act notifier.Send(cart); // assert // PROBLEM: how to verify if we don't have access to the object? } public void Should_send_when_greater_than_1000() { } public void Should_raise_exception_when_cannot_send() { } }
  • 10. Rule 4: Inject Tested Code ONLY • Don’t change legacy code • Use technique Sprout method/class • New testable code is called from the old not-testable • Old code is not changed • Use technique Wrap method • New code goes before/after the old not-testable one • Create a new method to call the new and old one
  • 11. Rule 4: Example (Sprout method) public class EuropeShop : Shop { public override void CreateSale() { var items = LoadSelectedItemsFromDb(); var taxes = new EuropeTaxes(); var saleItems = items.Select(item => taxes.ApplyTaxes(item)).ToList(); var cart = new Cart(); cart.Add(saleItems); taxes.ApplyTaxes(cart); // TODO SaveToDb(cart); } } public class EuropeShop : Shop { public override void CreateSale() { var items = LoadSelectedItemsFromDb(); var taxes = new EuropeTaxes(); var saleItems = items.Select(item => taxes.ApplyTaxes(item)).ToList(); var cart = new Cart(); cart.Add(saleItems); taxes.ApplyTaxes(cart); new EuropeShopNotifier().Send(cart); // Sprout method SaveToDb(cart); } } Source code: https://github.com/xtrmstep/ApplyingTddToLegacyCode
  • 12. Rule 4: Example (Wrap method) public class EuropeTaxes : Taxes { internal override SaleItem ApplyTaxes(Item item) { var saleItem = new SaleItem(item) { SalePrice = item.Price*1.2m }; return saleItem; } internal override void ApplyTaxes(Cart cart) { if (cart.TotalSalePrice <= 300m) return; var exclusion = 30m/cart.SaleItems.Count; foreach (var item in cart.SaleItems) if (item.SalePrice - exclusion > 100m) item.SalePrice -= exclusion; } } public class EuropeTaxes : Taxes { internal override void ApplyTaxes(Cart cart) { ApplyToItems(cart); ApplyToCart(cart); } private void ApplyToItems(Cart cart) { foreach (var item in cart.SaleItems) item.SalePrice = item.Price*1.2m; } private void ApplyToCart(Cart cart) { if (cart.TotalSalePrice <= 300m) return; var exclusion = 30m / cart.SaleItems.Count; foreach (var item in cart.SaleItems) if (item.SalePrice - exclusion > 100m) item.SalePrice -= exclusion; } } Source code: https://github.com/xtrmstep/ApplyingTddToLegacyCode
  • 13. Rule 5: Break Hidden Dependencies • Stop instantiate classes in methods • Extract initialization to constructor • Replace usages of class with usages of interface/abstract • Low coupling, High cohesion • Extract giant methods to classes • Remove violations of Law of Demeter “Cohesion partitions your functionality so that it is concise and closest to the data relevant to it, whilst decoupling ensures that the functional implementation is isolated from the rest of the system.” - Adrian Regan @ stackoverflow.com
  • 14. Rule 5: Example (dependencies) public class EuropeShop : Shop { public override void CreateSale() { var itemsRepository = new ItemsRepository(); var items = itemsRepository.LoadSelectedItems(); var saleItems = items.ConvertToSaleItems(); var cart = new Cart(); cart.Add(saleItems); new EuropeTaxes().ApplyTaxes(cart); new EuropeShopNotifier().Send(cart); itemsRepository.Save(cart); } } public class EuropeShop : Shop { private readonly IItemsRepository _itemsRepository; private readonly Taxes.Taxes _europeTaxes; private readonly INotifier _europeShopNotifier; public override void CreateSale() { var items = _itemsRepository.LoadSelectedItems(); var saleItems = items.ConvertToSaleItems(); var cart = new Cart(); cart.Add(saleItems); _europeTaxes.ApplyTaxes(cart); _europeShopNotifier.Send(cart); _itemsRepository.Save(cart); } } Source code: https://github.com/xtrmstep/ApplyingTddToLegacyCode Skipped by now, but we can use Factory to isolate
  • 15. Rule 6: The Less “Big” tests, the Better • “Big” tests • Integration tests • End-to-end tests • UI tests • Complexity grows by exponent • Double time to maintain and write • Special cases: • Performance tests • Load tests • DON’T substitute unit tests with integration tests
  • 16. Rule 7: Don’t Test Private Methods • Responsibility violation sign • Fragile tests • Not WHAT class does, but HOW it does • Don’t convert private methods to public ones • Extract and use internal for module inside logic • Consider new classes for private methods • Consider internal classes as complete tools
  • 17. Rule 7: Example (test) public class EuropeTaxes : Taxes { public override void ApplyTaxes(Cart cart) { ApplyToItems(cart); ApplyToCart(cart); } private void ApplyToItems(Cart cart) { foreach (var item in cart.SaleItems) item.SalePrice = item.Price*1.2m; } private void ApplyToCart(Cart cart) { if (cart.TotalSalePrice <= 300m) return; var exclusion = 30m / cart.SaleItems.Count; foreach (var item in cart.SaleItems) if (item.SalePrice - exclusion > 100m) item.SalePrice -= exclusion; } } public class EuropeTaxesTests { public void Should_not_fail_for_null() { } public void Should_apply_taxes_to_items() { } public void Should_apply_taxes_to_whole_cart() { } public void Should_apply_taxes_to_whole_cart_and_change_items() { } public void Should_apply_taxes_to_cart_greater_300() { } } Source code: https://github.com/xtrmstep/ApplyingTddToLegacyCode
  • 18. Rule 7: Example (test body) public void Should_apply_taxes_to_cart_greater_300() { // arrange // list of items which will create a cart greater 300 var saleItems = new List<Item>(new []{new Item {Price = 83.34m}, new Item {Price = 83.34m},new Item {Price = 83.34m}}) .ConvertToSaleItems(); var cart = new Cart(); cart.Add(saleItems); const decimal expected = 83.34m*3*1.2m; // act new EuropeTaxes().ApplyTaxes(cart); // assert Assert.Equal(expected, cart.TotalSalePrice); } Source code: https://github.com/xtrmstep/ApplyingTddToLegacyCode
  • 19. Rule 8: Don’t Test Algorithms • Fragile tests • Don’t care about number of calls • Don’t care about which method is executed • Do care about effects
  • 20. Rule 9: Stop Modifying Legacy Code w/o Tests • Add tests to Definition of Done & Code Review • May look as a waste of time at the early stage • Cumulative result at the later stage • Always practicing
  • 21. See Also • Book “Working Effectively with Legacy Code” by Michael Feathers • https://danlimerick.wordpress.com/2012/04/25/tdd-when-up-to-your-neck-in-legacy-code/ • https://danlimerick.wordpress.com/2012/06/11/breaking-hidden-dependencies/ • https://danlimerick.wordpress.com/2012/04/25/the-legacy-code-lifecycle/ • https://www.quora.com/Should-you-unit-test-private-methods-on-a-class • http://blog.ploeh.dk/2015/09/22/unit-testing-internals/ • https://medium.com/javascript-scene/5-common-misconceptions-about-tdd-unit-tests-863d5beb3ce9#.uav3gih3k • http://www.daedtech.com/intro-to-unit-testing-5-invading-legacy-code-in-the-name-of-testability/ • https://en.wikipedia.org/wiki/Law_of_Demeter