13 February, 2014

Sharing data across Tests in different TestClasses

Sharing data across Tests in different TestClasses

So today I saw another interesting question posted on the TestNG-Users google forum. How does one go about sharing data across test methods which are spilt over different test classes.

Here’s two ways which I can think of in which this can be done.

Approach 1: Via the TestClass’s instance.

Here’s how you do it.

Here are two classes that I will be using to demonstrate how to get this done.

First we will need an interface so that we can use it to access the shared data [ Program to interface is a very widely used technique in the world of Java ]

Here’s how the interface looks like :

public interface DataGrabber {
    List<Integer> getSumValue();
}

Now lets take a look at the class which apart from doing some tests on the data, also saves the data so that it can be used by other test classes. I am going to call it as Generator

package organized.chaos.testng;
 
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
 
import static org.testng.Assert.assertTrue;
 
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
 
public class Generator implements DataGrabber {
    private List<Integer> sumValue;
 
    public List<Integer> getSumValue() {
        return Collections.unmodifiableList(sumValue);
    }
 
    public Generator() {
        sumValue = new ArrayList<Integer>();
    }
 
    @Test(groups = "parent", dataProvider = "generateNumbers")
    public void testNumbers(Integer a, Integer b) {
        assertTrue(a != 0);
        assertTrue(b != 0);
        sumValue.add(a + b);
    }
 
    @DataProvider
    public Object[][] generateNumbers() {
        return new Object[][] { { 1, 2 }, { 3, 4 } };
    }
 
}

If you pay close attention you would notice that this class implements our DataGrabber interface. This is being done so that it becomes easy for us to read data from the Generator class’s instance using the interface.

Now lets look at our second class which is going to feed on the data that is generated by our Generator class. I am going to call it Consumer

package organized.chaos.testng;
 
import java.util.List;
 
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
 
import org.testng.ITestContext;
import org.testng.ITestNGMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
 
public class Consumer {
    private List<Integer> allTheValues = null;
 
    @BeforeMethod(alwaysRun = true)
    public void dataGrabber(ITestContext ctx) {
        for (ITestNGMethod eachMethod : ctx.getAllTestMethods()) {
            //We are specifically looking for the method testNumbers() because we know that once we find this method,
            //the instance of the class to which this method belongs to will have our details. 
            if (eachMethod.getConstructorOrMethod().getName().equals("testNumbers")) {
                //First we query the method to give out its instance
                Object testClassObject = eachMethod.getInstance();
                //Now we check if the instance implements our interface. We do this because we will cast the instance into the 
                //interface type to access our data
                if (testClassObject instanceof DataGrabber) {
                    allTheValues = ((DataGrabber) testClassObject).getSumValue();
                    //Ok.. now that we found our data, lets break out of the loop. No point in continuing further.
                    break;
                }
            }
        }
 
    }
 
    @Test(dependsOnGroups = "parent", groups = "child")
    public void myCrazyTest() {
        assertNotNull(allTheValues);
        assertFalse(allTheValues.isEmpty());
        assertTrue(allTheValues.contains(new Integer(3)));
        assertTrue(allTheValues.contains(new Integer(7)));
 
    }
}

And here’s how the suite file looks like, which is going to show how it runs :

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="Suite" parallel="false" verbose="2">
    <test name="Test">
        <groups>
            <run>
                <include name="parent"></include>
                <include name="child"></include>
            </run>
        </groups>
        <classes>
            <class name="organized.chaos.testng.Generator" />
            <class name="organized.chaos.testng.Consumer" />
        </classes>
    </test> 
</suite> 

Approach 2 Via the <test> attributes.

This time, we aren’t going to be using any interface, but we will bank on the fact that TestNG lets me add attributes to a <test>.

Here’s how the Generator class would look like [ Lets call it Generatorv2 ]

package organized.chaos.testng;
 
import static org.testng.Assert.assertTrue;
 
import java.util.ArrayList;
import java.util.List;
 
import org.testng.ITestContext;
import org.testng.annotations.AfterClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
 
public class Generatorv2 {
    public static final String MY_ATTRIBUTE = "sumOfValues";
    private List<Integer> sumValue;
 
    @AfterClass(alwaysRun = true)
    public void insertValueIntoAttribute(ITestContext ctx) {
        ctx.setAttribute(MY_ATTRIBUTE, sumValue);
    }
 
    public Generatorv2() {
        sumValue = new ArrayList<Integer>();
    }
 
    @Test(groups = "parent", dataProvider = "generateNumbers")
    public void testNumbers(Integer a, Integer b) {
        assertTrue(a != 0);
        assertTrue(b != 0);
        sumValue.add(a + b);
    }
 
    @DataProvider
    public Object[][] generateNumbers() {
        return new Object[][] { { 1, 2 }, { 3, 4 } };
    }
 
}

As you can see, we are adding attributes to the ITestContext. ITestContext is TestNG’s way of representing a <test> tag.

Now lets look at how the Consumer class is going to look like [ yep, you guessed it. I am going to be calling it as Consumerv2.. Gosh! either you must be good in mind reading or am getting way too predictable with my class names 🙂 ]

package organized.chaos.testng;
 
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
 
import java.util.List;
 
import org.testng.ITestContext;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
 
public class Consumerv2 {
    private List<Integer> allTheValues = null;
 
    @BeforeClass(alwaysRun=true)
    @SuppressWarnings("unchecked")
    public void fetchData(ITestContext ctx) {
        allTheValues = (List<Integer>) ctx.getAttribute(Generatorv2.MY_ATTRIBUTE);
    }
 
    @Test(dependsOnGroups = "parent", groups = "child")
    public void myCrazyTest() {
        assertNotNull(allTheValues);
        assertFalse(allTheValues.isEmpty());
        assertTrue(allTheValues.contains(new Integer(3)));
        assertTrue(allTheValues.contains(new Integer(7)));
 
    }
}

Now lets take a look at how our suite file would look like :

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="Suite" parallel="false" verbose="2">
    <test name="Test">
        <groups>
            <run>
                <include name="parent"></include>
                <include name="child"></include>
            </run>
        </groups>
        <classes>
            <class name="organized.chaos.testng.Generatorv2" />
            <class name="organized.chaos.testng.Consumerv2" />
        </classes>
    </test> 
</suite> 

As you can see, there wasn’t much of a change in our suite file except for our class name.

Just one thing you would notice in both the suites. We are running via groups and our Test classes leverage dependsOnGroups. In case you are wondering why did I do this, it was just to force TestNG to run my tests from the Generator class first and then my tests from Consumer. If my Test methods all resided in the same class, I could have used dependsOnMethods but since they are now scattered across different Test classes, dependsOnGroupsis the only way to force “order” amidst “chaos” 🙂

Hope this post would help you get started with “Sharing data” among test classes.

For any queries, log an issue here.


Tags: