1. Introduction to Dependency Methods

In automated testing, especially when using frameworks like TestNG, managing the execution flow of test methods is crucial. Dependency Methods allow you to define relationships between test methods, ensuring that certain tests run only if their prerequisites pass. This feature optimizes test execution by preventing the unnecessary running of tests that are likely to fail due to unmet dependencies.


2. Implementing Dependency Methods

Scenario Overview

Consider a test scenario with the following steps:

  1. Open Application (openApp)
  2. Login (login)
  3. Search Functionality (search)
  4. Advanced Search Functionality (advancedSearch)
  5. Logout (logout)

Each step depends on the successful execution of the previous one. For instance, if Open Application fails, there’s no point in attempting to Login or perform any subsequent actions.

Setting Up the Test Class

Here’s how you can implement dependency methods in TestNG:

package day44;

import org.testng.Assert;
import org.testng.annotations.Test;

public class DependencyMethods {

    @Test(priority = 1)
    public void openApp() {
        System.out.println("Opening the application");
        // Simulate failure by asserting false
        Assert.assertTrue(false, "Failed to open the application");
    }

    @Test(priority = 2, dependsOnMethods = {"openApp"})
    public void login() {
        System.out.println("Logging into the application");
        // Simulate pass
        Assert.assertTrue(true, "Login successful");
    }

    @Test(priority = 3, dependsOnMethods = {"login"})
    public void search() {
        System.out.println("Executing search functionality");
        // Simulate pass
        Assert.assertTrue(true, "Search successful");
    }

    @Test(priority = 4, dependsOnMethods = {"search"})
    public void advancedSearch() {
        System.out.println("Executing advanced search functionality");
        // Simulate pass
        Assert.assertTrue(true, "Advanced search successful");
    }

    @Test(priority = 5, dependsOnMethods = {"login"})
    public void logout() {
        System.out.println("Logging out of the application");
        // Simulate pass
        Assert.assertTrue(true, "Logout successful");
    }
}

Explanation of the Code:

  • openApp Method:
    • Priority: 1
    • Behavior: Simulates opening the application. Intentionally fails by asserting false.
  • login Method:
    • Priority: 2
    • Depends on: openApp
    • Behavior: Executes only if openApp passes. Simulates a successful login.
  • search Method:
    • Priority: 3
    • Depends on: login
    • Behavior: Executes only if login passes. Simulates a successful search.
  • advancedSearch Method:
    • Priority: 4
    • Depends on: search
    • Behavior: Executes only if search passes. Simulates a successful advanced search.
  • logout Method:
    • Priority: 5
    • Depends on: login
    • Behavior: Executes only if login passes. Simulates a successful logout.

Execution Flow and Results

When you execute the DependencyMethods test class:

  1. openApp executes and fails.
  2. login, which depends on openApp, is skipped.
  3. search, which depends on login, is skipped.
  4. advancedSearch, which depends on search, is skipped.
  5. logout, which depends on login, is skipped.

Console Output:


Opening the application
FAILED: openApp
java.lang.AssertionError: Failed to open the application
    at day44.DependencyMethods.openApp(DependencyMethods.java:8)
SKIPPED: login
SKIPPED: search
SKIPPED: advancedSearch
SKIPPED: logout

TestNG Report Summary:

  • Passed: 0
  • Failed: 1 (openApp)
  • Skipped: 4 (login, search, advancedSearch, logout)

Modifying Test Outcomes

Let’s explore different scenarios by modifying the test methods’ outcomes.

Case 1: openApp Passes, login Fails

@Test(priority = 1)
public void openApp() {
    System.out.println("Opening the application");
    // Simulate pass
    Assert.assertTrue(true, "Application opened successfully");
}

@Test(priority = 2, dependsOnMethods = {"openApp"})
public void login() {
    System.out.println("Logging into the application");
    // Simulate failure
    Assert.assertTrue(false, "Login failed");
}

Execution Flow:

  1. openApp executes and passes.
  2. login executes and fails.
  3. search, advancedSearch, and logout are skipped because login failed.

Console Output:


Opening the application
Logging into the application
FAILED: login
java.lang.AssertionError: Login failed
    at day44.DependencyMethods.login(DependencyMethods.java:16)
SKIPPED: search
SKIPPED: advancedSearch
SKIPPED: logout

