The testing software process is a minefield. I run tests on a daily basis for various projects and I see the undeniable benefits of automated testing. I realize that choosing an appropriate approach to automated testing can be challenging and sometimes our decisions turn out to be wrong. Then software testers are left with necessary but extensive refactoring or constant and burdensome changes. What rules should Quality Assurance follow to make automated software testing easy to implement and maintain? In this article, I’ll share my personal good practices that facilitate code maintenance and accelerate any needed refactor or debugging.
1. Independent automated test cycles
In one of the projects I had the opportunity to implement automation test scripts, the client provided a repository with automated testing that had already been written by another company. Obviously, I can’t show you how the original test cases looked like but the test structure looked something like this:
Everything works flawlessly until the first test stops working. Then you have a classic snowball effect. The “edit note” test will consequently fail too because the note’s name taken from the first test won’t be found. Same for the “delete note” test results – the edited note name won’t be found either.
Additionally, such combined automated testing will be problematic when the refactoring is only needed for the last test, i.e. “delete note”. In order to check the “delete note” test after introducing any changes to it, you need to run all three tests: “create note”, “edit note”, and “delete note”.
If your automated testing is extensive and launch takes a while, it will naturally extend the time required for maintenance. You need to wait until the “create note” and “edit note” tests (for which the data is collected) are performed. Only then you can check if changes made in the automated “delete note” tests are correct.
Finally, you’ll definitely get some errors if you try to refactor, e.g. the last test.
Same project, different test case. When you worked the app too long, it would just crash and prevent further actions. Basically, the same test cases as the previous example – refactoring or debugging of the “delete note” automated test was impossible.
You’ll be better off generating data for each test case execution separately. That will give you a full picture of how the application works.
So the previous test code after modification may look like this:
In this chapter we’ve applied the automation testing rule number one – execute tests independently from other automated tests that:
- reduce time spent on implementing new and refactoring old automated tests,
- eliminate cases of failing automated tests because of other failing automated tests,
- give an opportunity to run automated tests in parallel because automated tests have their own, separate test data.
But introducing only this one rule won’t make your solution entirely perfect because:
- if adding a note manually doesn’t work, the remaining test will fail anyway.
- if the page elements are not loaded correctly and thus are not found, the “edit note”/”delete note” automated tests will not pass,
- generating test data by UI testing (user interface) steps will take a long time, and cause an unnecessary extension of the test runtime.
It seems we need to polish the test code form a little. Another rule will help us in this: Using the API as a test data generator
2. Using the API as a test data generator
How does your web application API come in handy in test automation? All you have to do is send a request to the API to generate a note with the necessary parameters that you can use when editing or deleting.
The API solution has a few advantages:
- test execution time is significantly shorter,
- random testing errors (e.g. loading elements on frontend) are eliminated,
- if creating a note via API ends with a negative result, it gives you instant feedback with clear information that the error hides in the backend layer,
- if the creating the note error is on the frontend, you can test in editing and delete note features in automated tests any way
3. Test automation & design patterns
Okay, you’ve implemented two rules already. Your automated test cases are no longer dependent on each other. Moreover, automation tests use API that significantly stabilizes and speeds up their implementation. But it would be amazing to reduce the amount of code repetition.
I’ll show you how to do it using test automation example for a specific situation:
- Each test has separate steps for logging in the user.
- The developer makes changes to the login page, including in the selectors’ names.
Result: changes must be made to every single automated test, and applied to all with user login scenarios. The update won’t take long with 5-10 tests, but what if you have 50, 100, or 555? Not so fast anymore, eh? And the code gets duplicated anyway, which makes the test even less readable.
Design patterns are going to be lifesavers here. I’m talking about Page Object Pattern or App Actions recommended by the creators of Cypress. They will help to deal with refactor problems and facilitate the implementation of automated testing.
Applying a design pattern in our automated testing
Firstly, create the base class, and throw inside:
- element selectors used on multiple pages of the web application,
- steps performed on multiple pages of the web application and grouped into methods.
Create the “LoginPage” class that stores:
- selectors for items that appear only on the login page,
- the “loginUser” method containing the user login steps.
Create “NotesPage” class that stores:
- element selectors that you can find on the notes page,
- “CreateNote” method with steps for adding a note,
- the “CreateNoteAPI” method containing a query for user login and creating a note using the API,
- a “DeleteNote” method that contains steps for deleting a note.
Finally, add “before” and “beforeEach” sections, to eliminate the repetitive code that occurs in each test:
By dividing the test code this way, you significantly shorten the main test file and improve its readability for the whole testing team.
Benefits of automated tests with design patterns
Let’s go back to our situation – the developer has changed the names of the selectors and the tests need to be updated.
- update in the “LoginPage” class and replace out-of-date selectors with new ones,
- all automated tests use the new selectors when they are run,
- the update is not time-consuming and takes literally a moment even with 50 or 100 tests,
- methods and variables with selectors are separated into separate files, so you can reuse them without duplicating code,
- you can easily find the definition of a method or variable.
Design patterns help in organizing the code, navigating the repository, and shortening the refactor time. However, they will not eliminate necessary changes, such as updating selectors after development changes. The next part of the article will show you how to deal with that.
4. Locating elements by unique attributes
Not every project uses id for individual elements on the page. This can make implementation difficult, especially when you use test attributes that may actually change, e.g. class CSS when changing the design or generic element id. The automated testing is not very stable that way and you can’t predict what/when changes will occur.
You can advantage by adding dedicated attributes to a given element.
Let’s analyze this point with the example of our note-taking app. The notes page has a table with all the notes you’ve created:
To refer to this table in tests, you need to retrieve its characteristic attribute, class or HTML tag:
But what if there are more data tables on the page and they don’t have different attributes that you can refer to? You can add your own test attributes! If you have access to the frontend code of the application, you can add it yourselves in the appropriate place and file. If you don’t, you can always ask the frontend developer to add them in the right place.
Remember that the new attributes should be legible and give a clear message about what they refer to. You can inform developers that these are attributes needed for testing and should not be updated or removed during the code refactoring.
5. Regularly execute testing applications
None of the above rules will be 100% effective if you run the tests once in whatever. No matter how good and stable the tests are, they will cause problems if not used and not updated on a regular basis. Otherwise prepare for repetitive tasks and incredibly time-consuming, lengthy tests.
It’s worth running tests regularly:
- once a day with a bit of help from “scheduled pipelines”. With some clever automation testing tools or version control systems, you can create a configuration that will run the entire test case suite once a day (or night).
- with each merge request, an additional testing job is launched, and until the tests pass the developer cannot close their merge request.
Unfortunately, both solutions have some disadvantages:
- tests run once a day give you information that a possible issue was caused by one of the changes introduced in the last 24 hours, and sometimes that’s not specific enough,
- in the case of running tests at each merge request, the number of changes is significantly narrowed. On the other hand, waiting for all jobs in the entire pipeline to be completed is longer. Additionally, every developer would have to be involved in updating the tests, which is not always happily received.
No matter which option you choose, generate detailed test reports showing which automated tests have not passed. Just to be safe, run some manual testing to determine whether you need to improve the test or there is an actual error in the application.
What benefits of automated testing come from the aforementioned five good practices?
Maintaining tests can be simple and fast, not just a painful chore. Each of the five described rules has a positive impact on test automation, significantly speeding up the implementation and maintenance process.
Benefits are visible also from the business perspective. By saving time you’re saving money. Paradoxically, if you let test engineers sort out automated test coverage, your company and application owners will reduce costs related to not only the automation itself but the entire testing process.
Something is better than nothing.
I believe that even one of the good practices introduced to your software project will reap some automation testing benefits for the testing team. Like reducing the time spent on implementing test automation (that can be used better on more pressing issues) or running them as part of regression tests. I hope that the good practices of the test automation I presented will make it easier for you to maintain your code.