SlideShare a Scribd company logo
1 of 20
Refactoring for
      Testability




(or how I learned to stop worrying
      and love failing tests)

  Presented by Aaron Evans <aevans@ancestry.com>
Refactoring for Testability


       What is refactoring?

       What is testability?

          How do we do it?
Refactoring for Testability


           Why refactor?

          My code is good
Refactoring for Testability


           Why refactor?

 The only constant is change
Refactoring for Testability


        User Interface Code
                    Don’t hard code configuration
              Handle errors gracefully
                       Don’t expose other layers
       Encapsulate implementation details
                   Don’t tie your code to vendor
               Use meaningful variable names
 Separate business logic from display logic
Refactoring for Testability


        Refactoring the UI

Yes, Virginia, there really is display logic

                  ID tags

      Individually testable components

           Maintainable UI tests
Refactoring for Testability


 Push testing down the stack

          Reduce brittle UI tests

              Run tests faster

       Isolate the system under test

 Think about code as individual components
Refactoring for Testability


                  SOLID
      Single Responsibility Principle

      Open / Closed Principle

      Liskov Substitution Principle

      Interface Segregation Principle

      Dependency Inversion Principle
Refactoring for Testability


     Refactoring Strategies

      Extract                             Consolidate
     Change                                      Move
    Encapsulate                          Substitute
       Hide                                  Expose

      Pull up                              Push down

           http://www.refactoring.com/catalog/
Refactoring for Testability

Becomes…
This…



Implementation
Refactoring for Testability


 Adding Layers of Abstraction
                Indirection

                Reusability

        Composition over Inheritance

        Database Abstraction Layer
                    ORM
               Domain Model
              Business Logic
Refactoring for Testability


     Simplifying Interfaces




     Exposing Functionality
Refactoring for Testability


              Decoupling




      Presentation and Business Logic
       Persistence and Domain Objects

       Interface from Implementation
Refactoring for Testability


       Dependency Injection
            No news is good news

         No framework is necessary

              Factory pattern

     Constructors / Setters / Builders

               Loose Coupling
Refactoring for Testability

                              Extract Interface




                              Inject Dependency
Refactoring for Testability

                               Use Mocks




                       Single Responsibility
Refactoring for Testability
Refactoring for Testability


       Reduce Dependencies
Refactoring for Testability


    Coding in business terms

           Common domain language

            Acceptance criteria

 BDD frameworks – RSPEC, Specflow, JBehave
Questions?



Thanks...   Aaron Evans <aevans@ancestry.com>

More Related Content

Similar to Refactoring Code for Testability

Modernize your-java ee-app-server-infrastructure
Modernize your-java ee-app-server-infrastructureModernize your-java ee-app-server-infrastructure
Modernize your-java ee-app-server-infrastructurezslmarketing
 
Pilot essentials webinar
Pilot essentials webinarPilot essentials webinar
Pilot essentials webinarMaarga Systems
 
Mobile DevOps - Trends and Chellenges
Mobile DevOps - Trends and ChellengesMobile DevOps - Trends and Chellenges
Mobile DevOps - Trends and ChellengesSanjeev Sharma
 
Java_Couse_Content
Java_Couse_ContentJava_Couse_Content
Java_Couse_ContentMV Solutions
 
ATDD in Practice
ATDD in PracticeATDD in Practice
ATDD in PracticeSteven Mak
 
Chef for DevOps - an Introduction
Chef for DevOps - an IntroductionChef for DevOps - an Introduction
Chef for DevOps - an IntroductionSanjeev Sharma
 
Automated Testing Of EPiServer CMS Sites
Automated Testing Of EPiServer CMS SitesAutomated Testing Of EPiServer CMS Sites
Automated Testing Of EPiServer CMS Sitesjoelabrahamsson
 
Agile Open Source Performance Testing Workshop for Business Managers
Agile Open Source Performance Testing Workshop for Business ManagersAgile Open Source Performance Testing Workshop for Business Managers
Agile Open Source Performance Testing Workshop for Business ManagersClever Moe
 
