Test-Awesome: A Guide to Creating Clean and Maintainable Automated Tests

Rakesh Kamaraju
3 min readOct 12, 2023

In the world of test automation, writing clean and maintainable code is crucial to ensure your tests are not only functional but also easy to work with and adapt. Here are some simple, minimalist guidelines to help you achieve this goal:

1. Use Descriptive Test Names:

  • Ensure your test names clearly indicate what is being tested. This helps in quick comprehension and debugging.

2. Break Tests into Smaller Methods:

  • Avoid large, monolithic tests. Instead, break them down into smaller, focused methods. This promotes reusability and readability.

3. Follow the AAA Pattern (Arrange-Act-Assert):

  • Separate your test logic into three distinct sections: arranging the prerequisites, acting on the system, and asserting the expected outcome.

4. Leverage Helper Methods and Utilities:

  • Eliminate duplicated code by creating helper methods and utility functions.

5. Implement Page Object Model (POM):

  • For UI tests, use the Page Object Model to isolate UI mappings from your test logic.

6. Use External Test Data/Fixtures:

  • Avoid hard-coding values by using external test data or fixtures. This enhances maintainability.

7. Comment Rationale Behind Tests:

  • Add comments to explain the reasoning behind your tests. This aids understanding and collaboration.

8. Organize Code into Separate Files/Classes:

  • Structure your tests and supporting code into separate files or classes for better organization.

9. Name Elements Intuitively:

  • Give intuitive names to classes, variables, and methods. Clarity is key.

10. Maintain Consistent Code Formatting:

  • Follow consistent spacing and indentation practices. This enhances readability.

11. Keep Tests Independent and Modular:

  • Ensure your tests are independent to avoid dependency issues. Modular tests are easier to maintain.

12. Make Tests Idempotent and Repeatable:

  • Reset the application state as needed to make tests repeatable and independent.

13. Use Dependency Injection:

  • Externalize dependencies and configurations through dependency injection to enhance flexibility.

14. Abide by DRY Principles:

  • Embrace the “Don’t Repeat Yourself” (DRY) philosophy by creating reusable functions.

15. Handle Assertions and Errors Appropriately:

  • Use available assertion methods and implement proper exception handling, logging, and error messages.

16. Enforce Coding Standards:

  • Follow recognized style guides and use linting tools to enforce coding standards consistently.

17. Keep Test Code Separate:

  • Separate automated test code from production code for clarity and maintainability.

18. Leverage Source Control and Code Reviews:

  • Use version control systems and conduct code reviews to ensure the quality and maintainability of your test code.
package ios.appname.pages;

import io.appium.java_client.AppiumBy;
import io.appium.java_client.ios.IOSDriver;
import io.qameta.allure.Step;
import org.openqa.selenium.By;
import base.BaseIOSPage;

/**
* Owner: Rakesh Kamaraju
* Date: October, 12 2023
* Story: MT-1234 Login
* Page object for the login page.
* Contains locators and methods for login page interactions.
*/
public class NrLoginPage extends BaseIOSPage {

/**
* Login Page Mobile Elements
*/
By lblTerms = AppiumBy.accessibilityId("View Terms & Privacy details");
By txtUsername = By.xpath("(//XCUIElementTypeTextField)[1]");
By txtPassword = By.xpath("(//XCUIElementTypeSecureTextField)[1]");
By btnLogin = AppiumBy.accessibilityId("LOG IN");
By lblError = AppiumBy.accessibilityId("Error");
By btnOk = AppiumBy.accessibilityId("OK");

/**
* LoginPage constructor
*
* @param driver The IOSDriver instance
*/
public NrLoginPage(IOSDriver driver) {
super(driver);
}

/**
* Login with valid and invalid credentials
*
* @param strUsername - The username to enter
* @param strPassword - The password to enter
*/
@Step("Login with strUsername: {0} and strPassword: {1}")
public void loginAppWithInvalidAndValidCredentials(String strUsername, String strPassword) {
writeTextToElement(txtUsername, strUsername, "Username", true);
writeTextToElement(txtPassword, strPassword, "Password", true);
clickElement(btnLogin, "Login Button");
}

/**
* Asserts login error message is displayed
*
* @param strExpectedMessage - Enter the expected message
*/
@Step("Verify strExpectedMessage {0} is displayed after invalid login")
public void assertLoginErrorMessage(String strExpectedMessage) {
waitVisibility(lblError, "Error");
assertElementTextEquals(AppiumBy.accessibilityId(strExpectedMessage), strExpectedMessage, "Login error message");
clickElement(btnOk, "OK");
}

/**
* Navigates to login page
*/
@Step("Navigate to login page")
public void goToLoginPage(String strRealDevice) {
deepLink("nouria://localhost/Login", strRealDevice, btnLogin);
waitVisibility(lblTerms, "View Terms & Privacy Details");
}

}

The ultimate aim is to create tests that are not only functional but also readable, expressive, and easy to maintain. Remember, automated tests are code, and they should adhere to clean code practices to ensure their long-term effectiveness.

--

--