TestNG Report Summary:

  • Passed: 1 (openApp)
  • Failed: 1 (login)
  • Skipped: 3 (search, advancedSearch, logout)

Case 2: All Tests Pass

@Test(priority = 1)
public void openApp() {
    System.out.println("Opening the application");
    // Simulate pass
    Assert.assertTrue(true, "Application opened successfully");
}

@Test(priority = 2, dependsOnMethods = {"openApp"})
public void login() {
    System.out.println("Logging into the application");
    // Simulate pass
    Assert.assertTrue(true, "Login successful");
}

@Test(priority = 3, dependsOnMethods = {"login"})
public void search() {
    System.out.println("Executing search functionality");
    // Simulate pass
    Assert.assertTrue(true, "Search successful");
}

@Test(priority = 4, dependsOnMethods = {"search"})
public void advancedSearch() {
    System.out.println("Executing advanced search functionality");
    // Simulate pass
    Assert.assertTrue(true, "Advanced search successful");
}

@Test(priority = 5, dependsOnMethods = {"login"})
public void logout() {
    System.out.println("Logging out of the application");
    // Simulate pass
    Assert.assertTrue(true, "Logout successful");
}

Execution Flow:

  1. openApp executes and passes.
  2. login executes and passes.
  3. search executes and passes.
  4. advancedSearch executes and passes.
  5. logout executes and passes.

Console Output:


Opening the application
Logging into the application
Executing search functionality
Executing advanced search functionality
Logging out of the application

TestNG Report Summary:

  • Passed: 5 (openApp, login, search, advancedSearch, logout)
  • Failed: 0
  • Skipped: 0

3. Advantages of Dependency Methods

  1. Optimized Test Execution:
    • Prevents the execution of tests that are likely to fail due to unmet prerequisites, saving time and resources.
  2. Logical Test Flow:
    • Ensures that tests are executed in a meaningful sequence, reflecting real-world usage scenarios.
  3. Improved Test Reliability:
    • Reduces false positives by skipping dependent tests when critical steps fail.
  4. Cleaner Test Reports:
    • Provides clear insights into which tests failed and which were skipped due to dependencies, aiding in quicker debugging.

4. Best Practices for Using Dependency Methods

  1. Minimize Dependencies:
    • While dependencies are useful, excessive interdependencies can lead to fragile test suites. Aim for independent tests whenever possible.
  2. Clear Naming Conventions:
    • Use descriptive method names to make dependencies easily understandable.
  3. Avoid Circular Dependencies:
    • Ensure that no two test methods depend on each other, which can cause execution issues.
  4. Use Group Dependencies for Complex Scenarios:
    • When multiple tests depend on a group of tests, consider using group dependencies for better management.
  5. Leverage alwaysRun Attribute:
      • In certain cases, you might want a dependent test to run regardless of the dependency’s outcome. Use alwaysRun=true cautiously to override default skipping behavior.
      • Example:
    @Test(dependsOnMethods = {"openApp"}, alwaysRun = true)
    public void login() {
        // This test will run even if openApp fails
    }
    

5. Introduction to Grouping Concepts

In real-world projects, test suites often contain a large number of test cases. Organizing these tests into logical groups enhances manageability, scalability, and flexibility. TestNG’s Grouping feature allows you to categorize test methods into different groups, such as Sanity, Regression, Functional, etc., and execute specific groups as needed.


6. Implementing Grouping in TestNG

Assigning Groups to Test Methods

Consider the following test classes and methods:

  • LoginTest Class:
    • loginByEmail
    • loginByFacebook
    • loginByTwitter
  • SignUpTest Class:
    • signUpByEmail
    • signUpByFacebook
    • signUpByTwitter
  • PaymentTest Class:
    • paymentInRupees
    • paymentInDollars

Grouping Requirements:

  • Sanity Group:
    • loginByEmail
    • loginByFacebook
    • loginByTwitter
    • paymentInRupees
    • paymentInDollars
  • Regression Group:
    • signUpByEmail
    • signUpByFacebook
    • signUpByTwitter
    • paymentInRupees
    • paymentInDollars
  • Functional Group:
    • Combines both Sanity and Regression groups (i.e., paymentInRupees, paymentInDollars)

Annotating Test Methods with Groups