Agile Open Source Performance Test Workshop for Developers, Testers, IT Ops
Agile Open Source Performance Test Workshop for Developers, Testers, IT OpsAgile Open Source Performance Test Workshop for Developers, Testers, IT Ops
Agile Open Source Performance Test Workshop for Developers, Testers, IT OpsClever Moe
 
TechDays 2013 Juhani Lind: Acceptance Test Driven Development With VS 2012
TechDays 2013 Juhani Lind: Acceptance Test Driven Development With VS 2012TechDays 2013 Juhani Lind: Acceptance Test Driven Development With VS 2012
TechDays 2013 Juhani Lind: Acceptance Test Driven Development With VS 2012Tieturi Oy
 
Software Design for Testability
Software Design for TestabilitySoftware Design for Testability
Software Design for Testabilityamr0mt
 
Share point 2010 unit and integration testing
Share point 2010 unit and integration testingShare point 2010 unit and integration testing
Share point 2010 unit and integration testingEric Shupps
 
Beyond Scrum: Scaling Agile with Continuous Delivery and Subversion
Beyond Scrum: Scaling Agile with Continuous Delivery and SubversionBeyond Scrum: Scaling Agile with Continuous Delivery and Subversion
Beyond Scrum: Scaling Agile with Continuous Delivery and SubversionProduct Marketing Services
 
A comprehensive formal verification solution for ARM based SOC design
A comprehensive formal verification solution for ARM based SOC design A comprehensive formal verification solution for ARM based SOC design
A comprehensive formal verification solution for ARM based SOC design chiportal
 
Checking the health of your active directory enviornment
Checking the health of your active directory enviornmentChecking the health of your active directory enviornment
Checking the health of your active directory enviornmentSpiffy
 
Framework Engineering
Framework EngineeringFramework Engineering
Framework EngineeringYoungSu Son
 

Similar to Refactoring Code for Testability (20)

Modernize your-java ee-app-server-infrastructure
Modernize your-java ee-app-server-infrastructureModernize your-java ee-app-server-infrastructure
Modernize your-java ee-app-server-infrastructure
 
Pilot essentials webinar
Pilot essentials webinarPilot essentials webinar
Pilot essentials webinar
 
Mobile DevOps - Trends and Chellenges
Mobile DevOps - Trends and ChellengesMobile DevOps - Trends and Chellenges
Mobile DevOps - Trends and Chellenges
 
Java_Couse_Content
Java_Couse_ContentJava_Couse_Content
Java_Couse_Content
 
ATDD in Practice
ATDD in PracticeATDD in Practice
ATDD in Practice
 
Chef for DevOps - an Introduction
Chef for DevOps - an IntroductionChef for DevOps - an Introduction
Chef for DevOps - an Introduction
 
Chandrasekhar Pendyala_SeniorQA
Chandrasekhar Pendyala_SeniorQAChandrasekhar Pendyala_SeniorQA
Chandrasekhar Pendyala_SeniorQA
 
Automated Testing Of EPiServer CMS Sites
Automated Testing Of EPiServer CMS SitesAutomated Testing Of EPiServer CMS Sites
Automated Testing Of EPiServer CMS Sites
 
Agile Open Source Performance Testing Workshop for Business Managers
Agile Open Source Performance Testing Workshop for Business ManagersAgile Open Source Performance Testing Workshop for Business Managers
Agile Open Source Performance Testing Workshop for Business Managers
 
Agile Open Source Performance Test Workshop for Developers, Testers, IT Ops
Agile Open Source Performance Test Workshop for Developers, Testers, IT OpsAgile Open Source Performance Test Workshop for Developers, Testers, IT Ops
Agile Open Source Performance Test Workshop for Developers, Testers, IT Ops
 
Coding Naked
Coding NakedCoding Naked
Coding Naked
 
TechDays 2013 Juhani Lind: Acceptance Test Driven Development With VS 2012
TechDays 2013 Juhani Lind: Acceptance Test Driven Development With VS 2012TechDays 2013 Juhani Lind: Acceptance Test Driven Development With VS 2012
TechDays 2013 Juhani Lind: Acceptance Test Driven Development With VS 2012
 
