Writing automated tests means writing code. It means going through processes that regular code goes through. Like code review, and refactoring. I see a lot of hesitation when it comes to refactoring one’s own code, possibly because we feel that if our code needs correction, we did a crappy job writing it the first time. But that is not the case, and refactoring should be seen as a good thing. It is meant for changing something from good to even better.
When to refactor your/or someone else’s test code?
When you are implementing something new, from scratch, and realize, as you write the code, that there is a better way to do it. You don’t even need to get to the code review phase to realize that you might be able to refactor your code. Sometimes you are aware of this possibility way sooner, so you can make the changes in your code even before the review phase. This is totally allowed and encouraged. The goal is always to produce the best working version of any software. Don’t be concerned that you spent the time writing the initial code in vain. It helped you realize that: a. that was a way to do things, but b. there is an even better way.
Also, you don’t always come up with the best idea right away. That is why usually you spend some time analyzing the best options of implementing your tests before writing the actual code. But it is not always a guarantee that you find the best option during that phase. Sometimes, it is good to take a break from the code you are trying to write, and switch context a little bit, since that will actually help your thought process. Take walks, have discussions, think out of the box.
When an extension needs to be done to existing code, but the existing code does not support being extended. This is something quite common, as many times the requirements for implementing something are quite specific. In this case, usually the refactoring involves coming up with a more generic, parameterized version of the initial code, that can allow similar extensions in the future to happen almost/out of the box. For example, initially you are supposed to test something on only one language. Then, later on, for all languages. Instead of creating a new infrastructure for each new test, you can adapt the initial one to support all the languages easily. Another example would be tests for all kinds of categories: initially there was just the one specified product that you needed to buy on a site, but then the requirements ask you to buy products from different categories and with different expected behaviors.
When there is a change in one of the user flows, and that would imply way too much work on the existing end to end tests. If a new step in the user flow is added, or an existing step is removed - and that means changing too many lines of code (or spending way too many hours on performing the changes), something is fishy. You really need to rethink the way the tests were written and refactor them accordingly. You should start getting the vibe that refactoring needs to occur when you spent too much time working on these tests (like 3-4 hours), and not even managed to sort out a tenth of existing tests during that timeframe. You will be better off refactoring the whole thing instead of wasting that amount of time changing the existing tests. Not to mention how much time you would spend if another similar change will be required in the future.
My favorite: when your test framework utilities or configuration is not proper. Or even existing tests, written by no matter who, whether it’s you or anyone else. This category includes: there is duplicate code, code that blatantly breaks coding principles, code that will eventually lead to the framework becoming unusable, dependency management issues, or anything that is truly and undoubtedly wrong. Fix. It. Right. Away. Don’t linger in the “but it has been used like this for years” thinking. You might end up with a framework that will get so clogged that it will not run anymore. It is never too late to fix something that is broken, or improve something that can be better. But it is bad to just leave it in a horrible shape, when you could actually do something about it.
When not to refactor? Well, there is this “my code is the best” syndrome. I believe most people, when they see someone else’s code (or think it’s someone else’s code), tend to say: I have a better way of doing it. Or maybe just a different one. I remember one of Simon Stewart’s keynotes that I saw, back in 2016 at SeleniumConf UK, where he said: who ever looked at someone else’s code and said “wow this is amazing, I really like this code”? Probably some people, but let’s face it. We like to change other people’s code. Let’s just make sure that we don’t do it simply because of some misguided impulse. Let’s refactor only when refactor is needed, when there is a consensus that the new code is better.
And what does better mean? It depends on the context and what it is supposed to do. Either it is shorter, runs faster, or it uses less resources, or affects less systems, or uses some great patterns, it depends. Just make sure you bring an improvement to code when you are changing it and that you don’t change it “just because you can”.