Make your life easier with maintainable unit testing
- select the contributor at the end of the page -
If you’ve ever had to maintain a project without tests, you understand how great the fear of change can be. So, you likely either made no changes at all or you made them very slowly. Then, when you finally found a project with tests galore, you figured you could start making changes with ease, right? While that’s one possibility, there are many cases where all you’ve done is traded low confidence for the shackles of test code maintenance.
The problem is that test code deserves the same thought toward cleanliness as production code, yet tests are often slapped on without more than a passing thought as to whether or not they’ll break at the slightest of code changes. This leave us to wonder whether there’s a better way to maintain a library after its initial creation, or does it have to be a choice between fear of code-change or increased code maintenance on every change?
Most modern developers agree that any code base requiring maintenance also needs tests. So, we need a means of writing these tests that ensures they won’t fall out of sync on every code change. One way to accomplish this is by writing unit tests that test behavior instead of low-level details. This means that a test should cover the intent and not the implementation. And, just as there are patterns in production code, there are patterns we can use in testing. It’s through these patterns that we find a way to have tests offer both confidence and long-term maintainability.
If you’re starting out a new project, writing tests can be easier, as you have the ability to guide the path of maintainability from the start. One way is to avoid repetition in your suite through the use of infrastructural testing patterns. Patterns such as the abstract test infrastructure class can help to reduce setup and teardown duplication, while the abstract test driver class can reduce duplication when creating test coverage for code with inheritance.
Then, there are some even more common and simpler patterns, such as reusing test code across different data sets via test code parameterization. And, of course there’s the test-wrangling difficulties that lie with the growing prevalence of asynchronous code. Many test frameworks are building asynchronous testing abilities into their libraries, however there are cases where the framework can’t help. In these instances, I’ve found that delegating and waiting work well enough. These are some of the basic patterns you can use to help create, or even clean up, a test suite so that it withstands day-to-day changes mandated by any long-term code base.
But, what happens when you have no tests, and a substantial legacy system that still needs to be modified? You have patterns at your disposal so that you can get tests into place. One pattern, known as the golden master pattern, is meant for code that’s too intertwined for any well-meaning tests to be written. In this pattern you write a high-level, integration-y test(s), so that your code behavior is covered while you fix it and add more proper tests. Once clean tests are in place, you can scrap your slow golden master.
Even with this pattern, sometimes re-factoring to get tests in place and passing, is more difficult than adding tests from a clean slate. But, we know that tests give us the confidence to re-factor, so we must find a way to push them into place. For those difficult stretches, remembering the “Five Rules of Re-factoring,” as taught at legacy code retreats, is a means to keep calm and continue on with patience and ease:
- Baby steps
- Run tests often
- No new features/bug fixes
- Stop at your goal
- Minimize time in broken state
As we’ve seen, simply pushing out tests without thought isn’t a maintainable solution. But, with an eye for patterns, you can get close to simple tests that don’t mean pain and suffering further down the maintenance road. If you’ve ever found yourself in any of the above scenarios, or if you’re interested in learning about the aforementioned patterns, be sure to check out my new course onPragmatic Unit Testing Patterns. The course covers patterns and more, beginning with basics and building up to useful ways to build a test suite for the long term. Whether you’re new to unit testing or you’re a seasoned test writer, you’ll most likely find something here that fits your needs.