LoginTest Class:
package day44;

import org.testng.Assert;
import org.testng.annotations.Test;

public class LoginTest {

    @Test(priority = 1, groups = {"Sanity", "Functional"})
    public void loginByEmail() {
        System.out.println("Logging in by Email");
        // Simulate pass
        Assert.assertTrue(true, "Login by Email successful");
    }

    @Test(priority = 2, groups = {"Sanity", "Functional"})
    public void loginByFacebook() {
        System.out.println("Logging in by Facebook");
        // Simulate pass
        Assert.assertTrue(true, "Login by Facebook successful");
    }

    @Test(priority = 3, groups = {"Sanity", "Functional"})
    public void loginByTwitter() {
        System.out.println("Logging in by Twitter");
        // Simulate pass
        Assert.assertTrue(true, "Login by Twitter successful");
    }
}
SignUpTest Class:
package day44;

import org.testng.Assert;
import org.testng.annotations.Test;

public class SignUpTest {

    @Test(priority = 1, groups = {"Regression"})
    public void signUpByEmail() {
        System.out.println("Signing up by Email");
        // Simulate pass
        Assert.assertTrue(true, "Sign Up by Email successful");
    }

    @Test(priority = 2, groups = {"Regression"})
    public void signUpByFacebook() {
        System.out.println("Signing up by Facebook");
        // Simulate pass
        Assert.assertTrue(true, "Sign Up by Facebook successful");
    }

    @Test(priority = 3, groups = {"Regression"})
    public void signUpByTwitter() {
        System.out.println("Signing up by Twitter");
        // Simulate pass
        Assert.assertTrue(true, "Sign Up by Twitter successful");
    }
}
PaymentTest Class:
package day44;

import org.testng.Assert;
import org.testng.annotations.Test;

public class PaymentTest {

    @Test(priority = 1, groups = {"Sanity", "Regression", "Functional"})
    public void paymentInRupees() {
        System.out.println("Processing payment in Rupees");
        // Simulate pass
        Assert.assertTrue(true, "Payment in Rupees successful");
    }

    @Test(priority = 2, groups = {"Sanity", "Regression", "Functional"})
    public void paymentInDollars() {
        System.out.println("Processing payment in Dollars");
        // Simulate pass
        Assert.assertTrue(true, "Payment in Dollars successful");
    }
}

Key Points:

  • Multiple Group Assignments:
    • Test methods can belong to multiple groups by specifying multiple group names in the groups attribute.
  • Descriptive Group Names:
    • Use meaningful group names like Sanity, Regression, and Functional to enhance clarity and maintainability.

Configuring Group Execution in TestNG XML

To execute specific groups, you’ll need to configure your TestNG XML file accordingly.

Creating the TestNG XML File

Let’s create a TestNG XML file named grouping.xml to manage our grouped tests.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="GroupingSuite">
    <test name="GroupingTest">
        <groups>
            <run>
                
                <include name="Sanity"/>
                
                
            </run>
            <exclude>
                
                
            </exclude>
        </groups>
        <classes>
            <class name="day44.LoginTest"/>
            <class name="day44.SignUpTest"/>
            <class name="day44.PaymentTest"/>
        </classes>
    </test>
</suite>

Explanation of the XML Structure:

  • <suite> Tag:
    • Defines the entire test suite.
    • name Attribute: A descriptive name for the suite.
  • <test> Tag:
    • Represents a group of test classes.
    • name Attribute: A descriptive name for the test group.
  • <groups> Tag:
    • Contains the grouping configuration.
    • <run> Tag:
      • <include> Tag: Specifies which groups to include in the execution.
      • <exclude> Tag: Specifies which groups to exclude from the execution.
  • <classes> Tag:
    • Lists all the test classes that contain the test methods.

Executing Specific Groups

Case 1: Executing Only the Sanity Group

Configuration:

<groups>
    <run>
        <include name="Sanity"/>
    </run>
</groups>

Expected Execution:

  • Executed Test Methods:
    • loginByEmail (Sanity, Functional)
    • loginByFacebook (Sanity, Functional)
    • loginByTwitter (Sanity, Functional)
    • paymentInRupees (Sanity, Regression, Functional)
    • paymentInDollars (Sanity, Regression, Functional)
  • Total Methods Executed: 5

