As a software developer, you are probably familiar with the buzzwords 'legacy code‘ and 'refactoring‘. In test automation (TA), these terms are less common, despite the fact that many TA setups have a significant amount of legacy code long overdue for refactoring.
Sometimes, as test automation experts, we are lucky enough to be allowed to work on the 'greenfield‘. Maintaining an automation framework from the beginning often has its pleasant sides: Being able to select the technology stack, defining the structure and architecture of the project, and much more (like in software development). But here too, there are parallels: Just like in software development, it is much more common to have to work with existing test cases. So, what do we do when we arrive at a TA framework that is already outdated?
The most common and, at the same time, most devastating symptom of legacy test automation: test cases no longer work. In the worst case, all test cases may even fail. Or they are unstable and may work today, but might fail tomorrow.
In such scenarios, it is important to know principles and approaches for long-lasting test automation and to consider them during development, maintenance and further development. The following principles always help, whether you are allowed to be on a project from the beginning or to supervise it at a later stage.
What is an outdated (legacy) source code?
|The definition says: "Source code that accesses parts of software that is no longer supported or is no longer current."|
This also applies to test cases and frameworks. Often, there you can find the so-called testing and code-smells. But, let's start with the analysis first.
Analysis of test cases
Test cases can also alter. What can we do if we have to make outdated test cases or entire TA frameworks executable?
First, we should get a clear overview of the current test cases.
Consider this: you might be executing test cases that access obsolete systems. Such cases may already have been replaced by new systems or might have become obsolete in terms of content and subject matter. Such obsolete test cases are, of course, the simplest - they shall be removed.
You should also always ask yourself these questions:
- "Does this test case still have value for quality assurance (QA), development, and the project?"
- "Does it help in regression, does it cost more than it is worth?"
A small tip: Before removing, always consult with the specialist department and explain why the test cases should be removed.
But what do we do now with the test cases that have value for us?
Look at them from different perspectives, to guarantee their functionality:
- Analyze the stability of the test environments
- Check whether the test data is up-to-date
- Scan the test cases for technical errors
Analysis of test environment and test data
It can always happen that test data (username/password, test customer profiles, value lists, etc.) no longer matches the test cases or the system being tested currently. Therefore, examine the environment and data after analyzing the test cases.
The main focus here should clearly be on an environment that is stable for test automation and test data that is, at best, only used by TA. This avoids developers, manual testers, TAs, and specialist departments from hindering each other or even producing wrong results when using the same test data. If you can still set up your environment from within the TA framework, you have hit the jackpot. Often, however, this seems like a dream of the future.
In this phase, it may also be advisable to organize support from the business field again. This often results in simple configuration adjustments of the test cases, which lead to a positive and more stable result.
Once you have checked the test cases, test environment, and test data, you can concentrate on the source code.
Analyze source code
What should we consider with the source code itself? There are many points, and they chiefly apply to TA as well as to the rest of software development. I have often heard statements like "test automation experts are not software developers", mostly in a negative aspect, and I agree with this statement in one way.
|Test automation experts are more than just software developers. They are QA-oriented and quality-conscious software developers who have set themselves the goal of increasing the quality of the system under test!|
To achieve this goal, we want to write long-lasting TA frameworks and test cases with sustainable value.
A few keywords like Clean Code, SOLID, Don’t Repeat Yourself (DRY), Keep it simple, stupid (KISS), Testing/Code Smells or Law of Demeter should be part of every TA framework and are taught in the ICAgile program. Some of the points mentioned are described in detail below.
Static code analysis (code smells)
Static code analysis is one of the most important points to check your source code for the so-called code smells.
Different tools, IDEs, and programming languages offer many ways to identify these code smells.
In .NET projects, I wish everyone can use ReSharper as the ultimate weapon against code smells and for support in refactoring. If someone is not using the extension, now is the right time to talk to a scrum master or project manager about it.
What are code smells?
Simply put, any source code that makes it harder to maintain and understand a project is a code smell. Here are a few common code smells:
- Code duplicates
- Dead code
- Uncommunicative names
- Inconsistent names
- Large classes
- Long methods
- Many transfer parameters
An easy way to make source code/test cases/unit tests incomprehensible is to describe them with meaningless or misleading comments. Comments are useful when:
- APIs need to be made available to third parties.
- Additional information is required to describe a functionality (since that detail is not in the source code itself).
- You need to show why a particular approach was selected.
- You need to emphasize what you have to pay attention to.
In all other cases, comments are nothing more than code smells and should be removed, even if they are used, for example, as 'messages‘ in an assert.
When I find commented out code passages, I always ask: Is there a reason why the passage was commented out and not removed immediately? In most cases, you will hear answers such as "yes, the code should not be lost" or "yes, maybe we will need the code again!“
Even though these statements might have been partially true in the past, in times of Git/Subversion or similar, these reasons no longer apply. We no longer use floppy disks to backup source code. We can go back to where the old code was rewritten any time. This means there is no need to comment out source code now.
Please talk to your team, delete these places, and refer to your Source Code Management (SCM) tools.
In the 1990s, when software development evolved enough to consider class derivatives to minimize dependencies, some software developers also found that as a result, the complexity of the source code also increased significantly.
The more nested the derivations become, the more difficult it is for the human brain to understand the software. Therefore, you should think carefully whether and for what you want to use derivatives. Since derivatives can be a good thing, you should try to stick to a maximum of 3-4 derivatives.
That means, if you find nested classes, you should try to pull the classes together, wherever possible.
The more source code we write, the more likely it is that we will need certain parts of it at several places. All of us know the easiest way: Ctrl+C/Ctrl+V. But, it is also the worst way.
|Don’t repeat yourself (DRY)
Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.
You can now say that this interpretation is stringent and I agree. In some cases, we, as software architects and developers, have already concluded that this principle does not always make sense. Microservices are one such example. Nevertheless, it is a goal that we should pursue in a TA environment and framework.
Identical code digits should be pulled out. The maintainability will be significantly increased.
Just like comments, unnecessary source code should also be removed without leaving any residue. Parts that cannot be reached are often still there because the source code is commented out somewhere and the rest is just left over.
And this is the reason why we should remove such code.
Uncommunicative and inconsistent names
One of the most effective ways to clean up your code is to name methods, classes, variables, or other source code in a meaningful way. In a software, use meaningful names for everything. There is no reason to use 'Ctr' instead of 'Controller' or 'L' instead of 'Count' – except, perhaps, laziness. Being QA-related software developers, we should not let that come in the way of following best practices.
Like methods, we should also pay attention to the consistent naming of our test cases. There is nothing more confusing than similar code parts being named differently. If you have agreed on a name, keep it consistent. Introduce a glossary to help new developers find their way around the project.
If you come across words like 'and' in your names, I would like to point out the first principle of SOLID.
|Single responsibility principle (SRP)
A class should have one, and only one, reason to change.
There should only be one reason why classes, methods, or test cases exist. The connective word 'and' always indicates that this place was not implemented for a single reason. Split up your test cases. The same applies to assertions. More than one assert makes it more challenging to maintain and check the test case, and in case of an error, it makes the test case difficult to interpret.
Large classes always point out that they do not adhere to the single responsibility principle. But there are also other reasons why classes grow in size. Two of them can also be found in SOLID.
|Open/closed principle (OCP)
Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.
A class should always be extendable without changing the class itself. You can achieve this by separating classes into interfaces, introducing abstractions, and not deriving them directly from the actual implementation or using them. The extension of this also describes the dependency inversion principle.
Short: Depend upon abstractions, [not] concretions.
I think this principle is known to us all in TA, and if not, then you should take a close look at the principle. Without this principle, we could hardly carry out any test automation. Especially not on unit test or integration test basis.
Besides the usual suspects (e.g., SRP) I would also like to point out the KISS (Keep it simple, stupid) principle for the long methods.
|Keep it simple, stupid (KISS)
The KISS principle states that most systems work best if they are kept simple rather than made complicated
It is unnecessary to implement complicated constructs and design patterns if there are no requirements for them at all. Developers often try to think about where this system might go before they implement it. And yes, this is good. However, it is not necessary to implement every smallest probability at once.
So KISS also points out that topics should only be implemented when needed, and not from the very beginning. This, in turn, corresponds to the agile mindset and we, as test automation experts, must keep this in mind.
Many transfer parameters
If you find that your constructors or methods have more than two call parameters, consider storing the parameters in a separate object. This increases the readability and the possibility to test the system.
These examples are only a small part of what to consider when working with source code and will possibly inspire in dealing with this topic more intensively. The bottom line is that it streamlines things for you and your team.
If you are working on a project in which you discover the mentioned topics, then nothing like "Let's get down to business!“
- Talk to your team and your project manager
- Make yourself heard
- Explain the project to them
- And then‚‘Happy Refactoring‘
Tip: By the way, this does not mean that you always have to do a big refactoring on a project. One last principle: Do it like the boy scouts. The Boy Scout Rule says: "If you come to a place (e.g., class, method, etc.) where you find something that shouldn't be (e.g., code smells), then leave the place nicer than you found it". In short: Clean up, the next boy scout will thank you for it!