Software Design for Testability
Software Design for TestabilitySoftware Design for Testability
Software Design for Testability
 
Share point 2010 unit and integration testing
Share point 2010 unit and integration testingShare point 2010 unit and integration testing
Share point 2010 unit and integration testing
 
Beyond Scrum: Scaling Agile with Continuous Delivery and Subversion
Beyond Scrum: Scaling Agile with Continuous Delivery and SubversionBeyond Scrum: Scaling Agile with Continuous Delivery and Subversion
Beyond Scrum: Scaling Agile with Continuous Delivery and Subversion
 
A comprehensive formal verification solution for ARM based SOC design
A comprehensive formal verification solution for ARM based SOC design A comprehensive formal verification solution for ARM based SOC design
A comprehensive formal verification solution for ARM based SOC design
 
Answer powerpoint template
Answer powerpoint templateAnswer powerpoint template
Answer powerpoint template
 
Checking the health of your active directory enviornment
Checking the health of your active directory enviornmentChecking the health of your active directory enviornment
Checking the health of your active directory enviornment
 
How to make technical deicisons?
How to make technical deicisons?How to make technical deicisons?
How to make technical deicisons?
 
Framework Engineering
Framework EngineeringFramework Engineering
Framework Engineering
 

Refactoring Code for Testability

