Upgrade to Pro — share decks privately, control downloads, hide ads and more …

How to Tame TDD

How to Tame TDD

TDD is one of the most controversial subjects in the software industry. Its promises are many: a codebase that continuously checks itself, reduces maintainability costs, simplifies introduction of changes, and allows faster work cycles. Unfortunately, high expectations lead to high disappointments. Many good teams have found themselves enchained in webs of mock objects that not only hindered every promise made by TDD originally, but were more costly to maintain than the codebase itself! No wonder why TDD has been declared as waste by many respected professionals. Heck, I was hit by TDD's dark side myself... but found a way to slay the complexity dragon. In this session I'm going to share what I've learned the hard way: how to make peace with mock objects, how to avoid accidental complexities, and ultimately, how to make TDD work for you.

Vladik Khononov

November 17, 2017
Tweet

More Decks by Vladik Khononov

Other Decks in Programming

Transcript

  1. 16 – 17 November, Sofia ISTACON.ORG My twitter is I

    blog Chief Architect My email is vladik
 @ vladikk @ vladikk.com @ Naxex @ khononov.com

  2. 16 – 17 November, Sofia ISTACON.ORG Agenda TDD Journey Fix

    #1: Scoping Unit Tests Fix #2: Testing Strategies Common misconceptions Fix #3: TDD & Design TDD Revisited Cyclomatic Complexity Business Logic Modeling Patterns
  3. 16 – 17 November, Sofia ISTACON.ORG • Write a failing

    Unit Test • Write code to a make it pass • Proper design • Isolate the Class under test
  4. 16 – 17 November, Sofia ISTACON.ORG • Write a failing

    Unit Test • Write code to a make it pass • Proper design • Isolate the Class under test Fakes Mocks Stubs
  5. 16 – 17 November, Sofia ISTACON.ORG • Write a failing

    Unit Test • Write code to a make it pass • Proper design • Isolate the Class under test • Refactor Fakes Mocks Stubs
  6. 16 – 17 November, Sofia ISTACON.ORG • Write a failing

    Unit Test • Write code to a make it pass • Proper design • Isolate the Class under test • Refactor Fakes Mocks Stubs
  7. 16 – 17 November, Sofia ISTACON.ORG • Write a failing

    Unit Test • Write code to a make it pass • Proper design • Isolate the Class under test • Refactor • Repeat • 100% unit test coverage • Maintainable and extendable code • FAST!!! Fakes Mocks Stubs
  8. 16 – 17 November, Sofia ISTACON.ORG • Write a failing

    Unit Test • Write code to a make it pass • Proper design • Isolate the Class under test • Refactor • Repeat • 100% unit test coverage • Maintainable and extendable code • FAST!!! Fakes Mocks Stubs
  9. 16 – 17 November, Sofia ISTACON.ORG • Write a failing

    Unit Test • Write code to a make it pass • Proper design • Isolate the Class under test • Refactor • Repeat • 100% unit test coverage • Maintainable and extendable code • FAST!!! Fakes Mocks Stubs
  10. 16 – 17 November, Sofia ISTACON.ORG • Write a failing

    Unit Test • Write code to a make it pass • Proper design • Isolate the Class under test • Refactor • Repeat • 100% unit test coverage • Maintainable and extendable code • FAST!!! Fakes Mocks Stubs TEST-DRIVEN DESIGN
  11. 16 – 17 November, Sofia ISTACON.ORG Fakes Mocks Stubs •

    Write a failing Unit Test • Write code to a make it pass • Proper design • Isolate the Class under test • Refactor • Repeat • 100% unit test coverage • Maintainable and extendable code • FAST!!! TEST-DRIVEN DESIGN
  12. 16 – 17 November, Sofia ISTACON.ORG Fakes Mocks Stubs •

    Write a failing Unit Test • Write code to a make it pass • Proper design • Isolate the Class under test • Refactor • Repeat • 100% unit test coverage • Maintainable and extendable code • FAST!!! TEST-DRIVEN DESIGN
  13. 16 – 17 November, Sofia ISTACON.ORG Fakes Mocks Stubs •

    Write a failing Unit Test • Write code to a make it pass • Proper design • Isolate the Class under test • Refactor • Repeat • 100% unit test coverage • Maintainable and extendable code • FAST!!! TEST-DRIVEN DESIGN
  14. 16 – 17 November, Sofia ISTACON.ORG ” “ A unit

    is a single logical functional use case in the system that can be invoked by some
 public interface… A unit can span a single method, a whole class or multiple classes working together to achieve one single logical purpose Roy Osherove The Art of Unit Testing
  15. 16 – 17 November, Sofia ISTACON.ORG Create a new list

    Create a new task Mark as completed Update list name Units
  16. 16 – 17 November, Sofia ISTACON.ORG Unit Test: Create a

    new task List Task Status Database Online Calendar Isolate External systems
  17. 16 – 17 November, Sofia ISTACON.ORG • Align Units with

    the system’s use cases • Do not isolate implementation details • Isolate external systems and services Adjustment #1
  18. 16 – 17 November, Sofia ISTACON.ORG public User(long id, Agency

    agency, User createdBy, DateTime createdOn) : base(createdBy, createdOn) { this.Id = id; this.agency = agency; this.AutoLeadAssignmentPausedUntil = DateTime.UtcNow; } public User(Agency agency, User createdBy, DateTime createdOn) : base(createdBy, createdOn) { this.agency = agency; this.AutoLeadAssignmentPausedUntil = DateTime.UtcNow; } public virtual void AddGroup(Group group) { RemoveGroupsWithOtherDesks(group.Desk); groups.AddIfNotExists(group); if (!group.Users.Contains(this)) group.AddUser(this, false); } public virtual void SetActive(bool value) { if (value && !this.Active) this.StatisticsShouldBeRecalculated = true; this.Active = value; } private void RemoveGroupsWithOtherDesks(Desk newDesk) { if (newDesk == Desk) return; foreach (var g in groups) g.RemoveUser(this); groups.Clear(); } public virtual void AddOrganizationUnit(OrganizationUnitUserAssociation ouUserAssociation) { if (organizationUnitsAssociations.All(x => x.Unit.Id != ouUserAssociation.Unit.Id)) organizationUnitsAssociations.Add(ouUserAssociation); } public virtual Desk[] GetManagedDesks(Advertiser advertiser) { return this.CanAccessAllDesks ? advertiser.Desks.ToArray() : this.ManagesDesks.ToArray(); } public virtual Group[] GetManagedGroups(Advertiser advertiser) { return this.CanAccessAllDesks ? advertiser.Desks.SelectMany(x => x.Groups).Distinct().ToArray() : GetManagedDesks(advertiser) .SelectMany(x => x.Groups) .Concat(this.Groups.Where(x => x.IsManagedBy(this))) .Distinct() .ToArray(); } public virtual Advertiser[] GetAllowedAdvertisers()
  19. 16 – 17 November, Sofia ISTACON.ORG Number of independent execution

    paths within a section of code Cyclomatic Complexity
  20. 16 – 17 November, Sofia ISTACON.ORG if(SomeCondition()) DoSomething(); else DoSomethingElse();

    if(AnotherCondition()) ExecuteSomething(); else ExecuteSomethingElse(); High C. Complexity
  21. 16 – 17 November, Sofia ISTACON.ORG M = E −

    N + 2P M = E − N + 2 M = π − s + 2
  22. 16 – 17 November, Sofia ISTACON.ORG Transaction Script Active Record

    Domain Model Business Logic Modeling Patterns
  23. 16 – 17 November, Sofia ISTACON.ORG Simple logic Extract, Transform

    and Load Procedural code Transaction Script
  24. 16 – 17 November, Sofia ISTACON.ORG DB.StartTransaction(); var job =

    DB.LoadNextJob();
 var json = LoadFile(source); var xml = ConvertJsonToXml(json); WriteFile(destionation, xml.ToString(); DB.MarkJobAsCompleted(job); DB.Commit()
  25. 16 – 17 November, Sofia ISTACON.ORG Active Record Presentation Layer

    Active Records Service / Application Layer Data Structures Business Logic
  26. 16 – 17 November, Sofia ISTACON.ORG public class User {

    public Guid Id { get; set; }
 public string Name { get; set; }
 public List<Interest> Interests { get; set; }
 public Address Address { get; set; } public void Save() { … }
 public void Delete() { … }
 public static User Get(Guid id) { … }
 public static List<User> GetAll() { … } }
  27. 16 – 17 November, Sofia ISTACON.ORG Active Record Presentation Layer

    Service / Application Layer Active Records Data Structures Business Logic
  28. 16 – 17 November, Sofia ISTACON.ORG public class CreateUser {

    public void Execute(userDetails) { try { DB.StartTransaction();
 var user = new User();
 user.Name = userDetails.Name;
 user.Email = userDetails.Email;
 user.Save();
 DB.Commit(); } catch { DB.Rollback();
 throw; } } }
  29. 16 – 17 November, Sofia ISTACON.ORG public class User {

    public Guid Id { get; set; }
 public string Name { get; set; }
 public List<Interest> Interests { get; set; }
 public Address Address { get; set; } public void Save() { … }
 public void Delete() { … }
 public static User Get(Guid id) { … }
 public static List<User> GetAll() { … } }
  30. 16 – 17 November, Sofia ISTACON.ORG public class CreateUser {

    public void Execute(userDetails) { try { DB.StartTransaction();
 var user = new User();
 user.Name = userDetails.Name;
 user.Email = userDetails.Email;
 user.Save();
 DB.Commit(); } catch { DB.Rollback();
 throw; } } }
  31. 16 – 17 November, Sofia ISTACON.ORG public class User {

    public Guid Id { get; private set; }
 public string Name { get; private set; }
 public List<Interest> Interests { get; private set; }
 public Address Address { get; private set; } public void UpdateDetails() { … }
 public void AddInterest() { … }
 public static User InitializeNew() { … } }
  32. 16 – 17 November, Sofia ISTACON.ORG • Transaction Script -

    100% End to end Adjustment #2: Testing Strategies
  33. 16 – 17 November, Sofia ISTACON.ORG • Transaction Script -

    100% End to end • Active Record - 100% Integration tests Adjustment #2: Testing Strategies
  34. 16 – 17 November, Sofia ISTACON.ORG • Transaction Script -

    100% End to end • Active Record - 100% Integration tests • Domain Model - 100% Unit tests Adjustment #2: Testing Strategies
  35. 16 – 17 November, Sofia ISTACON.ORG Well designed code is

    testable Testable code is well designed
  36. 16 – 17 November, Sofia ISTACON.ORG Well designed code is

    testable Testable code is well designed
  37. 16 – 17 November, Sofia ISTACON.ORG Transaction Script Active Record

    Domain Model Business Logic Modeling Patterns
  38. 16 – 17 November, Sofia ISTACON.ORG • System’s design is

    driven by its business domain, its needs and its problems • Development can be driven by tests Adjustment #3: Design vs. Development
  39. 16 – 17 November, Sofia ISTACON.ORG • System’s design is

    driven by its business domain, its needs and its problems • Development can be driven by tests • Test-Driven Design • Test-Driven Development Adjustment #3: Design vs. Development
  40. 16 – 17 November, Sofia ISTACON.ORG [TestMethod] public void SetAsUnqualified_ClearWeights()

    { var lead = BuildLead(10, 15, 24, false); lead.SetAsUnqualified(updatedContactDetails, policy, 12); Assert.AreEqual(12, lead.Proprity); }
  41. 16 – 17 November, Sofia ISTACON.ORG [TestMethod] public void SetAsUnqualified_ClearWeights()

    { var lead = BuildLead(10, 15, 24, false); lead.SetAsUnqualified(updatedContactDetails, policy, 12); Assert.AreEqual(12, lead.Proprity); }
  42. 16 – 17 November, Sofia ISTACON.ORG [TestMethod] public void SetAsUnqualified_ClearWeights()

    { var lead = BuildLead(someUserId, groupId, events, notNew); lead.SetAsUnqualified(updatedContactDetails, policy, agentId); Assert.AreEqual(newLeadPriority, lead.Proprity); }
  43. 16 – 17 November, Sofia ISTACON.ORG [TestMethod] public void CreateLeadActivity_CreateValidJSON()

    { var depositActivity = new DepositActivity(); depositActivity.ActivityTypeId = 6002; depositActivity.AdvertiserActivityTypeId = 3; depositActivity.ActivityDescription = "Deposit-Approved"; depositActivity.AdvertiserId = 1; depositActivity.AgencyId = 23; depositActivity.BrandId = 1; depositActivity.CustomerId = "[email protected]"; depositActivity.CreatedDate = new DateTime(2015, 03, 23, 9, 30, 42); depositActivity.Domain = "deposit.23traders.com"; depositActivity.LabelId = 1; depositActivity.TransactionId = 25; depositActivity.CreatedDateStr = "2015-03-23T09:30:42+00:00"; var leadActivityDTo = new LeadActivityDTO(depositActivity, false); var serializeObject = new JsonNetAdapter().SerializeObject(leadActivityDTo); A.CallTo(() => jsonSerializer.SerializeObject(leadActivityDTo)).WithAnyArguments().Returns(serializeObject); var result = new DepositActivityFactory(jsonSerializer).CreateLeadActivity(depositActivity); Assert.AreEqual(result, leadActivityJSON); }
  44. 16 – 17 November, Sofia ISTACON.ORG [TestMethod] public void CreateLeadActivity_CreateValidJSON()

    { var depositActivity = new DepositActivity(); depositActivity.ActivityTypeId = 6002; depositActivity.AdvertiserActivityTypeId = 3; depositActivity.ActivityDescription = "Deposit-Approved"; depositActivity.AdvertiserId = 1; depositActivity.AgencyId = 23; depositActivity.BrandId = 1; depositActivity.CustomerId = "[email protected]"; depositActivity.CreatedDate = new DateTime(2015, 03, 23, 9, 30, 42); depositActivity.Domain = "deposit.23traders.com"; depositActivity.LabelId = 1; depositActivity.TransactionId = 25; depositActivity.CreatedDateStr = "2015-03-23T09:30:42+00:00"; var leadActivityDTo = new LeadActivityDTO(depositActivity, false); var serializeObject = new JsonNetAdapter().SerializeObject(leadActivityDTo); A.CallTo(() => jsonSerializer.SerializeObject(leadActivityDTo)).WithAnyArguments().Returns(serializeObject); var result = new DepositActivityFactory(jsonSerializer).CreateLeadActivity(depositActivity); Assert.AreEqual(result, leadActivityJSON); }
  45. 16 – 17 November, Sofia ISTACON.ORG Scenario: Check monthly report

    Given today is 2015/05/18 And a payment was made for the 2015/04 period on 2015/04/01 When we generate agent's monthly report for 2015/03 Then payment log cannot be generated And the payment log was generated on 2015/04/01
  46. 16 – 17 November, Sofia ISTACON.ORG public virtual void SetAsConverted(…)

    { UpdateContactDetails(updatedContactDetails.Name); var waitDaysForDeposit = Settings.GetAppConfigInt("Days"); ClearWeights(); TrackPhoneCall(phoneNumber, convertedBy, convertedOn); ResetInteraction(); UpdateModifiedByAndOn(convertedBy.Id, convertedOn); Apply(new DepositPending(State.Id, interactionId)); } C.Complexity: Low or High?
  47. 16 – 17 November, Sofia ISTACON.ORG public virtual void SetAsConverted(…)

    { UpdateContactDetails(updatedContactDetails.Name); var waitDaysForDeposit = Settings.GetAppConfigInt("Days"); ClearWeights(); TrackPhoneCall(phoneNumber, convertedBy, convertedOn); ResetInteraction(); UpdateModifiedByAndOn(convertedBy.Id, convertedOn); Apply(new DepositPending(State.Id, interactionId)); } Unit Test or Integration Test?
  48. 16 – 17 November, Sofia ISTACON.ORG Active Record or Domain

    Model? You are working on a CRM system, and you are asked to build a service that calculates commissions for sales agents. There about 100 different commissions rules, and managers should be able to optimize them on the fly.
  49. 16 – 17 November, Sofia ISTACON.ORG Full Coverage by Unit

    or Integration Tests? You are asked to build a service that calculates commissions for sales agents. There are about 100 different commissions rules, and managers should be able to optimize them on the fly.
  50. 16 – 17 November, Sofia ISTACON.ORG • Write a failing

    Unit Test • Write code to a make it pass • Simplest design • Isolate the Class under test • Refactor • Repeat • 100% unit test coverage • Maintainable and extendable code • FAST!!! Fakes Mocks Stubs TEST-DRIVEN DESIGN
  51. 16 – 17 November, Sofia ISTACON.ORG • Write a failing

    Unit Test • Write code to a make it pass • Simplest design • Refactor • Repeat • 100% unit test coverage • FAST!!! Fakes Mocks Stubs TEST-DRIVEN DESIGN • Isolate the Class under test • Maintainable and extendable code
  52. 16 – 17 November, Sofia ISTACON.ORG • Write a failing

    Unit Test • Write code to a make it pass • Simplest design • Refactor • Repeat • 100% unit test coverage • FAST!!! TEST-DRIVEN DESIGN
  53. 16 – 17 November, Sofia ISTACON.ORG • Write code to

    a make it pass • Simplest design • Refactor • Repeat • FAST!!! TEST-DRIVEN DESIGN • Write a failing Unit Test • 100% unit test coverage
  54. 16 – 17 November, Sofia ISTACON.ORG • Write code to

    a make it pass • Simplest design • Refactor • Repeat • FAST!!! TEST-DRIVEN DESIGN • 100% unit test coverage
  55. 16 – 17 November, Sofia ISTACON.ORG • Write code to

    a make it pass • Simplest design • Refactor • Repeat • FAST!!! TEST-DRIVEN DESIGN
  56. 16 – 17 November, Sofia ISTACON.ORG • Write code to

    a make it pass • Simplest design • Refactor • Repeat • FAST!!! TEST-DRIVEN DESIGN
  57. 16 – 17 November, Sofia ISTACON.ORG • Write code to

    a make it pass
 • Repeat
 
 
 TEST-DRIVEN • Simplest design • Refactor • Fast DESIGN
  58. 16 – 17 November, Sofia ISTACON.ORG • Write code to

    a make it pass
 • Repeat
 
 
 TEST-DRIVEN • Refactor • Fast DESIGN
  59. 16 – 17 November, Sofia ISTACON.ORG • Write code to

    a make it pass
 • Repeat
 
 
 TEST-DRIVEN • Fast DESIGN
  60. 16 – 17 November, Sofia ISTACON.ORG • Write code to

    a make it pass
 • Repeat
 
 
 TEST-DRIVEN DESIGN
  61. 16 – 17 November, Sofia ISTACON.ORG • Write code to

    a make it pass
 • Repeat
 
 
 TEST-DRIVEN
  62. 16 – 17 November, Sofia ISTACON.ORG • Learn the business

    domain • Analyze • Assess its complexity TEST-DRIVEN DEVELOPMENT
  63. 16 – 17 November, Sofia ISTACON.ORG • Learn the business

    domain • Analyze • Assess its complexity • Design the solution • Business logic modeling pattern • Testing strategy TEST-DRIVEN DEVELOPMENT
  64. 16 – 17 November, Sofia ISTACON.ORG • Learn the business

    domain • Analyze • Assess its complexity • Design the solution • Business logic modeling pattern • Testing strategy • Development • Start with a failing test (according to the testing strategy) • Make it pass • Refactor TEST-DRIVEN DEVELOPMENT