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

[RailsConf 2022] Testing legacy code when you dislike tests (and legacy code)

[RailsConf 2022] Testing legacy code when you dislike tests (and legacy code)

Are you supporting legacy code? Would you like to stop? A good testing strategy can transform legacy code into living code that is resilient and easy to evolve.

Learn why legacy code is so difficult to maintain and identify where tests can make the most impact. Not just any tests, though! We'll dive into the characteristics of high-value versus low-value tests and learn techniques for writing tests that minimize the cost of change.

Developers of any experience level can benefit from these concepts. Familiarity with Rails and an automated testing framework is helpful but not required.

Maeve Revels
PRO

May 19, 2022
Tweet

Other Decks in Programming

Transcript

  1. @maeverevels #RailsConf2022
    Maeve Revels
    Testing Legacy Code
    When You Dislike Testing


    (and Legacy Code)
    betteroff.dev/railsconf-2022-testing-legacy-code

    View Slide

  2. @maeverevels #RailsConf2022
    Agenda
    Questions that deserve answers
    • What is legacy code?


    • Why should we test it?


    • When should we test it?


    • What should we test?


    • How do we avoid legacy tests?


    • How do we write high-value tests?

    View Slide

  3. @maeverevels #RailsConf2022
    What is legacy code?

    View Slide

  4. View Slide

  5. View Slide

  6. View Slide

  7. @maeverevels #RailsConf2022
    Anonymous dev at your old job, probably
    Code originally written by someone else
    that is currently broken in production.

    View Slide

  8. @maeverevels #RailsConf2022
    • 20 years of experience creating legacy code


    • Principal Software Engineer at


    • Platform team - infrastructure and scalability
    @maeverevels
    @maeve
    [email protected]
    Maeve Revels

    View Slide

  9. @maeverevels #RailsConf2022
    Brownfield


    development

    View Slide

  10. @maeverevels #RailsConf2022
    Brownfield


    development
    💩

    View Slide

  11. View Slide

  12. @maeverevels #RailsConf2022
    Support burden
    https://xkcd.com/2347/

    View Slide

  13. @maeverevels #RailsConf2022
    Risk of the unknown

    View Slide

  14. @maeverevels #RailsConf2022
    High cost of change

    View Slide

  15. @maeverevels #RailsConf2022
    Legacy code is


    any code deployed to production


    without tests

    View Slide

  16. @maeverevels #RailsConf2022

    View Slide

  17. @maeverevels #RailsConf2022
    Michael C. Feathers, Working Effectively with Legacy Code
    Code without tests is bad code.

    View Slide

  18. @maeverevels #RailsConf2022
    Not my code

    View Slide

  19. @maeverevels #RailsConf2022
    Not my code
    please not my code

    View Slide

  20. @maeverevels #RailsConf2022
    Why should we write tests?

    View Slide

  21. Support burden
    Cost
    Time
    @maeverevels #RailsConf2022
    Go-to-market blog post
    Weekly email newsletter
    Social media campaign
    Call to action in application

    View Slide

  22. Risk of the unknown
    Risk
    Time
    @maeverevels #RailsConf2022

    View Slide

  23. Risk of the unknown
    Risk
    Time
    @maeverevels #RailsConf2022
    Dev switches projects

    View Slide

  24. Risk of the unknown
    Risk
    Time
    @maeverevels #RailsConf2022
    Dev surprised by git blame
    Dev switches projects

    View Slide

  25. Cost of change
    Cost
    Time
    @maeverevels #RailsConf2022

    View Slide

  26. Cost of change
    Cost
    Time
    @maeverevels #RailsConf2022
    Passed acceptance testing

    View Slide

  27. Cost of change
    Cost
    Time
    @maeverevels #RailsConf2022
    Passed acceptance testing
    Tweaks based on feedback

    View Slide

  28. Cost of change
    Cost
    Time
    @maeverevels #RailsConf2022
    Passed acceptance testing
    Tweaks based on feedback
    Changes for large customer

    View Slide

  29. Cost of change
    Cost
    Time
    @maeverevels #RailsConf2022
    Passed acceptance testing
    Tweaks based on feedback
    Changes for large customer 🤞

    View Slide

  30. Cost of change
    Cost
    Time
    @maeverevels #RailsConf2022
    Passed acceptance testing
    Tweaks based on feedback
    Changes for large customer
    Production incident 😱

    View Slide

  31. Cost of change
    Cost
    Time
    @maeverevels #RailsConf2022
    Passed acceptance testing
    Tweaks based on feedback
    Changes for large customer
    Production incident
    Time to write tests…

    View Slide

  32. Cost of change
    Cost
    Time
    @maeverevels #RailsConf2022
    Passed acceptance testing
    Tweaks based on feedback
    Changes for large customer
    Production incident
    PM wants new functionality

    View Slide

  33. @maeverevels #RailsConf2022
    Time

    View Slide

  34. @maeverevels #RailsConf2022
    Gompertz function
    Time

    View Slide

  35. @maeverevels #RailsConf2022
    Gompertz growth model
    Time
    • Growth rate of organisms


    • Population dynamics


    • Market demand for new products


    • Adoption of new technology

    View Slide

  36. @maeverevels #RailsConf2022
    Maintaining legacy code
    Risk
    Time

    View Slide

  37. @maeverevels #RailsConf2022
    When should we write tests?

    View Slide

  38. @maeverevels #RailsConf2022
    Optimize inputs
    Risk
    Time
    f(t) = aeln( c
    a
    )(1 − e−bt)

    View Slide

  39. @maeverevels #RailsConf2022
    Optimize inputs
    Risk
    Time
    b = 0.20
    f(t) = aeln( c
    a
    )(1 − e−bt)

    View Slide

  40. @maeverevels #RailsConf2022
    Optimize inputs
    Risk
    Time
    b = 0.14
    f(t) = aeln( c
    a
    )(1 − e−bt)

    View Slide

  41. @maeverevels #RailsConf2022
    Optimize inputs
    Risk
    Time
    b = 0.10
    f(t) = aeln( c
    a
    )(1 − e−bt)

    View Slide

  42. @maeverevels #RailsConf2022
    Optimize inputs
    Risk
    Time
    b = 0.08
    f(t) = aeln( c
    a
    )(1 − e−bt)

    View Slide

  43. @maeverevels #RailsConf2022
    Maintaining legacy code
    Risk
    Time

    View Slide

  44. @maeverevels #RailsConf2022
    Testing legacy code
    Risk
    Time

    View Slide

  45. @maeverevels #RailsConf2022
    Testing legacy code
    Risk
    Time

    View Slide

  46. @maeverevels #RailsConf2022
    Testing legacy code
    Risk
    Time

    View Slide

  47. @maeverevels #RailsConf2022
    Testing legacy code
    Risk
    Time

    View Slide

  48. @maeverevels #RailsConf2022
    Testing legacy code
    Risk
    Time

    View Slide

  49. @maeverevels #RailsConf2022
    Testing legacy code
    Risk
    Time

    View Slide

  50. @maeverevels #RailsConf2022
    Testing legacy code
    Risk
    Time

    View Slide

  51. @maeverevels #RailsConf2022
    Testing legacy code
    Risk
    Time

    View Slide

  52. @maeverevels #RailsConf2022
    Testing legacy code
    Risk
    Time

    View Slide

  53. @maeverevels #RailsConf2022
    What should we test?

    View Slide

  54. @maeverevels #RailsConf2022
    Application behavior

    View Slide

  55. @maeverevels #RailsConf2022
    The tests should pass as long as
    application behavior remains
    unchanged

    View Slide

  56. @maeverevels #RailsConf2022
    @maeverevels #RailsConf2022
    Seam Model

    View Slide

  57. @maeverevels #RailsConf2022
    A seam is a place where


    we can change behavior without
    changing code in that place

    View Slide

  58. @maeverevels #RailsConf2022
    An enabling point is a place where


    we decide to use one behavior


    or another

    View Slide

  59. @maeverevels #RailsConf2022
    Application seams

    View Slide

  60. View Slide

  61. View Slide

  62. @maeverevels #RailsConf2022
    Subsystem seams

    View Slide

  63. @maeverevels #RailsConf2022

    View Slide

  64. @maeverevels #RailsConf2022

    View Slide

  65. @maeverevels #RailsConf2022

    View Slide

  66. @maeverevels #RailsConf2022

    View Slide

  67. @maeverevels #RailsConf2022

    View Slide

  68. @maeverevels #RailsConf2022

    View Slide

  69. @maeverevels #RailsConf2022

    View Slide

  70. @maeverevels #RailsConf2022

    View Slide

  71. @maeverevels #RailsConf2022

    View Slide

  72. @maeverevels #RailsConf2022

    View Slide

  73. @maeverevels #RailsConf2022

    View Slide

  74. @maeverevels #RailsConf2022

    View Slide

  75. @maeverevels #RailsConf2022

    View Slide

  76. @maeverevels #RailsConf2022
    Object seams

    View Slide

  77. @maeverevels #RailsConf2022

    View Slide

  78. @maeverevels #RailsConf2022
    How do we avoid legacy tests?

    View Slide

  79. @maeverevels #RailsConf2022
    Sandi Metz, Practical Object-Oriented Design: An Agile Primer Using Ruby
    From a practical point of view, changeability
    is the only design metric that matters; code
    that's easy to change is well-designed.

    View Slide

  80. @maeverevels #RailsConf2022
    Sandi Metz, Practical Object-Oriented Design: An Agile Primer Using Ruby
    Efficient tests prove that altered code
    continues to behave correctly without
    raising overall costs.

    View Slide

  81. @maeverevels #RailsConf2022
    Focus on high-value testing

    View Slide

  82. @maeverevels #RailsConf2022
    Test value
    Bugs prevented Initial cost Maintenance

    View Slide

  83. @maeverevels #RailsConf2022
    Test value
    Time
    Bugs prevented Initial cost Maintenance

    View Slide

  84. @maeverevels #RailsConf2022
    Cost(change)
    >
    Cost(execution)

    View Slide

  85. @maeverevels #RailsConf2022
    The tests should pass as long as
    application behavior remains
    unchanged

    View Slide

  86. @maeverevels #RailsConf2022
    If it is easier to fix regressions


    than it is to fix the test,


    that test is not worth the effort

    View Slide

  87. @maeverevels #RailsConf2022
    How do we write high-value
    tests?

    View Slide

  88. @maeverevels #RailsConf2022
    Integration tests

    View Slide

  89. @maeverevels #RailsConf2022
    Integration test value
    Bugs prevented Initial cost Maintenance

    View Slide

  90. @maeverevels #RailsConf2022
    Always create a harness for a


    happy path test

    View Slide

  91. @maeverevels #RailsConf2022
    Avoid fixture (or factory) fallout

    View Slide

  92. @maeverevels #RailsConf2022
    Use dynamic count assertions

    View Slide

  93. @maeverevels #RailsConf2022
    # Don't assert static counts


    expect(page).to have_content("4 requirements")


    # Get dynamic counts from the data layer instead


    expect(page).to have_content("#{feature.requirements.count} requirements")

    View Slide

  94. @maeverevels #RailsConf2022
    Abstract away selectors

    View Slide

  95. @maeverevels #RailsConf2022
    # Avoid direct use of selector logic


    expect(page).to have_css(".card__field i.fa-flag[data-
    title='International expansion']")






    View Slide

  96. @maeverevels #RailsConf2022
    # Avoid direct use of selector logic


    expect(page).to have_css(".card__field i.fa-flag[data-
    title='International expansion']")


    # Extract complex selectors into descriptive methods


    def have_goal_icon(goal_name)


    have_css(".card__field i.fa-flag[data-title='#{goal_name}']")


    end


    expect(page).to have_goal_icon("International expansion")

    View Slide

  97. @maeverevels #RailsConf2022
    Functional testing

    View Slide

  98. @maeverevels #RailsConf2022
    Functional test value
    Bugs prevented Initial cost Maintenance

    View Slide

  99. @maeverevels #RailsConf2022
    Testing behavior with


    many known edge cases

    View Slide

  100. @maeverevels #RailsConf2022
    Stub or mock


    external resources

    View Slide

  101. @maeverevels #RailsConf2022
    Unit tests

    View Slide

  102. @maeverevels #RailsConf2022

    View Slide

  103. @maeverevels #RailsConf2022

    View Slide

  104. @maeverevels #RailsConf2022

    View Slide

  105. @maeverevels #RailsConf2022

    View Slide

  106. @maeverevels #RailsConf2022

    View Slide

  107. @maeverevels #RailsConf2022
    Unit test value
    Bugs prevented Initial cost Maintenance

    View Slide

  108. @maeverevels #RailsConf2022
    Only keep unit tests that are
    useful to you right now

    View Slide

  109. @maeverevels #RailsConf2022
    Unit testing techniques


    can be generalized

    View Slide

  110. @maeverevels #RailsConf2022
    Putting it all together

    View Slide

  111. @maeverevels #RailsConf2022
    • Legacy code maximizes costs


    • Testing minimizes costs


    • Focus on behavior over inputs/outputs


    • Write high-value tests for high-value code

    View Slide

  112. @maeverevels #RailsConf2022
    The tests should pass as long as
    application behavior remains
    unchanged

    View Slide

  113. In memory of


    Zach Schneider


    1992
    -
    2021

    View Slide

  114. @maeverevels #RailsConf2022
    Thank you
    @maeverevels
    [email protected]
    betteroff.dev/railsconf-2022-testing-legacy-code

    View Slide