Console Output:


Logging in by Email
Logging in by Facebook
Logging in by Twitter
Processing payment in Rupees
Processing payment in Dollars
Case 2: Executing Only the Regression Group

Modification:

<groups>
    <run>
        <include name="Regression"/>
    </run>
</groups>

Expected Execution:

  • Executed Test Methods:
    • signUpByEmail (Regression)
    • signUpByFacebook (Regression)
    • signUpByTwitter (Regression)
    • paymentInRupees (Sanity, Regression, Functional)
    • paymentInDollars (Sanity, Regression, Functional)
  • Total Methods Executed: 5

Console Output:


Signing up by Email
Signing up by Facebook
Signing up by Twitter
Processing payment in Rupees
Processing payment in Dollars
Case 3: Executing Both Sanity and Regression Groups

Modification:

<groups>
    <run>
        <include name="Sanity"/>
        <include name="Regression"/>
    </run>
</groups>

Expected Execution:

  • Executed Test Methods:
    • All methods assigned to either Sanity or Regression groups.
    • Note: Methods belonging to both groups are executed once.
  • Total Methods Executed: 8

Console Output:


Logging in by Email
Logging in by Facebook
Logging in by Twitter
Signing up by Email
Signing up by Facebook
Signing up by Twitter
Processing payment in Rupees
Processing payment in Dollars
Case 4: Executing Only the Functional Group

Modification:

<groups>
    <run>
        <include name="Functional"/>
    </run>
</groups>

Expected Execution:

  • Executed Test Methods:
    • loginByEmail (Sanity, Functional)
    • loginByFacebook (Sanity, Functional)
    • loginByTwitter (Sanity, Functional)
    • paymentInRupees (Sanity, Regression, Functional)
    • paymentInDollars (Sanity, Regression, Functional)
  • Total Methods Executed: 5

Console Output:


Logging in by Email
Logging in by Facebook
Logging in by Twitter
Processing payment in Rupees
Processing payment in Dollars
Case 5: Executing Only the Functional Group Using Combined Group

To execute tests that belong to both Sanity and Regression, we can define a combined group, such as Functional.

Modification:

@Test(priority = 1, groups = {"Sanity", "Regression", "Functional"})
public void paymentInRupees() {
    System.out.println("Processing payment in Rupees");
    Assert.assertTrue(true, "Payment in Rupees successful");
}

@Test(priority = 2, groups = {"Sanity", "Regression", "Functional"})
public void paymentInDollars() {
    System.out.println("Processing payment in Dollars");
    Assert.assertTrue(true, "Payment in Dollars successful");
}

XML Configuration:

<groups>
    <run>
        <include name="Functional"/>
    </run>
</groups>

Expected Execution:

  • Executed Test Methods:
    • paymentInRupees (Functional)
    • paymentInDollars (Functional)
  • Total Methods Executed: 2

Console Output:


Processing payment in Rupees
Processing payment in Dollars

Including and Excluding Groups

TestNG allows you to fine-tune your test execution by including or excluding specific groups. This flexibility is beneficial when you want to run a subset of tests without modifying the test classes.

Executing Multiple Groups with Inclusion and Exclusion

Example Requirement:

  • Execute all Sanity tests except those also part of Regression.

XML Configuration:

<groups>
    <run>
        <include name="Sanity"/>
    </run>
    <exclude>
        <exclude name="Regression"/>
    </exclude>
</groups>

Explanation:

  • Include: Selects all test methods in the Sanity group.
  • Exclude: Removes test methods that are also part of the Regression group, effectively selecting only those methods that are purely in Sanity.

Expected Execution:

  • Executed Test Methods:
    • loginByEmail (Sanity, Functional)
    • loginByFacebook (Sanity, Functional)
    • loginByTwitter (Sanity, Functional)
  • Skipped Test Methods:
    • paymentInRupees (Sanity, Regression, Functional)
    • paymentInDollars (Sanity, Regression, Functional)

Console Output:


Logging in by Email
Logging in by Facebook
Logging in by Twitter

7. Advanced Grouping Techniques

Defining Multiple Groups in @Test Annotation

Test methods can belong to multiple groups, enabling versatile test execution strategies.

@Test(groups = {"Sanity", "Regression"})
public void someTestMethod() {
    // Test code
}

