SlideShare ist ein Scribd-Unternehmen logo
1 von 21
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

Weitere ähnliche Inhalte

Was ist angesagt?

Google mock for dummies
Google mock for dummiesGoogle mock for dummies
Google mock for dummies
Harry Potter
 

Was ist angesagt? (19)

Google C++ Testing Framework in Visual Studio 2008
Google C++ Testing Framework in Visual Studio 2008Google C++ Testing Framework in Visual Studio 2008
Google C++ Testing Framework in Visual Studio 2008
 
Testing in-python-and-pytest-framework
Testing in-python-and-pytest-frameworkTesting in-python-and-pytest-framework
Testing in-python-and-pytest-framework
 
MUTANTS KILLER - PIT: state of the art of mutation testing system
MUTANTS KILLER - PIT: state of the art of mutation testing system MUTANTS KILLER - PIT: state of the art of mutation testing system
MUTANTS KILLER - PIT: state of the art of mutation testing system
 
Write readable tests
Write readable testsWrite readable tests
Write readable tests
 
Doing the Impossible
Doing the ImpossibleDoing the Impossible
Doing the Impossible
 
Write testable code in java, best practices
Write testable code in java, best practicesWrite testable code in java, best practices
Write testable code in java, best practices
 
Google mock for dummies
Google mock for dummiesGoogle mock for dummies
Google mock for dummies
 
Unit Test Your Database
Unit Test Your DatabaseUnit Test Your Database
Unit Test Your Database
 
Automated testing in Python and beyond
Automated testing in Python and beyondAutomated testing in Python and beyond
Automated testing in Python and beyond
 
Python Testing Fundamentals
Python Testing FundamentalsPython Testing Fundamentals
Python Testing Fundamentals
 
C++ Unit Test with Google Testing Framework
C++ Unit Test with Google Testing FrameworkC++ Unit Test with Google Testing Framework
C++ Unit Test with Google Testing Framework
 
Test driven development and unit testing with examples in C++
Test driven development and unit testing with examples in C++Test driven development and unit testing with examples in C++
Test driven development and unit testing with examples in C++
 
MUTANTS KILLER (Revised) - PIT: state of the art of mutation testing system
MUTANTS KILLER (Revised) - PIT: state of the art of mutation testing system MUTANTS KILLER (Revised) - PIT: state of the art of mutation testing system
MUTANTS KILLER (Revised) - PIT: state of the art of mutation testing system
 
TDD CrashCourse Part3: TDD Techniques
TDD CrashCourse Part3: TDD TechniquesTDD CrashCourse Part3: TDD Techniques
TDD CrashCourse Part3: TDD Techniques
 
Quickly Testing Qt Desktop Applications
Quickly Testing Qt Desktop ApplicationsQuickly Testing Qt Desktop Applications
Quickly Testing Qt Desktop Applications
 
Introduzione al TDD
Introduzione al TDDIntroduzione al TDD
Introduzione al TDD
 
Python unit testing
Python unit testingPython unit testing
Python unit testing
 
TDD with Visual Studio 2010
TDD with Visual Studio 2010TDD with Visual Studio 2010
TDD with Visual Studio 2010
 
JUnit 5 - Evolution and Innovation - SpringOne Platform 2019
JUnit 5 - Evolution and Innovation - SpringOne Platform 2019JUnit 5 - Evolution and Innovation - SpringOne Platform 2019
JUnit 5 - Evolution and Innovation - SpringOne Platform 2019
 

Ähnlich wie Applying TDD to Legacy Code

Presentation 4th
Presentation 4thPresentation 4th
Presentation 4th
Connex
 
Automated integration tests for ajax applications (с. карпушин, auriga)
Automated integration tests for ajax applications (с. карпушин, auriga)Automated integration tests for ajax applications (с. карпушин, auriga)
Automated integration tests for ajax applications (с. карпушин, auriga)
Mobile Developer Day
 
Turku loves-storybook-styleguidist-styled-components
Turku loves-storybook-styleguidist-styled-componentsTurku loves-storybook-styleguidist-styled-components
Turku loves-storybook-styleguidist-styled-components
James Stone
 
Concurrent Collections Object In Dot Net 4
Concurrent Collections Object In Dot Net 4Concurrent Collections Object In Dot Net 4
Concurrent Collections Object In Dot Net 4
Neeraj Kaushik
 

Ähnlich wie Applying TDD to Legacy Code (20)

Testing the waters of iOS
Testing the waters of iOSTesting the waters of iOS
Testing the waters of iOS
 
Java 102
Java 102Java 102
Java 102
 
Unit iii
Unit iiiUnit iii
Unit iii
 
UNIT III.ppt
UNIT III.pptUNIT III.ppt
UNIT III.ppt
 
