Strategies to test legacy code - PART 1
Often developers have to deal with code base without test, which might lead to disconfort in maintaining the code base. In one hand there is the business that needs a new feature or a bug fix ready for the users to use, and in the other hand there is the developer without a guide to make sure that the change in the code doesn’t make any side effect.
To refactor is the ability to change the code, without affecting it’s behavior . If the code is changed and it’s beahvior change, it is not refactoring, it is changing code without confidence.
The following content aims to define strategies to refactor existing code without tests. Taking into account two basic rules, also named in :
- Don’t change existing code without a test first ( states: “Never write a new functionality without a failing test”)
- Boy Scout Rule - improving the code you need to touch 
The acceptance testing is, compared to the unit test slower to run and depends on the integration of many components, it is the test that simulates a user interacting with the application.
Often the acceptance testing (UI Tests) is used with a web driver, to open a browser and start to reproduce the events that an user would have done . The pyramid of test  though, gave it a label of being slow and that depends on integration to be possible to test.
There are at least two reasons for acceptance test be considered slow, the first is the need to use a web drive, which creates a real browser environment to execute the test. The second is related to the first, wich requires for each test to reset the browser instance. The recommended approach is to have a few acceptance tests as possible.
The unit test is the test that tests the smallest part in the code, often referred as a function. The unit tests is the test written by the developer along the production code, following the pyramid test , the unit test is the type of test that should have the higher amount in the suite.
Unit test are fast to run and gives fast feedback to the developer while writing the program. The opposite of acceptance testing.
Defining a strategy
The first strategy to approach testing in legacy systems is to start by writing acceptance testing via web driver.
The flow is to get confidence in as many scenarios as possible, in a way that is possible to refactor the code with confidence. In PHP for instance, is possible to use codeception, would be recommended to follow the TDD metodology even with acceptance test, in short the flow is as follows:
- Write acceptance test
- Refactor the code to be possible to add unit test
- Execute the acceptance test
The pro is to have a few testes which gives the developer confidence in refactoring in as many scenarios as possible, on the other hand acceptance test is slow to execute. Even a small change would take a few minutes to execute the suite.
Be mindful that this trategy is a first approach, trying to follow the “make the change easy, then make the easy change” by Kent Beck. Which Martin Fowler also used as a metaphor on an example of preparatory refactoring 
Iterating over the strategy
Having acceptance test in place is a start to start improving the test strategy. As mentioned before acceptance test is slow to execute, in addition it should be a few as possible in the test suite.
The acceptance test in the scenario gives the developer more conficen to start refactoring, where the goal to achive is to increase the unit test based on the acceptance test suite.
For each acceptance test is recommended to start to substitute the acceptance test with the unit test doing refactoring, the recommended approach would be:
- Identify which classes are touched by the acceptance test
- Pick one class and start to refactoring with the unit test
- If the class does’t have a unit test, create a class for it
- If the class already have a unit test class, start to write the test
- Repeat step 2 until all acceptance tests is converted into unit test
The last part is to remove specific cases in the acceptance testing suite, and keep only the general ones, which covers the most basic flow.
It is possible to keep with all the acceptance test suite in place as well, just the execution approach would change from a developer point of view.
Once the unit test suite is good enough to have confidence, the developer at each change would execute the suite to have feedback on the change. Whereas in the acceptance suite would be executed only once the refactor is done.
- M. Fowler, Improving the Design of Existing Code(2nd Edition). Addison-Wesley Professional, 2018.
- S. Mancuso, “Working with legacy code,” 2011 [Online]. Available at: https://www.codurance.com/publications/2011/07/03/working-with-legacy-code. [Accessed: 22-Mar-2021]
- S. Freeman and N. Pryce, Growing object-oriented software, guided by tests. Pearson Education, 2009.
- R. C. Martin, Clean code: a handbook of agile software craftsmanship. Pearson Education, 2009.
- seleniumHQ, “Selenium WebDriver,” 2018 [Online]. Available at: https://www.seleniumhq.org/projects/webdriver. [Accessed: 06-Feb-2019]
- H. Vocke, “The Practical Test Pyramid,” 2018 [Online]. Available at: https://martinfowler.com/articles/practical-test-pyramid.html. [Accessed: 06-Feb-2019]
- M. Cohn, SUCCEEDING WITH AGILE (1st edition). Addison-Wesley Professional, 2009.
- M. Fowler, “An example of preparatory refactoring,” 2021 [Online]. Available at: https://martinfowler.com/articles/preparatory-refactoring-example.html. [Accessed: 05-Jan-2015]
Edit 27, 2019 - PHPMad talk
Table of contents
- Acceptance testing
- Unit test
- Defining a strategy
- Iterating over the strategy
- Edit 27, 2019 - PHPMad talk