Using Group Dependencies

You can define dependencies based on groups rather than individual methods. This is useful when multiple tests depend on an entire group of tests.

@Test(groups = {"Login"})
public void loginTest1() {
    // Test code
}

@Test(groups = {"Login"})
public void loginTest2() {
    // Test code
}

@Test(dependsOnGroups = {"Login"})
public void postLoginTest() {
    // Test code
}

Behavior:

  • postLoginTest will execute only if all tests in the Login group pass.

8. Best Practices and Common Pitfalls

8.1. Best Practices

  1. Logical Grouping:
    • Categorize tests based on functionality, priority, or testing phases (e.g., Smoke, Sanity, Regression).
  2. Consistent Naming Conventions:
    • Use clear and consistent names for groups to avoid confusion and enhance readability.
  3. Minimal Overlapping Groups:
    • Limit the number of groups a test method belongs to to maintain clarity and avoid complexity.
  4. Avoid Grouping Solely by Technology:
    • Focus on test purpose rather than underlying technologies or tools used.
  5. Document Group Definitions:
    • Maintain documentation outlining what each group represents to aid team members in test management.
  6. Leverage Combined Groups for Complex Scenarios:
    • When necessary, create combined groups like Functional to handle tests that span multiple categories.

8.2. Common Pitfalls and Solutions

  1. Overlapping Groups:
    • Issue: Test methods belonging to too many groups can lead to confusion during execution.
    • Solution: Assign groups judiciously and ensure each group has a clear purpose.
  2. Inconsistent Group Naming:
    • Issue: Using inconsistent or vague group names hampers test management.
    • Solution: Adopt a standardized naming convention across all test methods.
  3. Neglecting to Update XML Configuration:
    • Issue: Changes in group assignments aren’t reflected in the XML file, leading to unexpected test executions.
    • Solution: Regularly update the TestNG XML configuration to align with test method groupings.
  4. Excluding Essential Groups:
    • Issue: Accidentally excluding critical groups can result in incomplete test coverage.
    • Solution: Carefully review include and exclude configurations to ensure necessary tests are not omitted.
  5. Ignoring Group Dependencies:
    • Issue: Not considering dependencies among groups can lead to execution order issues.
    • Solution: Define clear dependencies and utilize dependsOnGroups where appropriate.
  6. Hardcoding Group Names:
    • Issue: Hardcoding group names in both test methods and XML files can lead to maintenance challenges.
    • Solution: Use constants or external configuration files to manage group names centrally.

9. Conclusion

Mastering Dependency Methods and Grouping Concepts in TestNG is pivotal for building efficient, organized, and maintainable automated test suites. These features allow you to control the execution flow based on test dependencies and categorize tests for selective execution, aligning your testing strategy with project requirements.

Key Takeaways:

  • Dependency Methods:
    • Use dependsOnMethods to define dependencies between test methods.
    • Prevent the execution of dependent tests if prerequisite tests fail, optimizing test execution time and resource utilization.
  • Grouping Concepts:
    • Categorize test methods into logical groups using the groups attribute.
    • Execute specific groups by configuring the TestNG XML file, enabling flexible and targeted test runs.
    • Utilize combined groups for tests belonging to multiple categories, enhancing execution flexibility.
  • Best Practices:
    • Maintain clear and consistent groupings and dependencies.
    • Regularly update and validate TestNG XML configurations.
    • Strive for test independence while leveraging dependencies judiciously to optimize test flows.

Next Steps:

  1. Explore Advanced Grouping and Dependency Features:
    • Learn about dependsOnGroups and how to manage inter-group dependencies.
  2. Implement Parallel Testing:
    • Configure TestNG for parallel execution to further optimize test execution times.
  3. Integrate with Continuous Integration (CI) Tools:
    • Automate your TestNG test executions using CI tools like Jenkins, GitLab CI, or CircleCI.
  4. Adopt Page Object Model (POM):
    • Enhance test maintainability by separating page interactions from test logic using the POM design pattern.
  5. Enhance Reporting:
    • Utilize advanced reporting tools like ExtentReports or Allure to generate detailed and visually appealing test reports.
  6. Practice and Refine:
    • Apply dependency methods and grouping concepts in your test projects to gain hands-on experience and deepen your understanding.

Happy Testing! 🚀