UNIT III (2).ppt
UNIT III (2).pptUNIT III (2).ppt
UNIT III (2).ppt
 
Unit testing PHP apps with PHPUnit
Unit testing PHP apps with PHPUnitUnit testing PHP apps with PHPUnit
Unit testing PHP apps with PHPUnit
 
CNIT 127: 8: Windows overflows (Part 2)
CNIT 127: 8: Windows overflows (Part 2)CNIT 127: 8: Windows overflows (Part 2)
CNIT 127: 8: Windows overflows (Part 2)
 
Dart for Java Developers
Dart for Java DevelopersDart for Java Developers
Dart for Java Developers
 
Presentation 4th
Presentation 4thPresentation 4th
Presentation 4th
 
Automated integration tests for ajax applications (с. карпушин, auriga)
Automated integration tests for ajax applications (с. карпушин, auriga)Automated integration tests for ajax applications (с. карпушин, auriga)
Automated integration tests for ajax applications (с. карпушин, auriga)
 
Turku loves-storybook-styleguidist-styled-components
Turku loves-storybook-styleguidist-styled-componentsTurku loves-storybook-styleguidist-styled-components
Turku loves-storybook-styleguidist-styled-components
 
Concurrent Collections Object In Dot Net 4
Concurrent Collections Object In Dot Net 4Concurrent Collections Object In Dot Net 4
Concurrent Collections Object In Dot Net 4
 
stacks and queues class 12 in c++
stacks and  queues class 12 in c++stacks and  queues class 12 in c++
stacks and queues class 12 in c++
 
Adding a modern twist to legacy web applications
Adding a modern twist to legacy web applicationsAdding a modern twist to legacy web applications
Adding a modern twist to legacy web applications
 
Breaking Dependencies To Allow Unit Testing - Steve Smith | FalafelCON 2014
Breaking Dependencies To Allow Unit Testing - Steve Smith | FalafelCON 2014Breaking Dependencies To Allow Unit Testing - Steve Smith | FalafelCON 2014
Breaking Dependencies To Allow Unit Testing - Steve Smith | FalafelCON 2014
 
Breaking Dependencies to Allow Unit Testing
Breaking Dependencies to Allow Unit TestingBreaking Dependencies to Allow Unit Testing
Breaking Dependencies to Allow Unit Testing
 
Metrics ekon 14_2_kleiner
Metrics ekon 14_2_kleinerMetrics ekon 14_2_kleiner
Metrics ekon 14_2_kleiner
 
TDD And Refactoring
TDD And RefactoringTDD And Refactoring
TDD And Refactoring
 
TDD with Puppet Tutorial presented at Cascadia IT Conference 2014-03-07
TDD with Puppet Tutorial presented at Cascadia IT Conference 2014-03-07TDD with Puppet Tutorial presented at Cascadia IT Conference 2014-03-07
TDD with Puppet Tutorial presented at Cascadia IT Conference 2014-03-07
 
Bring the fun back to java
Bring the fun back to javaBring the fun back to java
Bring the fun back to java
 

Kürzlich hochgeladen

call Now 9811711561 Cash Payment乂 Call Girls in Dwarka Mor
call Now 9811711561 Cash Payment乂 Call Girls in Dwarka Morcall Now 9811711561 Cash Payment乂 Call Girls in Dwarka Mor
call Now 9811711561 Cash Payment乂 Call Girls in Dwarka Mor
vikas rana
 
the Husband rolesBrown Aesthetic Cute Group Project Presentation
the Husband rolesBrown Aesthetic Cute Group Project Presentationthe Husband rolesBrown Aesthetic Cute Group Project Presentation
the Husband rolesBrown Aesthetic Cute Group Project Presentation
brynpueblos04
 
KLINIK BATA Jual obat penggugur kandungan 087776558899 ABORSI JANIN KEHAMILAN...
KLINIK BATA Jual obat penggugur kandungan 087776558899 ABORSI JANIN KEHAMILAN...KLINIK BATA Jual obat penggugur kandungan 087776558899 ABORSI JANIN KEHAMILAN...
KLINIK BATA Jual obat penggugur kandungan 087776558899 ABORSI JANIN KEHAMILAN...
Cara Menggugurkan Kandungan 087776558899
 
call Now 9811711561 Cash Payment乂 Call Girls in Dwarka
call Now 9811711561 Cash Payment乂 Call Girls in Dwarkacall Now 9811711561 Cash Payment乂 Call Girls in Dwarka
call Now 9811711561 Cash Payment乂 Call Girls in Dwarka
vikas rana
 

Kürzlich hochgeladen (14)

2k Shots ≽ 9205541914 ≼ Call Girls In Dashrath Puri (Delhi)
2k Shots ≽ 9205541914 ≼ Call Girls In Dashrath Puri (Delhi)2k Shots ≽ 9205541914 ≼ Call Girls In Dashrath Puri (Delhi)
2k Shots ≽ 9205541914 ≼ Call Girls In Dashrath Puri (Delhi)
 