Editor's Notes

  1. Hi, my name is Aaron Evans.My presentation is on refactoring. Specifically, refactoring to improve testability. So we can write better tests and have more confidence when we refactor code (and add new features) that we don&apos;t break existing functionality -- except when we really want to. That&apos;s a nice idea, it&apos;s but not practical. I have all this legacy code -- some of it I don&apos;t understand -- or even want to.Here in the real world, we do what we can, test new features as best we can, and try to make sure we don&apos;t break anything with regression testing (often manual) -- and try not to look too closely at that cruft. Maybe we&apos;ll poke it occasionally with a long sharp stick to see if it&apos;s still working, but there&apos;s no time to clean it up even if we wanted to.I hope I can touch on a few points that will help you think about how to tackle a refactoring task with an eye to improving testability. And talk a bit about why that&apos;s a good thing.
  2. What is refactoring?First a few definitions:&quot;Refactoring is a disciplined technique for restructuring an existing body of code, altering its internal structure without changing its external behavior&quot;That&apos;s from Martin Fowler. He literally wrote the book on refactoring. In simpler words, refactoring means &quot;changing your code but not changing what it does.&quot;That&apos;s why it&apos;s so hard to convince your manager that you really need to go back and clean up that mess you shipped last month. &quot;If it ain&apos;t broke, don&apos;t fix it.&quot;If refactoring doesn&apos;t change how code works, the test of a successful refactor is that after your changes, your tests (you do have tests, right?) still pass. If you have confidence in your tests, you should be confident in your refactoring.Nice theory, right?What is testability?So what do I mean by testability? Being able to write tests that you have confidence in. Tests are often brittle, and sometimes inaccurate or misleading. A code coverage tool can show a metric of 100% and not cover use cases very thoroughly. Above all, keeping our tests up to date with code changes is a never ending and thankless task. Testers are often limited in what they can test, and we often have to rely on UI testing, manual testing, heuristics, and even our gut feeling or instincts. But sometimes a slight change in code can make our work easier. That tantalizing hope is what refactoring for testability is about.How do we do it?I&apos;m going to highlight a few strategies for refactoring and go over how they can improve the testability of code.
  3. Why RefactorWhy should we refactor? Especially if &quot;It Just Works&quot; (TM). If we know it works, it must have been tested, and if it&apos;s well tested, it must be testable.My code is goodRight?We&apos;re all great coders, we use the appropriate design patterns, and we don&apos;t make mistakes...Our code is flawless. But those guys before left us a mess, and we have to clean it up.
  4. Why refactor?Even if you&apos;re the Michelangelo of code, time will affect your code. If not yours, then all other code around it. Even the Sistine Chapel needed a touch up after a few hundred years.The only constant is change.And systems will change, requirements will change. Operating Systems and device drivers will change. Database schemas and use cases will change.Maybe not all for the better, but it&apos;s something we have to learn to live with.And some of us may actually improve our skills over time. Code I wrote 2 or 5 years ago is not as clean as code I wrote yesterday (but you should see the horrible mess I wrote last week).If you practice agile, you start coding with a simple solution -- the simplest possible solution that could possibly work, and elaborate from there. It&apos;s like starting with an impressionist painting and adding the details, until the picture comes into clearer focus as you trick the requirements and implementation details out from the business customers.All of these are causes of change.
  5. User Interface CodeHow many of you have seen something like this?PHP Code SampleI chose this example -- right out of the PHP manual -- because it&apos;s an easy target. But the problems illustrated aren&apos;t just because it&apos;s PHP. You can write fairly clean PHP. I&apos;ve written fairly decent PHP. But why would you want to, right? And now that my credibility is completely shot...First of all, there&apos;s the classic problem that everyone did in the bad old days of the internet (admit it, you did it too--or were still in diapers). Separate business logic from display logicThat&apos;s mixing business and display logic. But this is only one particularly egregious example. Hopefully no one does this anymore. But there are other layers that can get mixed just as easily -- but shouldn&apos;t. Mixing database and business logic. Relying on a single vendor&apos;s implementation.And then there are all these other problems...I&apos;m sure you could spot more in this example.
  6. Refactoring the UISince we&apos;re on the subject, how many of you have written UI tests? Aren&apos;t they fun to automate? ...and maintain? There are a few simple things we can do to improve our UI tests. The easiest is probably to not do them at all. But alas, sometimes there is no other apparent way.Yes, Virginia, there really is UI logic And sometimes, there really is logic in the UI that needs tested. Sometimes we start to wonder if we really need these tests, like a little girl who doesn&apos;t know if she should still believe in Santa Claus.ID tagsOne thing that would be nice is if our HTML elements all had IDs. Or at least the important ones. Add them. Or get the owner of the code to add them. Just do it. Send XPath back to where it came from (the W3C - Have you ever tried to read one of their specs?).Individually Testable ComponentsSomething else that would be nice is if a GUI component could be rendered (and thus tested) in isolation. You can&apos;t test a full page layout this way, but creating individually testable UI components is a step in the right direction. It helps you concentrate on the issue at hand -- and it may help make the backend code cleaner.There&apos;s often more &quot;PHP&quot; than we&apos;d like to admit in our code.Maintainable UI testsThere are ways to make UI tests themselves maintainable. Things like Page Objects. I can talk more about that another time. There is a better way to create UI tests than record / playback.
  7. Pushing testing down the stackIt really is better to push as much testing as possible further down the stack. Eliminate the UI component from your tests wherever possible.Reduce brittle UI testsYou can start by making your tests not dependent on the UI automation framework. If a method has the word &quot;test&quot; in it, make sure it doesn&apos;t also have the word &quot;Selenium&quot;. Describe your tests in terms of functionality, not clicks and wait fors.As you refactor your tests, you can then reduce the brittle UI component in stages.Run tests fasterThere are more advantages to this than immediately apparent. The first is that your tests will run faster. Not having to instantiate a browser for every test is a huge win. Eliminating network latency for an HTTP response, and not having to render (or parse) HTML are other advantages.Isolate the system under testIf you isolate the system under test, it allows you to focus your testing as well. It removes unnecessary variables, and likely makes the code cleaner.Think about code as individual componentsWhen you think about individual components with specific behaviors, and code accordingly, it reduces dependencies, and increases testability.
  8. SOLIDI&apos;m sure you&apos;ve all heard of SOLID principles. Right?Oops, wrong picture. That&apos;s not SOLID.Just to review, SOLID is an acronym for:Single Responsibility PrincipleAn object should do only one thing, and do it well.This can lead to huge wins for testing, and I&apos;ll talk about it more when I talk about Dependency Injection.Open /Closed PrincipleObjects should be open to extension, but closed for modification.While true, when you&apos;re refactoring, sometimes you have to break this egg. You might have to destroy the object&apos;s interface in order to save it.Liskov substitution principleYou should be able to replace an object with one of its subtypes without breaking things. I don&apos;t know who Liskov is. I suspect he was invented just to fit the pneumonic.Interface SegregationMany specific interfaces are better than one general purpose interface.There is a balance here between simplicity and complexity -- but the principle is generally true. Lines of code per method is often a good measure of if you&apos;re doing this right.Dependency Inversion PrincipleNot dependency injection -- which is something almost completely but not entirely unlike...actually they&apos;re somewhat related.The idea is that you should not depend on a concrete implementation.
  9. Refactoring StrategiesIt helps to keep SOLID principles in mind when refactoring. It helps you spot areas that are ripe for refactoring. Let&apos;s talk a little bit about some specific examples of refactoring.Martin Fowler, as I mentioned, wrote a book on refactoring and maintains the website refactoring.com. It has a catalog of refactoring examples, and I&apos;ve tried to distill some common refactoring strategies here.Extract, ConsolidateIf a method is too long, or does more than one thing, you can turn it into multiple methods. Use descriptive names to make your code more readable.Consolidate conditional statements and describe algorithms in business terms.Change, MoveRename a variable or method name, rearrange the order of commands.Encapsulate, SubstituteProvide limited accessors to a collection (i.e. add, remove).Substitute a different, maybe more efficient algorithm.Hide, ExposeMake fields and methods private that are not used externally. Expose fields that may be useful for extension, testing, etc.Pull Up, Push DownMove methods to a parent or child class as appropriate.And many more... See refactoring.com for more examples.
  10. Refactoring Example Summer DiscountHere&apos;s an example demonstrating several refactorings.
  11. Adding Layers of AbstractionIndirectionOne of the pioneers of Computer Science, David Wheeler (the inventor of the subroutine) famously said:&quot;Any problem in computer science can be solved with another layer of indirection&quot;There are several abstraction layers we use every day, without even thinking about them. File I/O is a great example. Indeed &quot;Files&quot; and &quot;Windows&quot; are excellent metaphors that help programmers as well as users to think in abstractions.Abstraction is the generalization of a model or algorithm, apart from the specific implementation. ReusabilityThe goal should be reusability or simplification, not abstraction for its own sake.Composition over InheritanceComposition is when one object contains instances of other objects -- as opposed to inheritance when it extends a base object and implements additional functionality. The reason composition is preferable to inheritance is that it allows the composing object to be (somewhat) in the dark about its children. It leads to cleaner code -- that is easier to test -- and helps simplify interfaces.Here is an example of multiple abstraction layers.
  12. Simplifying InterfacesThere is a constant battle between simplicity and complexity. Coding is primarily the exercise of reducing complexity.Abstraction is one way to combat complexity. But abstraction can also create its own complexity. We need to be careful about this, and only add layers of abstraction when it adds value.A simple interface leads to cleaner code, but too simple and it can be difficult to test.Exposing functionalityUse your judgment about when to expose functionality.
  13. DecouplingOne of the main goals of refactoring...and especially refactoring for testability is to decouple dependencies.Too often one component depends on another component -- when it shouldn&apos;t have too.Presentation and Business LogicDecoupling presentation and business logic shouldn&apos;t need any more justification. MVC frameworks exist to do this.Persistence and Domain ObjectsDecoupling persistence and domain objects is another low hanging fruit. ORM frameworks try to do this. Interface from ImplementationNot just creating separate interface and implementation files but even more, not relying on a specific implementation in your code. For example, using a database abstraction layer (like ODBC) instead of a specific database driver. Or using a standard API (like OpenGL) instead of a vendor library.This allows you to swap out algorithms and other implementation details. It also allows you to create mocks--for the file system or network interface, for example.
  14. Dependency InjectionHow many of you have heard of dependency injection? Can you explain what it does?The main point behind dependency injection is to remove internal dependencies. To enable decoupling. Which makes for more modular code. And makes testing easier as a side benefit.No news is good newsOne simple check for dependencies is do you instantiate composite objects in your classes? And especially, do you have to configure these objects?As my obscure puppet friend always said: &quot;No Gnus is good Gnus...whatsoever!&quot;No framework is necessaryWhen people hear dependency injection, they often think of some complex Aspect Oriented Framework and run for cover. But no fancy framework is necessary to accomplish dependency injection in many cases. Factory patternYou can start by using factories -- whenever you have more than one class that implements an interface, a factory can be used to &quot;inject&quot; the right implementation for the occasion.Constructors, Setters, BuildersDependency injection (even with fancy frameworks) is usually accomplished by passing instantiated (and configured) objects into your constructor, or using an explicit setter method to pass one to your object before using it. Sometimes this is done with bytecode manipulation, but when you have the source... you can do it the easy way. There is a risk of polluting your API with a bunch of object parameters you&apos;d rather not think about. Another thing you can do is create &quot;builders&quot; that know how to assemble your object and sew in all its dependencies, thus hiding the ugly details behind -- that’s right -- another layer of abstraction.Your builder may have reasonable defaults that cover the common cases so only when you need to build an exotic configuration do you ever have to worry about the details.Loose CouplingAgain, the goal of dependency injection is loose coupling.
  15. Dependency Injection ExampleMyFileHandlerMyFileHandler has methods to read, write, and delete a file. With a pretty simple implementation. Of course this isn&apos;t a realistic example, but I hope it illustrates the point.FileUtilFileUtil can open a file and alphabetize it. Pretty neat, huh? I&apos;m looking for venture capital for this revolutionary new app.What can we do to improve this? Extract InterfaceFirst, we can create an interface for MyFileHandler to implement. Not very spectacular, but it allows us to nowInject DependencyInject a dependency with any class that implements the read(), write(), and delete() methods. This means we can swap out one implementation for another ...with say a factory... and uses Unix style sockets or pipes in the same way as files.
  16. Use mocksOr help us test by allowing us to create mocks, and inject them into the code. Now, we&apos;re not dependent on the file system. Or it could be the database, or a web service, or some third party framework -- or something that isn&apos;t completely developed yet.But let&apos;s see if we can do even better.Single ResponsibilityThat&apos;s more like it. We don&apos;t want to test the file system. And we don&apos;t want to create mocks either. We only want to test our alphabetizer. That it is used for alphabetizing files is a detail we don&apos;t care about. An object should do one thing and do it well.I suppose you want to see how it does it... 
  17. Refactored TextUtil ImplementationIn the fine tradition of computer science, as popularized by Mr. Donald Knuth, I&apos;ll leave it as an exercise for the reader.But you get the idea.
  18. Reduce dependenciesWe want to reduce dependencies. It&apos;s not realistic that we&apos;ll never have to use mocks. But you should keep an eye out for where you can eliminate them. Because when you eliminate a dependency, an angel gets his wings...Or something like that. 
  19. Coding in Business TermsThis is one last area I want to touch on in refactoring. That&apos;s trying to write your code in business terms.Common domain languageNot only does it give you a common domain language to talk about the problem, but it makes your tests more readable, and that helps them more clearly reflect the business needs.Try writing tests first that express your API as close as possible to the business language. And then write your interface to match the tests.Acceptance CriteriaThis helps you to write tests that more closely resemble the acceptance criteria. Acceptance tests are different from functional tests in that they&apos;re not testing the implementation. They are testing the business requirements. Often there is no easy way to do this. That&apos;s why we frequently end up with UI tests that are brittle, unmaintainable, etc. If we can push testing down the stack so that we&apos;re not relying on the UI to test business features, we can make our tests better -- and our code more testable.BDD frameworksOne thing worth exploring are Behavior Driven Development Frameworks like RSpec, SpecFlow, JBehave, Cucumber. They attempt to use a natural language syntax to express tests with the aim of making them more readable.Given {some state}When {a condition}It should {do something}I&apos;d encourage you to look at them if you haven&apos;t yet, but you don&apos;t really need a framework to help you write better tests.  Often, what you really need is just a little refactoring.
  20. That&apos;s all. Any Questions?Thanks.Aaron Evans &lt;aevans@ancestry.com&gt;