I got introduced to the Golden Master testing technique 2 years ago when in Grenoble, France we had the chance to have J.B. Rainsberger come here for the worlds first Legacy Code Retreat. It was very an efficient technique to get total line coverage on that ugly piece of code of some 200-300 lines. The variation that he used was to put log statements all around the code, run it with many variations of input arguments, save the log files and write a test that rerun the application with the exact same arguments - if you only refactor (no behaviour changes i.e. transformations) then tests must be green.
Summary: Golden master is a technique where you bombard your system with many many many variations in input arguments, capture all the outputs to some file(s) which is committed. Every time you run the tests they must produce the same file(s). Chris Melinn describes the technique in detail. Sandro Mancuso has an example
I never used it later - except for dojos on legacy code. The reason is that it is so easy to on code that takes simple arguments and return all the results or at least has some easily verifiable side effects - like writing to a file. But production legacy code has soooooo many really horrible dependencies, intricate side effects and very often results depend very much on the state of the machine, application, database etc. Another reason I didn't use the Golden Master technique is that those tests are unmaintainable - very fragile to any modification of the behaviour.
But then a few months ago I had a few insights that suddenly made it very interesting. Here's an overview of them.
- To deal with behaviour depending on state - I can transform them into direct input arguments by writing a wrapper function of the SUT (System Under Test) that configures different states
- To deal with results doing side effects - I can transform them into return arguments by reading the state after exercising the SUT. So the system now behaves like a pure function from a testing perspective.
- Those tests are ephemeral - I throw them away one I'm done with the refactoring. So they don't need to be explicit, clear, robust and all other things that takes time. They way I mock the system can also be quick and dirty.
- Ordinary unit tests (one test class per production class) on awful code are often too fine grained and give only moderate security (failing to capture some intricate side effects) and while they allow refactoring below the interface they are testing, they hinder refactoring of that, often ill designed, interface - because they are a second client to it.
- Write non-regression tests
- Refactor, to make the system Open-Closed (with respect to the feature you want to add)
- Write ordinary unit tests for the refactored code
- Test-drive the new functionality in isolation from current production code
- Plug the new functionality (this is usually a one-line code or config change)
To me this is a wonderful technique for working with legacy code! I and a few friends have used it in a professional context with excellent results, Rémy Sanlaville wrote about string serialization and Matthieu Cans about coverage. I'm exploring this subject in detail and will post about it as I learn more.
Tooling: ApprovalTests and a powerful mocking library like PowerMock. Code coverage is also essential.
Btw, while we do throw away the tests, the majority of the work is still useful - the wrapper function(s) can be kept as an example and whatever we did to make the system testable has decoupled the system.
I like the way you describe the 'Classical TDD method on legacy'.
RépondreSupprimerAt work, we have built a full kata plan to practice refactoring techniques on legacy (https://philippe.bourgau.net/a-coding-dojo-exercises-plan-towards-refactoring-legacy-code/). Indeed, we use the different techniques you suggest (Golden Master, Anti corruption layer ...). I found out that the more techniques we have our at our disposal, the easier it is to combine them and deal with the code at hand. I hope we'll be able to share the full kata plan soon
Thanks again
Great blog, thanks for posting this.
RépondreSupprimer