2k Shots ≽ 9205541914 ≼ Call Girls In Jasola (Delhi)
2k Shots ≽ 9205541914 ≼ Call Girls In Jasola (Delhi)2k Shots ≽ 9205541914 ≼ Call Girls In Jasola (Delhi)
2k Shots ≽ 9205541914 ≼ Call Girls In Jasola (Delhi)
 
WOMEN EMPOWERMENT women empowerment.pptx
WOMEN EMPOWERMENT women empowerment.pptxWOMEN EMPOWERMENT women empowerment.pptx
WOMEN EMPOWERMENT women empowerment.pptx
 
call Now 9811711561 Cash Payment乂 Call Girls in Dwarka Mor
call Now 9811711561 Cash Payment乂 Call Girls in Dwarka Morcall Now 9811711561 Cash Payment乂 Call Girls in Dwarka Mor
call Now 9811711561 Cash Payment乂 Call Girls in Dwarka Mor
 
(Anamika) VIP Call Girls Navi Mumbai Call Now 8250077686 Navi Mumbai Escorts ...
(Anamika) VIP Call Girls Navi Mumbai Call Now 8250077686 Navi Mumbai Escorts ...(Anamika) VIP Call Girls Navi Mumbai Call Now 8250077686 Navi Mumbai Escorts ...
(Anamika) VIP Call Girls Navi Mumbai Call Now 8250077686 Navi Mumbai Escorts ...
 
Call Girls In Mumbai Just Genuine Call ☎ 7738596112✅ Call Girl Andheri East G...
Call Girls In Mumbai Just Genuine Call ☎ 7738596112✅ Call Girl Andheri East G...Call Girls In Mumbai Just Genuine Call ☎ 7738596112✅ Call Girl Andheri East G...
Call Girls In Mumbai Just Genuine Call ☎ 7738596112✅ Call Girl Andheri East G...
 
LC_YouSaidYes_NewBelieverBookletDone.pdf
LC_YouSaidYes_NewBelieverBookletDone.pdfLC_YouSaidYes_NewBelieverBookletDone.pdf
LC_YouSaidYes_NewBelieverBookletDone.pdf
 
the Husband rolesBrown Aesthetic Cute Group Project Presentation
the Husband rolesBrown Aesthetic Cute Group Project Presentationthe Husband rolesBrown Aesthetic Cute Group Project Presentation
the Husband rolesBrown Aesthetic Cute Group Project Presentation
 
KLINIK BATA Jual obat penggugur kandungan 087776558899 ABORSI JANIN KEHAMILAN...
KLINIK BATA Jual obat penggugur kandungan 087776558899 ABORSI JANIN KEHAMILAN...KLINIK BATA Jual obat penggugur kandungan 087776558899 ABORSI JANIN KEHAMILAN...
KLINIK BATA Jual obat penggugur kandungan 087776558899 ABORSI JANIN KEHAMILAN...
 
(Aarini) Russian Call Girls Surat Call Now 8250077686 Surat Escorts 24x7
(Aarini) Russian Call Girls Surat Call Now 8250077686 Surat Escorts 24x7(Aarini) Russian Call Girls Surat Call Now 8250077686 Surat Escorts 24x7
(Aarini) Russian Call Girls Surat Call Now 8250077686 Surat Escorts 24x7
 
2k Shots ≽ 9205541914 ≼ Call Girls In Mukherjee Nagar (Delhi)
2k Shots ≽ 9205541914 ≼ Call Girls In Mukherjee Nagar (Delhi)2k Shots ≽ 9205541914 ≼ Call Girls In Mukherjee Nagar (Delhi)
2k Shots ≽ 9205541914 ≼ Call Girls In Mukherjee Nagar (Delhi)
 
call Now 9811711561 Cash Payment乂 Call Girls in Dwarka
call Now 9811711561 Cash Payment乂 Call Girls in Dwarkacall Now 9811711561 Cash Payment乂 Call Girls in Dwarka
call Now 9811711561 Cash Payment乂 Call Girls in Dwarka
 
Pokemon Go... Unraveling the Conspiracy Theory
Pokemon Go... Unraveling the Conspiracy TheoryPokemon Go... Unraveling the Conspiracy Theory
Pokemon Go... Unraveling the Conspiracy Theory
 
2k Shots ≽ 9205541914 ≼ Call Girls In Palam (Delhi)
2k Shots ≽ 9205541914 ≼ Call Girls In Palam (Delhi)2k Shots ≽ 9205541914 ≼ Call Girls In Palam (Delhi)
2k Shots ≽ 9205541914 ≼ Call Girls In Palam (Delhi)
 

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