Joining a new company is an exciting time where you get to learn new frameworks and technologies. For me, joining a large charity was an opportunity to really make a difference in people’s lives. I was the automation lead, overseeing multiple scrum teams and engineering a solid automation framework across the teams.
Getting my laptop set up took much longer than expected. But there was a lot of documentation to read, so that kept me busy. Once I got my machine set up and I was ready to start running the automation tests, I cloned the repo and started looking around. At this point, I discovered it might take a while to get this under control. I opened up the base class and one of the test classes had 1000 lines 🤯
Inheriting An Automation Testing Framework
Teams relied on the framework to run their regression tests. Therefore it’s not just a case of retiring the old framework and moving to a new one. It was important for me to understand the features of the framework the teams were using and its challenges. This framework had been around for a while, so the development language had changed.
The inherited framework was written in Java whereas the knowledge within the development team wasn’t there anymore. So there was a need to move to a different programming language as well. I’m going to explain the refactoring concepts, agnostic of language as part of this article.
Deleting Code is An Encouraged Form of Refactoring
Firstly, I deleted quite a lot of code such as:
- Commented sections
- Functions that weren’t being called anywhere
- Duplicate functions
After the cleanup operation, the mountain seemed a little less daunting. By reading through the code that was redundant, I started to understand the layout and structure of the framework. The next stage was to look at the “helpers” class. It can be easy when creating a generic framework to stick lots of functions into a helper’s section.
However, it’s important to break down the specific features you are delivering into the groups. Also, keep the classes small enough for people to find what they need easily. Without grouping your helpers, you can end up duplicating functions. This happens because they can’t find what they were looking for in a reasonable time ⏲
Breaking Down Your Acceptance Tests
Moving on from the framework refactoring, the next stage was to look at the large test classes. Acceptance tests are designed for high-level core user journeys. It’s important to remember where it’s best to place those edge case test scenarios within your test suite.
I know that you are enthusiastically trying to include those complex cases. Once again, I came across very large test files, which went over the size I would recommend for any file. I would recommend trying to keep your classes under 100 lines, maximum of 200.
When you are defining your test classes they often start like the name of the page you are developing, as recommended in the Page Object Model. However, when the page gets rich with features, then you need to start thinking about breaking up your test classes. A couple of approaches I have seen in the past include:
- Positive and negative scenarios
- By components
- By CRUD operations
Chopping Up Tests By Component
I prefer to break tests down by component, aligning with the development approach used in frameworks such as React. By breaking it down to components you can now isolate the tests, making them more stable and easier to maintain ✅
By doing this, I find that you are drifting away from the acceptance criteria or the user’s behavior that you are trying to test. But that shouldn’t be the case. Keep the test scenarios centered around the user and how they would use the application as a whole.
Again, don’t focus on testing all the ways you can interact with that specific component, but on how it fits into the core user journey. For example, let’s say you are setting up your donation page for your chosen charity.
The component you are moving to its own component is the date component- within the acceptance test suite, you focus on choosing a date in the future for your charity fun run. Scenarios like adding dates in the past or dates in 2 years’ time, or entering an invalid date can be covered at the unit level.
Steps To Include In The Acceptance Tests After Refactoring
You’ve extracted all the components out into their own test classes which look cleaner and easier to read. However, now you have setup steps within your test that are not related to the actual test you are verifying. For example, you have a test class that looks something like this:
@Test (description="Edit Username")
public void EditAccount() {
// Before
driver.get("https://myaccount.com
");
driver.findElement(By.id("MyAccount")).click();
// Arrange
driver.findElement(By.id("Edit")).click();
// Act
driver.findElement(By.id(“Username”)).sendKeys(“Lewis”);
assertEquals("Lewis", driver.findElement(By.id("User")).getText());
// After - Call API to clean up data
setUsernameTo(“Mary”);
}
Following the Arrange, Act, Assert methodology, you want to keep your test concise and failing for the reason related to that specific component. Therefore any setup steps can be moved to the Before statement and any cleanup steps also should be within the After statement.
By doing this you may realize that many groups of steps are being used in lots of different test classes. This is where you may want to create reusable functions 🔁 Ensure to name the helper classes as specific as you can to not fall into the large helper class trap again.
Wrapping Up
Let’s go over what we have covered:
- Delete as much as you can before tackling any refactoring, as you might be duplicating your work when you can just delete it at the start
- Don’t follow the page object model strictly, but adopt the right approach for your context. Test files can be broken down in different ways to ensure they are not overwhelming to read and easy to search for existing functions to reuse
- Focus on the user journey, and implement your refactored tests with the user behavior in mind to ensure you retain the end-to-end coverage
I hope this guide will help you the next time you are refactoring your acceptance tests. While this may not apply to everyone’s situation, the experience of dealing with large test files is something I’ve come across quite a few times in my career. It’s often due to time constraints or simply never stopping to wonder what the implications are 💡