- Changing Software
- Four reasons to change software
- adding a feature
- fixing a bug
- improving design
- optimizing resource usage
- Adding features and fixing bugs
- some argue whether something is a bug fix vs feature
- more important distinction is behavior i.e. adding or changing
- Improving Design (Refactoring)
- keep behavior intact
- programmers avoid because easy to lose behavior or add bad behavior
- should have tests to make sure existing behavior doesn’t change
- make more maintainable and no functional changes
- Optimization
- no functional changes, but use less time and/or memory
- Putting it all together
- refactoring and optimization keep functionality invariant
- in terms of existing functionality, held constant for features, refactoring and optimization
- in all cases, generally want to preserve much more than we’re changing
- not only making sure things we change are correct, but also that its preserving rest of behavior
- often don’t know how much behavior at risk
- Risky Change
- can have considerable risk
- what changes?
- how will we know we’ve done correctly?
- how will we know we haven’t broken anything
- avoiding changes will catch up with you
- in poorly structured code, won’t feel good about change
- people get rusty if don’t make changes and live in fear
- Working with Feedback
- Intro
- two ways change can be made 1) edit and pray 2) cover and modify
- without automated tests, feedback loop can be large
- extensive unit tests allow quicker and more confident refactoring
- What is unit testing?
- concerned with most atomic behavioral units of a system
- in oop, usually units are classes
- large tests make error localization difficult and can take long
- also may be hard to cover behavior of newly added code
- unit tests should be fast and help localize problems
- 1/10th a second for a test is slow
- unit tests shouldn’t 1) talk to db, 2) network calls, 3) filesystem 4) special things to set up environment
- high level testing
- useful to pin down behavior for set of classes
- test coverings
- safer to have tests around changes
- more likely to catch mistakes
- important to be able to instantiate class
- dependency is one of the most critical problems in software
- much legacy code work involves breaking dependencies so that changes can be easier
- some cases not practical or possible to test without changing code
- e.g. InvoiceUpdateResponder(DBConnection, InvoiceUpdateServlet)
- use interface for db and pass in collection of invoice Ids vs servlet
- do these initial refactorings very conservatively
- the legacy code change algorithm
- want to make functional changes that deliver value and more test coverage
- identify change points: sensitive to your architecture if don’t know design enough 1) review ch16 or ch 17
- Find test points: could be hard to know where to add
- break dependencies: manifest as difficulty instantiating objects in test harness and difficulty running methods
- write tests
- make changes and refactor
- advocate TDD to add features in legacy code
- techniques in book not for clean, ideal, pattern-enriched design
- “baby-steps” make a bit more maintainable
- simple things like breaking down a large class can make a significant difference
- Sensing and separation
- Intro
- sensing: see effect of our calls
- separation: can’t run class separately
- hard to test if class is closed box
- logic we want to test may have nothing to do with everything required to instantiate class
- Faking collaborators
- fake objects can be used to put code in place of something else to sense what is happening
- e.g. let’s say we have Sale class with scan(barcode: String)
- if display code buried, can be hard to check that part
- could break out display class with showLine(line: String)
- sale class can now hold that class or FakeDisplay
- sale can accept display interface w/ showLine method
- with fake display, can see what would have been called
- helps localize error and understand a small piece
- two sides of a fake object
- can add additional methods to help testing
- e.g. “showLine” is part of interface and “getLastLine” can help with testing
- fakes distilled
- often implemented as simple class
- mock objects
- more advanced objects that can perform assertions internally
- we can tell them what calls to expect
- not available in all languages but simple fake objects suffice in most situations
- The Seam Model
- Intro
- code seems poorly suited to testing
- usually don’t see good coverage unless write tests as you develop
- try to think of code as a huge sheet of text
- Seams
- how to change method without changing the call?
- seam is place where you can alter behavior of program without editing in that place
- e.g. subclass and override
- many different kinds
- replacing behavior at seams allow selectively excluding dependencies
- can also run other code where those dependencies were to sense conditions
- Preprocessing seams
- some languages like C, C++ have preprocessing step prior to compilation
- not good to use a lot in production code, but can be useful in language because provides seams
- e.g. provide another definition for a routine
- source should be same in production and test
- every seam has an enabling point
- Link seams
- compilation usually not last step
- linkers combine those representations
- in language e.g. Java, compiler does linking
- regardless of scheme, can usually exploit to substitute
- in Java, when you import, java uses class path env to determine where java system looks to find those classes
- you can create classes with same name and put in different directory and alter class path
- can be handy in testing and breaking dependencies, don’t use in prod
- enabling point is the class path
- in some language, linking is static
- easiest way to use link seam is to create separate library for any classes or functions you want to replace
- alter build scripts to link those
- useful if code littered with 3rd party library calls
- can “sense” by fake and record calls, but can become complicated
- Object seams
- method that is called can belong to different objects
- not all method class are seams e.g. if object is instantiated prior to call
- if object is passed into arg, that is enabling point
- may have to subclass and override
- let’s you get tests in place safely
- usually object seam best for OO languages
- tests that depend on preprocessing/linking may be hard to maintain
- when used to seeing seams can use different methods to structure new code
- Tools
- Intro
- need editor and build system
- Automated refactoring tools
- refactor: easier to understand, cheaper to modify, don’t change behavior
- careful with automated tools
- write own tests and check it doesn’t introduce subtle bugs
- mock objects
- several mock libraries freely available
- unit-testing harnesses
- xUnit very effective
- lets programmer test in their language
- all tests run in isolation
- tests grouped in suites that can be run and re-run
- junit
- subclass “TestCase”
- testXXX, where XXX is method name
- uses reflection to find all test methods
- instantiates class with method for each
- general testing harness
- Framework for Integrated Tests (FIT): write documents about your system.
- Embed tables that describe inputs and outputs
- Fitnesse: FIT hosted on a wiki
- I don’t have much time and have to change it
- Intro
- additional work initially, but saves time and frustration in long-run
- initial steps hard. ask group how to get started
- try to instantiate class in test harness
- Sprout method
- create new method
- don’t intertwine changes into existing code
- can move to static method. treat as staging area
- Disadvantage: kind of giving up on testing existing class
- Advantage: clear separation
- Sprout class
- avoid invasive refactorings
- adding new responsibilities not related to main functionality
- can’t get class in test harness
- disadvantage: adds conceptual complexity
- wrap method
- rename method
- create new method with same name as old and call
- does not increase size of existing methods
- new functionality explicitly independent
- may lead to poor names
- wrap class
- create new class that can take in class and be used in its place
- decorator pattern
- wraps class and has same interface
- good to use sparingly
- overview
- sprout method when existing method communicates a clear algorithm
- wrap when new feature is as important as work that was there before
- wrap class A) behavior I want to add is completely independent and don’t want to pollute class B) class large and don’t want to make worse
- when you start seeing new classes or methods that are well-tested, leads you to want to fix other existing trash
- It takes forever to make a change
- understanding
- in bad system, takes a long time to find where changes must happen and change is difficult
- if you don’t understand
- ch 16 and ch 17
- lag time
- if you have to wait for a while for feedback, your mind engages in planning process each time
- quick feedback allows trying approaches quickly and concentration is more intense
- should be able to compile every class and module in your system separately from others
- braking dependencies
- first step, try to instantiate class in test harness
- ch 9 if difficult
- ch 10 if using individual method difficult
- build dependencies
- extract interfaces for your class
- move code to new project or library
- overall cost to build system goes up but avg time for a build can decrease
- extract implementer e.g. change ConsultantSchedulerDB to an interface and add ConsultantSchedulerDBImpl
- Dependency Inversion: interface dependency is more minor and unobtrusive. usually requires less changes to code.
- How do I add a feature
- Intro
- In general, better to get code under test first
- TDD
- write failing test, get to compile, make it pass, remove duplication, repeat
- removing duplication important or making maintenance burden
- either writing code or refactoring, never doing both at once
- Programming by difference
- In OO, can use inheritance to introduce features without modifying class directly then figure out how to integrate
- doesn’t mean we keep the inheritance
- e.g. MessageForwarding class extracts “from” address from a message and passes along to mailing list
- want to anonymize in some cases
- don’t need to update MessageForwarder, but make AnonymousMessageForwarder and override “getFromAddress”
- new class for simple method, but test passes quickly, which can be used to preserve new behavior
- downside, if we keep doing this design can degrade
- e.g. want to be able to send bcc, can subclass again but what if you want to do that and anonymous from?
- problem with inheritance is can only have one feature at a time
- option 1) stop and refactor before adding second functionality
- option 2) for anonymous forwarding, could have made this a config option and make class accept collection of properties
- add functionality to base class and comment out override method in AnonymousMessageForwarder then remove it when not doing anything
- getFrom method messy, but can extract method to clean up
- not SRP, but if simple maybe ok
- if many properties and code in class gets littered with conditional statements start using class rather than property collection
- can use MailingConfiguration class and give it “getFromAddress”
- can move more methods e.g. “buildRecipientList”
- maybe “MailingList” more appropriate since config usually passive
- “Rename Class” powerful refactoring changes way people see code
- “Programming by Difference” useful, lets make change quickly and use tests to move to a clearer design
- some gotchas
- Liskov substitution principle: objects of subclasses should be substitutable for objects of their superclasses
- 1) avoid overriding concrete methods
- if you do, see if you can call the method you are overriding
- overriding concrete can be changing the meaning of code
- someone may see parent class and not know its pointing to subclass and get unexpected results
- could make MessageForwarder abstract so no overriding of concrete methods
- “normalized hierarchy”
- some concrete override ok as long as not making LSP violations
- good to think about normalized forms from time to time
- I can’t get this class into a test harness
- case of the irritating parameter
- first just try to initialize without args, compiler will tell you what you need
- if connection is passed in, can extract interface and pass in fake
- in test code, ok breaking encapsulation by making variables public to test, but should still be clean and easy to understand
- ok to pass in null in test only, can simplify test for classes not required to test certain functionality
- only do if you know run-time will detect null pointer errors
- try starting out by passing in nulls and see if it complains
- consider using null objects in prod
- can also subclass and override if trouble is coming from one method make sure not altering behavior
- the case of the hidden dependency
- constructor uses some resource we can’t nicely access in test harness
- e.g. calling new in constructor
- “Parameterize Constructor” to externalize dependency
- allows you to pass in fake
- to not break client code, extract body of new constructor with passed in dependency to new “initialize” method and call that from constructor and use initialize in test
- preserves signatures so safe change
- other techniques for dependencies hidden in constructors
- extract and override getter
- extract and override factory method
- supercede instance variable
- I like to use parameterize constructor as often as I can
- easy if created object doesn’t have constructor dependencies itself
- the case of the construction blob
- sometimes parameterize constructor may result in long param list
- if a lot of objects are created certain object embedded in a blob of object creation
- “supercede instance variable” you write setter to swap instance after constructing object
- extract interface and pass in fake
- the case of the irritating global dependency
- singleton design pattern
- problem is opacity hard to reason about
- can relax singleton property by adding new static method that allows you to replace static instance
- some reasons people want to enforce singletons
- modeling real world and there is only one e.g. databases
- if two created serious problems
- multiple can use too much resources
- primary reason people use is because don’t want to pass variables around
- if for latter reason, may not need to enforce singleton property
- can also subclass and override methods
- can also extract interface
- often times global dependencies aren’t used globally
- refactoring and separating out responsibilities can help dependencies become localized
- the case of the horrible include dependencies
- some c++ specific
- include code from header files
- the case of the onion parameter
- sometimes need to create a chain of objects to create an object
- objects inside of objects like an onion
- must at least be one class that doesn’t require another object to create
- If the passed in parameter is not actually needed, pass in null
- can also extract interface or implementation
- the case of the aliased parameter
- sometimes extract interface/implementation not practical
- e.g. a parameter may have issues due to inheritance hierarchy
- might have to create chain of interfaces that correspond 1 to 1 with classes which is a lot of work
- If issue is just one method, subclass and override method
- I can’t run this method in a test harness
- Intro
- reasons why method may be hard to test
- private
- hard to construct parameters
- side effects
- need to sense through objects
- the case of the hidden method
- if its private you could try to test through the public method
- if we need to test it, should make it public
- if that bothers us usually 1) method is just utility or 2) method could adversely affect results
- usually making public in case 1 is not too severe
- second case, can move method to new class and create internal instance of it
- good design is testable and design that isn’t testable is bad
- can be hard to test if class has too many responsibilities
- if can’t afford to separate responsibilities, can make private to protected and subclass and get access to that method
- violates encapsulation, but fair trade for tests
- the case of the “helpful” language feature
- might not be able to subclass if sealed or final
- “Adapt parameter” subclass non sealed superclass and pass that in as arg
- can also extract interface and write wrapper can be annoying but price of security
- the case of undetectable side-effects
- separate work that is independent of side effecting methods
- “Extract method”
- create instance variable and only use them in methods
- easier to test
- potentially break out into different classes
- I need to make a change, what methods should I test?
- Intro
- write tests for each method that we change
- think about change, see what it will affect and what affected things will affect
- reasoning about effects
- every functional change has associated chain of effects
- see which methods it affects
- understand what things can be affected and what won’t be
- reasoning forward
- if members are public, need to be aware if they are being used
- see which clients are using as well as sub and superclasses
- effect propagation
- return values that are used by a caller
- modifications to objects passed in
- changes to global/static data
- heuristics to use for looking for effects
- identify method that will change
- if there is return value look at its callers
- see if method modifies any values
- make sure to look for superclass and subclasses
- look at parameters and see if they or any objects they return are used by code you want to change
- look for global variables and static data that is modified
- tools for reasoning
- most important tool is knowledge of programming language
- learning from effect analysis
- best code doesn’t have many gotchas
- in bad code, people don’t know what the rules are
- programming gets easier as we narrow effects in a program
- simplify effect sketches
- removing duplication can let you have smaller set of endpoints
- sometimes techniques break encapsulation, but purpose of encapsulation is to help us reason about code
- having explanatory tests can make it easier to reason about code
- in legacy code, a change in one place can have effects in many
- know where to test for effects
- I need to make many changes in one area. Do I have to break dependencies for all classes involved?
- Intro
- sometimes pays to find a place to write tests for several changes at once
- higher level tests can be good first step in getting unit tests in place
- first need to figure out where to write them
- Interception points
- place where you can detect the effects of a particular change
- requires effect reasoning / tracing
- good to pick interception point close to change
- higher level interception points
- public methods best place
- sometimes ok to test group of classes
- pinch point is where multiple changes can be detected
- sometimes impossible to find
- judging design with pinch points
- is a natural encapsulation boundary
- could be indicative of a hidden class
- pinch point traps
- can become mini-integration tests
- they can help write unit tests and later delete
- I need to make a change, but I don’t know what tests to write
- Intro
- automated tests help your team write correct code consistently
- tests that specify become tests that preserve
- can’t fix all bugs in legacy code
- characterization tests
- tests for the actual behavior of system not necessarily what its “supposed” to do
- write code and make fail and see what correct behavior is
- putting in mechanism to catch bugs later
- acts as documentation. write tests until satisfied we understand it
- before using a method in legacy code, check to see if there are tests for them
- characterizing classes
- try to figure out what it does at high level
- look for tangled pieces of logic and consider using sensing variable
- as you discover responsibilities make a list of what can go wrong and formulate tests to trigger
- think about inputs you supply in tests and extreme values
- should any conditions be true at all times?
- as you discover bugs, fix if haven’t been deployed and consider downstream users if it has
- targeted testing
- consider different potential paths of execution and test
- heuristics for writing
- write tests for area you will make your changes
- look at specific things you are going to change and attempt to write those
- if extracting or moving functionality, verify you are exercising the code and it is connected properly
- Dependencies on libraries are killing me
- can become overly reliant
- what if they raise royalties and code is tightly intertwined with your code?
- sometimes best thing is to write a thin wrapper
- My application is all API calls
- first, identify computational core of code
- separate responsibilities
- separate core logic from API
- skin and wrap API
- API is small
- want complete separation from dependency
- don’t have tests and can’t write through API
- responsibility-based extraction
- API more complex
- code might depend on higher level interactions that we might not be able to get under test
- I don’t understand the code well-enough to change it
- notes / sketching
- listing markup
- print code
- use markers and different colors
- line up blocks
- circle code you’d like to extract method
- put marks on variables that effect others to see how changes propagate
- scratch refactoring
- just do it to understand code better
- throw away
- risk: make gross mistake and think system is doing something it isn’t
- may tie us to that particular way of having done it
- delete unused code
- version control to get old things back
- My application has no structure
- Intro
- applications can degrade
- system is so complex takes long time to get big picture
- so complex there is no big picture
- team in reactive mode lose sight of big picture
- bad if architect and programmers have different view of system
- important everyone on team knows architecture
- telling the story of a system
- have two people
- one explains system in terms anyone can understand
- start with most important aspect and use simple stories
- serves as indication of what an ideal system could look like
- do this often
- when adding new features and thinking about where to add, think about what makes the story less of a lie and still accurate
- naked CRC
- explain architecture with blank index cards
- each card represents and instance
- stack for collections
- use movements and position to explain interactions
- conversation scrutiny
- listen to conversations about design
- are concepts same as concepts in code?
- e.g. instead of using an array use a LockingPolicy for tracking counts
- mistake if people think designing part is over
- if no one feels comfortable introducing new abstractions code will get worse
- My test code is in the way
- Intro
- end up with a lot of test code
- good to establish conventions else swamped
- class naming convention
- assumes test and prod code in same directories
- usually test class for each class can prefix or suffix with “Test”
- for fake objects I like to prefix “Fake”
- for testing subclasses, prefix “Testing”
- not dogmatic, consider how things end up getting grouped
- test location
- may impact code size and deployment
- may not matter if running on own servers, but may if its commercial product running on someone else’s
- some languages spanning directories doesn’t matter but in many languages and environments it does
- moving back and forth can be taxing
- could have scripts that remove tests in deployment can work if good naming convention
- if you separate test of prod code, make sure its for a good reason
- My project is not object orientated. how do i make safe changes
- Intro
- can be hard to introduce tests to procedural code
- hard to break dependencies
- a hard case
- e.g. function writes own notification to third party system
- one way is writing library with fake and linking to it
- might be ok, if behavior is peripheral to main logic of system
- if you want specific sensing or return values, might be tedious, because can only link one function for each executable
- can use file inclusion to include test definition and tests
- file inclusion and macro replacement can help in thorniest code
- adding new behavior
- TDD can help alter design in good ways
- extracting core logic from execution
- if language supports function pointers can pass a struct of pointers to function and pass in different versions in prod and test
- taking advantage of object orientation
- in OO, can take advantage of object seams
- not all software can be easily migrated to OO
- first step, usually encapsulate global references
- help get out of bad dependency situation
- can find function declarations and wrap in a class
- declare global instance
- wrap the function using it in another class that takes global instance class as parameter and be able to pass in global version and fake in tests
- it’s all object orientated
- procedural programs are object-orientated in that its like having one object
- OO allows you to break up program for testing
- besides breaking dependencies, move to better design by grouping related functions in classes and breaking apart tangled responsibilities
- This class is too big and I don’t want it to get any bigger
- Intro
- big classes confusing
- can result in clashes if people working concurrently
- pain to test
- encapsulation good, nut if it hides too much inside rots
- first, use sprout method/class so things don’t get worse
- key remedy is refactoring
- SRP: every class should have a single responsibility
- “responsibility” is vague focus on main purpose
- seeing responsibilities
- can list all methods and think about purpose
- group them into lists
- heuristic #1: group methods
- look for similar method names
- don’t have to move to new class right away
- wait until you have to modify and decide later whether you should extract a class
- heuristic #2: look at hidden methods
- if many private/protected, can be indicative that another class dying to get out
- if you have urge to test private method, chances are it should be public and if that bothers you, probably should be new class
- heuristic #3: Look for decisions that can change
- good idea to extract methods that reflect what you need a at a high level
- heuristic #4: Look for internal relationships
- look for relationships between instance variables and methods
- usually some lumping / clustering
- can use a feature sketch
- start with all variables in circles
- then add a method draw arrow to variable or method it uses
- clusters could potentially be its own class
- figure out if cluster has good distinct responsibility
- lines connecting clusters are pinch points
- heuristic #5: look for the primary responsibility
- try to describe class responsibility in single sentence
- SRP can be violated at interface and implementation level
- interface violation has many methods and seems to be responsible for a lot of things
- care more about implementation violation
- for interface level, consider if client should use a new class directly
- interface segregation principle: clients usually use different groupings of methods. Can create interface for each grouping that class implements and each client can see large class through interface which helps hide information
- can be hard change if clients have to change code
- heuristic #6: scratch refactoring if all else fails
- heuristic #7: focus on the current work
- if you are providing a different way of doing something, might help identify a a responsibility that you should extract
- other techniques
- way to get better is read more
- read books about design patterns
- read other people’s code, open source projects
- see how people do and name things
- moving forward / strategy
- rare to find time for big rewrite and risky
- better approach is identify responsibilities make sure everyone on team understands them and break down on an as-needed basis
- spread out risk of changes
- tactics
- usually start at implementation level
- introducing SRP at interface level requires more work
- important that you can see tests around the methods
- may not be able to test individual methods, nut can test bigger scope methods to get coverage
- breaking out a class
- identify responsibility
- find associated instance variables and move somewhere
- for methods, extract bodies into new methods and prefix “MOVING”
- for parts of methods, extract and similar prefix
- do text search and check that no variables you’re moving ar used outside
- move to new class and update method calls with instance of new class
- remove “MOVING”
- warning
- can have subtle bugs related to inheritance
- especially with overridden variables / methods
- don’t move original methods, create new methods by extracting bodies of old ones
- after extract class
- danger to be overambitious
- sometimes best thing is just formulate a view of how it can be refactored
- can help lead to future better direction
- I’m changing the same code all over the place
- Intro
- can remove duplication in small chunks
- first thing we need are a set of tests
- first steps
- first, step back and get full scope of duplication
- helps you think about what kind of class you’ll end up with
- removing small pieces of duplication can make it easier to see larger areas later
- can always refactor into other groupings later
- when two methods look similar, extract the differences into other methods
- be consistent about naming convention using abbreviations can be confusing and lead to inconsistency
- provides less knobs for modifying
- localized behavior easier to add/replace to
- removing duplication helps design emerge
- open/closed: don’t have to change much code to add new features
- I need to change a monster method and I can’t write tests for it
- bulleted methods
- nearly no indentation
- might be tricky if temporary variables declared in one section and used in the rest
- snarled
- dominated by single large indented section
- usually issue is mix of the two
- tacking monsters with automated refactoring support
- when doing automated, use tool exclusively
- try to communicate at higher level
- manual refactoring
- tests are strongest tool
- may forget to pass variable
- may override method in base class
- issue with types
- introducing sensing variables
- help sense conditions
- remove later
- check certain paths have been executed
- keep them in place during refactorings
- extract what you know
- extract small pieces at first and add tests to cover them
- 2-3 (5 at most) lines
- try to have small coupling count (number of vars going in and coming out), harder to make mistakes
- 0 coupling count safest, usually commands to do something to state
- small chunks builds up over time
- often start to extract 0-count methods
- gleaning dependencies
- sometimes code that is secondary to method’s main purpose
- not all behavior is equal in an application
- maybe just write test covering functionality you care about
- breaking out a method object
- create class whose only responsibility is to do the work of your monster method
- skeletonize methods
- can extract condition and body separately
- e.g. if (orderNeedsRecalculation) recalculateOrder(order)
- fixed sequences
- extract condition and body together
- recalculateOrder(order)
- strategy
- can be conflicting
- lean towards skeletonize when feel control structure will need to be refactored (snarled)
- sequence when identifying sequence will make code cleaner (bulleted)
- extract to the current class first
- sometimes may identify behavior that should live in another class
- don’t move initially
- refactor with potentially awkward name and can always move later
- less error prone
- extract small pieces
- safer and will be more likely to succeed
- be prepared to redo extractions
- there are many ways to break down a monster
- can often times discover better ways
- not wasted effort because that leads to insight for better design
- How do I know that I’m not breaking anything?
- hyperaware editing
- thinking about how each key stroke affects everything else
- good to get constant feedback
- single-goal editing
- focus on one thing at a time
- preserve signatures
- be particularly conservative for refactorings done without tests
- don’t do too many things at once
- lean on compiler
- can do type checking and variable usage
- may not be practical if compile takes too long
- inheritance may cause issues
- good to know about limitations
- pair programming
- We feel overwhelmed. It isn’t going to get any better
- figure out what’s in it for you
- connect to what’s fun about programming
- not much can replace a workplace with people you respect and know how to have fun
- connect with larger community
- view legacy code as a challenge
- if low morale, consider getting ugliest, most obnoxious set of classes under test
- Dependency-breaking techniques
- adapt a parameter
- extract interface often best choice, but some cases may be hard
- can wrap parameter in new class/interface
- create prod and test implementation
- try to create narrow interfaces targeted towards what you need
- breaking out method object
- create class to house method code
- in constructor pass some args as method and reference to original class if requires its methods or instance variables
- create execution method e.g. “run”
- instantiate and use it to delegate
- consider extract interface to break dependency on original class
- definition completion
- in test file (where method definition in source not header) and define missing methods
- not recommended, but if you use, try to replace soon
- encapsulate global references
- identify globals
- create a class and copy globals into them
- comment out initial globals
- initialize class and replace previous globals with instance globals
- expose static method
- preserve signatures whenever possible
- statics aren’t really part of class
- extract method to static
- extract and override call
- use often
- use unless many different calls against same global
- identify call, create new method and give it signature
- override method in testing subclass
- extract and override factory method
- identify an object in constructor
- extract all work involved in creation into factory method
- create testing subclass
- extract and override getter
- introduce a getter for the instance variable that you want to replace with a fake object
- don’t use often
- better when many problematic methods on same object
- extract implementer
- make copy of class declaration and give another name e.g. prefix “Production”
- turn source class into an interface
- make “Production” class implement interface
- make sure all method signatures in interface are implemented
- find places where instances of source class were created and replace with new prod class
- can get tricky with inheritance hierarchies
- extract interface
- don’t need to extract all public methods
- create a new interface, don’t add any methods
- make class you are extracting implement interface
- change place where you want to use object with interface
- compile and introduce method declaration on interface that compiler throws errors for
- link substitution
- create a dummy library
- could create list and track calls and make sure if called in right order
- good in C functions that are mostly data sinks that don’t care about return type e.g. graphic libraries
- in Java, create class with same name and methods and change your class path
- parameterize constructor
- externalize object creation in constructor
- add parameter for object in constructor and set it
- remove body in constructor and call new method passing in object instantiated
- allows clients to not change, while in test can pass in arbitrary new object
- risk: other code now can access that object and can increase dependency, generally small risk
- I use this technique a lot
- parameterize method
- pass in object instead of instantiating it directly
- can add new object dependency to interface
- primitize parameter
- develop free function that does the work using intermediate data structure
- make method in class delegate to this
- leaves code in bad state
- consider adding new code to your class or sprout class
- only use if you’re confident you will take time to bring the class under test later
- pull up feature
- if methods you want to test don’t use dependency, create superclass and pull them up
- requires testing subclass (abstract superclass)
- not ideal from design point of view
- might be risky to delegate, can do later
- push down dependency
- identify problem dependency
- create new subclass with name that communicates specific environment of those dependencies
- copy instance variables and methods that contain those into new subclass
- make methods protected and abstract in original class
- make original class abstract
- create testing subclass
- replace function with function pointer
- find functions you want to replace
- create function pointers with same name
- rename original function
- initialize pointers to addresses of old functions
- replace global reference with getter
- identify global reference and use a getter instead
- subclass in test
- subclass and override method
- identify dependencies you want to separate or sense
- make each method overridable
- create subclass and override methods
- supercede instance variable
- create method “supercedeXX” that sets new variables
- in C++, can’t override call to virtual function in constructor
- extract and override factory method usually better choice for language that allow virtual calls in constructor
- template redefinition
- identify feature you want to replace in the class
- turn class into template, parameterize it by the variables that you need to replace and copying method bodies into the header
- give the template another name e.g. suffix with “Impl”
- Add a typedef statement after template definition and instantiate the template on new types that will replace the ones you need to replace to test
- text redefinition
- in interpreted languages methods can be redefined on the fly
- one downside, class may be permanently modified for other tests
- can do in C and C++ using preprocessor
Like this:
Like Loading...