In following-up with yesterday’s post on unit testing WordPress code, I ended up wanting to say more than I did. There are are a lot of things to talk about (entire books cover TDD so what could a single post offer, anyway?).
Rather than try and tackle so many things in a handful of posts, I’ll stick with writing shorter posts on a handful of these topics.
One of the things that I wanted to discuss is the how unit testing can help drive architectural decisions. The challenge with the latter, though, is that part of writing tests often comes with retrofitting tests (or how we can unit test existing code).
And this is a topic all its own.
Unit Test Existing Code
The short of it is that very few of us start off doing test-driven development from the outset of a project. And sometimes, the rules around test-driven development can seem so rigid that we can easily convince ourselves not to do it at all and just let user testing suffice.
But the advantages to having tests around our code can outweigh some of the risks that come with writing tests on an existing codebase.
In it’s purest form, test-driven development dictates that we should write tests first then write the code to make them pass. Some refer to this whole idea as Red-Green-Repeat or Red-Green-Refactor-Repeat.
The logic behind this is that we’ll be thinking about how the program should work, writing tests to evaluate that code, and then writing the code to make the tests pass. If we do it the other way around, we know how the code works so we’ll have a bias in how we write the tests.
So in the worst case scenario, we’ll have false positives when our tests pass.
Though this may be true, it’s not enough to forgo writing tests altogether, is it? That is, there is no reason to avoid retrofitting tests around our code. Sure, it may require a bit of a different approach when adding tests to existing code (you know, the whole Refactor part as mentioned above). But that doesn’t mean we can’t do it.
One of the things that come with adding tests after-the-fact is that it can cause a bit of discomfort regarding your project architecture. It exposes weaknesses in the design.
But that’s a follow-up to all of this.
In my experience, I’ve found it’s still important to get the first phase of the testing done before worrying about refactoring the codebase. If there’s a single way to do this, I don’t know what it is, but some of the things that I try are as follows:
- I review the function or class that I’m about to test. I try to test individual functions since it’s important to test each module. Sometimes this works, sometimes it doesn’t. If it doesn’t, this means that I have multiple assertions per test which some consider an indication of poor architecture.
- I see if there are code comments that explain what the function should do. If so, use that as the basis for your test. If there aren’t comments, use the function name as the basis for the test. If that doesn’t work, scan the code to get an idea as to what it looks like it should do.
- When possible, I try to talk to someone on the project or have a peer review the code and explain to me what the function should be doing. This way, I can try to write the test without actually seeing the function’s implementation.
- From there, I write my first round of tests. If they don’t pass, then I carefully look to see if the code isn’t doing what it should or if the test isn’t doing what it should. With pre-existing code, it can often be the case that the test isn’t doing what it should be doing.
If there’s little-to-no documentation in the code (because the code isn’t always self-documenting despite what some would like us to think :), it’s not always easy to discern what code should be doing from the name of the classes or functions.
Other Ways To Test
I’m sure there are many other ways to approach something like this, but the size of the team, the type of project, and other external variables dictate how much you can do.
As such, the above suggestions can only go so far.
Ultimately, it’s about trying to be as careful as possible when writing tests around existing code that you’re maximizing confidence and minimizing bias. And that’s where having a third-party or a peer developer come in to help write the code.